Data Model: Spec Kitty 3.2 Documentation Refresh
Mission: spec-kitty-3-2-docs-01KS4KSZ | Phase: 1 (Design) | Date: 2026-05-21
All shapes below are filesystem-backed. The Python representations are @dataclass(slots=True, frozen=True) (or TypedDict for shapes loaded from YAML where mutation is needed before validation). Every entity is read-only at run time from the docs tooling's perspective; mutation happens through editorial edits to the underlying markdown/YAML.
VersionTag (enum)
Canonical version-relevance classification (FR-001).
class VersionTag(StrEnum):
CURRENT = "current" # 3.2-current
SUPPORTED = "supported" # 3.1-relevant, not 3.2-complete
ARCHIVAL = "archival" # 1.x or 2.x
MIGRATION = "migration" # version transition guidance
INTERNAL = "internal" # dev-only / non-public
Validation: every docs page that appears in the page inventory must map to exactly one tag.
DivioType (enum)
class DivioType(StrEnum):
TUTORIAL = "tutorial"
HOW_TO = "how-to"
REFERENCE = "reference"
EXPLANATION = "explanation"
NONE = "none" # planning artifacts, READMEs, etc. that don't fit Divio
PageInventoryEntry
Row in docs/development/3-2-page-inventory.yaml. Loaded with ruamel.yaml.
@dataclass(slots=True, frozen=True)
class PageInventoryEntry:
path: str # repo-relative path, e.g., "docs/how-to/install-macos.md"
tag: VersionTag
divio_type: DivioType
owning_workstream: str # one of "A","B","C","D","E","F" or "none"
current_target: bool # True if this page is expected to appear in 3.2-current nav
citation_refs: list[str] # external URLs cited; empty list for non-harness pages
notes: str | None # optional one-line context
YAML on disk (one row per page):
- path: docs/how-to/install-macos.md
tag: current
divio_type: how-to
owning_workstream: E
current_target: true
citation_refs: []
notes: pip/pipx/uv on macOS
- path: docs/1x/index.md
tag: archival
divio_type: none
owning_workstream: C
current_target: false
citation_refs: []
notes: 1.x archive landing page
Validation rules:
pathmust point to an existing file underdocs/,architecture/, or root.- If
tag == archival, thencurrent_target == false. - If
tag == current, thencurrent_target == true. - If
tag == migration, the file body must contain a migration banner pattern (regex defined in the leakage-check contract).
CommandPathEntry
Result of walking the live Typer app. Materialised in memory by scripts/docs/_typer_walker.py; not persisted to disk except as a build artifact under scripts/docs/_cache/ (gitignored).
@dataclass(slots=True, frozen=True)
class CommandPathEntry:
path: tuple[str, ...] # ("spec-kitty","agent","decision","open")
kind: Literal["command", "group"]
hidden: bool
deprecated: bool
help_summary: str # the first line / short description rendered by Typer
source_file: str | None # best-effort; populated where introspection can locate it
source_function: str | None
requires_saas_sync: bool # True if this path only materialises with SPEC_KITTY_ENABLE_SAAS_SYNC=1
Validation:
path[0] == "spec-kitty"for every entry.kind == "group"implies the entry contributes only structure, not a leaf command.requires_saas_sync == Truefor descendants oftrackerand forissue-search(percli-audit-3-2.md).
MetaIssueEntry
Row in docs/development/3-2-cli-reference-audit-meta-issues.md (FR-010). Stored as a markdown table; loadable into the shape below for check_cli_reference_freshness.py cross-checks.
class ProblemType(StrEnum):
INACCURATE = "inaccurate"
INCOMPLETE = "incomplete"
STALE = "stale"
MISSING = "missing"
CONFUSING = "confusing"
VERSION_LEAKAGE = "version_leakage"
class BlockingStatus(StrEnum):
BLOCKING = "blocking" # blocks publication
NON_BLOCKING = "non_blocking" # acknowledged, not blocking
RESOLVED = "resolved" # fixed in code and verified
@dataclass(slots=True, frozen=True)
class MetaIssueEntry:
command_path: str # space-joined, e.g., "spec-kitty agent decision open"
source_file: str # e.g., "src/specify_cli/cli/commands/agent_decision.py"
source_function: str | None
observed_help: str
observed_behavior_or_test_evidence: str
problem_type: ProblemType
recommended_fix: str
owner_area: str # e.g., "agent", "tracker", "doctor", "core"
blocking_status: BlockingStatus
Validation: every row references a real command path or explicitly states "PATH NO LONGER VISIBLE" in observed_help for stale entries.
HarnessEntry
Row in the harness support matrix (docs/reference/supported-harnesses.md) and per-harness pages.
class HarnessMechanism(StrEnum):
SLASH_COMMAND = "slash_command"
PROMPT = "prompt"
WORKFLOW = "workflow"
SKILL = "skill"
COMMAND_FILE = "command_file"
CONFIG = "config"
class SupportTier(StrEnum):
FIRST_CLASS = "first_class"
SUPPORTED = "supported"
PARTIAL = "partial"
EXPERIMENTAL = "experimental"
ARCHIVED = "archived"
@dataclass(slots=True, frozen=True)
class HarnessEntry:
name: str # display name, e.g., "Claude Code"
key: str # short identifier, e.g., "claude"
repo_directory: str # e.g., ".claude/commands" — installed surface
mechanism: HarnessMechanism
support_tier: SupportTier
external_doc_citations: list[str] # at least one for tier >= supported
page_path: str | None # docs/how-to/harnesses/<key>.md when promoted
notes: str | None
Validation:
support_tier in {first_class, supported}requiresexternal_doc_citationsnon-empty.support_tier == archivedrequirespage_pathto live underdocs/migration/or be absent.keyis unique across the matrix.
InstallTargetEntry
One cell of the (tool × OS) install matrix (FR-017).
class InstallTool(StrEnum):
PIP = "pip"
PIPX = "pipx"
UV_TOOL = "uv_tool"
class OS(StrEnum):
MACOS = "macos"
LINUX = "linux"
WINDOWS = "windows"
@dataclass(slots=True, frozen=True)
class InstallTargetEntry:
tool: InstallTool
os: OS
install_command: str # e.g., "uv tool install spec-kitty-cli"
upgrade_command: str
uninstall_command: str
verification_command: str # e.g., "spec-kitty --version"
platform_notes: list[str] # PATH, PowerShell, py-launcher caveats
docs_page: str # repo-relative path of the how-to that covers this cell
Validation: nine entries total (3 tools × 3 OSes); every entry has a non-empty install_command, upgrade_command, uninstall_command, and verification_command.
FreshnessReport
Output of scripts/docs/check_docs_freshness.py (FR-020).
@dataclass(slots=True, frozen=True)
class FreshnessFinding:
rule_id: str # e.g., "REF-MISSING", "REF-EXTRA", "LEAK-CURRENT-LINKS-ARCHIVAL"
severity: Literal["error", "warning"]
location: str # path or "(virtual)"
message: str
suggested_action: str
@dataclass(slots=True, frozen=True)
class FreshnessReport:
started_at: str # ISO8601
cli_version: str # pyproject.toml version
visible_paths_count: int
reference_entries_count: int
inventory_rows_count: int
findings: list[FreshnessFinding]
saas_sync_flag: bool # was SPEC_KITTY_ENABLE_SAAS_SYNC=1 in effect?
exit_code: Literal[0, 1, 2, 3] # 0 clean / 1 violations / 2 input errors / 3 environmental setup errors
Validation:
exit_code == 0↔findingscontains noseverity == "error"entries.- If
saas_sync_flag == False, the report includes a top-levelWARN: tracker/issue-search not capturedfinding (rule_idENV-SAAS-SYNC-OFF) but does not block publication on its own.
OccurrenceMap (bulk-edit guardrail)
Loaded from kitty-specs/spec-kitty-3-2-docs-01KS4KSZ/occurrence_map.yaml when workstream A2 (frontmatter rollout) or workstream C (archive/migration moves) is in flight. Tasks-phase artifact, not implemented by tooling here; documented for plan completeness.
change_mode: bulk_edit
identifier: version_tag
categories:
code_symbols:
action: not_applicable
import_paths:
action: not_applicable
filesystem_paths:
action: rewrite
occurrences:
- path: docs/1x/**
from_pattern: docs/1x/
to_pattern: docs/archive/1x/
- path: docs/2x/**
from_pattern: docs/2x/
to_pattern: docs/archive/2x/
serialized_keys:
action: add
occurrences:
- file_glob: docs/**/*.md
key: version_tag
rule: per-page from page-inventory.yaml
cli_commands:
action: not_applicable
user_facing_strings:
action: review
occurrences:
- context: archive banners in moved 1.x/2.x pages
tests_fixtures:
action: not_applicable
logs_telemetry:
action: not_applicable
Cross-entity invariants
1. For every CommandPathEntry with hidden == False, the docs/reference/cli-commands.md file contains a heading or inline mention naming the full path. Enforced by the architectural test and by check_cli_reference_freshness.py. 2. For every PageInventoryEntry with tag == current and current_target == True, the file at path either contains a version_tag: current frontmatter line or is exempt because it predates the rollout and is on the deferred-frontmatter list within 3-2-page-inventory.yaml. 3. The set of HarnessEntry.support_tier in {first_class, supported} matches the set of pages under docs/how-to/harnesses/ once decision 01KS4KTS4V300M9MMTS1AJEGXY is resolved or its plan default ("matrix-first promotion") applies. 4. The set of InstallTargetEntry cells is exhaustive: nine entries, no duplicates, every entry maps to exactly one docs_page. 5. MetaIssueEntry rows with blocking_status == BLOCKING block the publication checklist (FR-021); rows marked RESOLVED may be referenced from CHANGELOG entries.