spec-kitty-cli 0.12.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. spec_kitty_cli-0.12.1.dist-info/METADATA +1767 -0
  2. spec_kitty_cli-0.12.1.dist-info/RECORD +242 -0
  3. spec_kitty_cli-0.12.1.dist-info/WHEEL +4 -0
  4. spec_kitty_cli-0.12.1.dist-info/entry_points.txt +2 -0
  5. spec_kitty_cli-0.12.1.dist-info/licenses/LICENSE +21 -0
  6. specify_cli/__init__.py +171 -0
  7. specify_cli/acceptance.py +627 -0
  8. specify_cli/agent_utils/README.md +157 -0
  9. specify_cli/agent_utils/__init__.py +9 -0
  10. specify_cli/agent_utils/status.py +356 -0
  11. specify_cli/cli/__init__.py +6 -0
  12. specify_cli/cli/commands/__init__.py +46 -0
  13. specify_cli/cli/commands/accept.py +189 -0
  14. specify_cli/cli/commands/agent/__init__.py +22 -0
  15. specify_cli/cli/commands/agent/config.py +382 -0
  16. specify_cli/cli/commands/agent/context.py +191 -0
  17. specify_cli/cli/commands/agent/feature.py +1057 -0
  18. specify_cli/cli/commands/agent/release.py +11 -0
  19. specify_cli/cli/commands/agent/tasks.py +1253 -0
  20. specify_cli/cli/commands/agent/workflow.py +801 -0
  21. specify_cli/cli/commands/context.py +246 -0
  22. specify_cli/cli/commands/dashboard.py +85 -0
  23. specify_cli/cli/commands/implement.py +973 -0
  24. specify_cli/cli/commands/init.py +827 -0
  25. specify_cli/cli/commands/init_help.py +62 -0
  26. specify_cli/cli/commands/merge.py +755 -0
  27. specify_cli/cli/commands/mission.py +240 -0
  28. specify_cli/cli/commands/ops.py +265 -0
  29. specify_cli/cli/commands/orchestrate.py +640 -0
  30. specify_cli/cli/commands/repair.py +175 -0
  31. specify_cli/cli/commands/research.py +165 -0
  32. specify_cli/cli/commands/sync.py +364 -0
  33. specify_cli/cli/commands/upgrade.py +249 -0
  34. specify_cli/cli/commands/validate_encoding.py +186 -0
  35. specify_cli/cli/commands/validate_tasks.py +186 -0
  36. specify_cli/cli/commands/verify.py +310 -0
  37. specify_cli/cli/helpers.py +123 -0
  38. specify_cli/cli/step_tracker.py +91 -0
  39. specify_cli/cli/ui.py +192 -0
  40. specify_cli/core/__init__.py +53 -0
  41. specify_cli/core/agent_context.py +311 -0
  42. specify_cli/core/config.py +96 -0
  43. specify_cli/core/context_validation.py +362 -0
  44. specify_cli/core/dependency_graph.py +351 -0
  45. specify_cli/core/git_ops.py +129 -0
  46. specify_cli/core/multi_parent_merge.py +323 -0
  47. specify_cli/core/paths.py +260 -0
  48. specify_cli/core/project_resolver.py +110 -0
  49. specify_cli/core/stale_detection.py +263 -0
  50. specify_cli/core/tool_checker.py +79 -0
  51. specify_cli/core/utils.py +43 -0
  52. specify_cli/core/vcs/__init__.py +114 -0
  53. specify_cli/core/vcs/detection.py +341 -0
  54. specify_cli/core/vcs/exceptions.py +85 -0
  55. specify_cli/core/vcs/git.py +1304 -0
  56. specify_cli/core/vcs/jujutsu.py +1208 -0
  57. specify_cli/core/vcs/protocol.py +285 -0
  58. specify_cli/core/vcs/types.py +249 -0
  59. specify_cli/core/version_checker.py +261 -0
  60. specify_cli/core/worktree.py +506 -0
  61. specify_cli/dashboard/__init__.py +28 -0
  62. specify_cli/dashboard/diagnostics.py +204 -0
  63. specify_cli/dashboard/handlers/__init__.py +17 -0
  64. specify_cli/dashboard/handlers/api.py +143 -0
  65. specify_cli/dashboard/handlers/base.py +65 -0
  66. specify_cli/dashboard/handlers/features.py +390 -0
  67. specify_cli/dashboard/handlers/router.py +81 -0
  68. specify_cli/dashboard/handlers/static.py +50 -0
  69. specify_cli/dashboard/lifecycle.py +541 -0
  70. specify_cli/dashboard/scanner.py +437 -0
  71. specify_cli/dashboard/server.py +123 -0
  72. specify_cli/dashboard/static/dashboard/dashboard.css +722 -0
  73. specify_cli/dashboard/static/dashboard/dashboard.js +1424 -0
  74. specify_cli/dashboard/static/spec-kitty.png +0 -0
  75. specify_cli/dashboard/templates/__init__.py +36 -0
  76. specify_cli/dashboard/templates/index.html +258 -0
  77. specify_cli/doc_generators.py +621 -0
  78. specify_cli/doc_state.py +408 -0
  79. specify_cli/frontmatter.py +384 -0
  80. specify_cli/gap_analysis.py +915 -0
  81. specify_cli/gitignore_manager.py +300 -0
  82. specify_cli/guards.py +145 -0
  83. specify_cli/legacy_detector.py +83 -0
  84. specify_cli/manifest.py +286 -0
  85. specify_cli/merge/__init__.py +63 -0
  86. specify_cli/merge/executor.py +653 -0
  87. specify_cli/merge/forecast.py +215 -0
  88. specify_cli/merge/ordering.py +126 -0
  89. specify_cli/merge/preflight.py +230 -0
  90. specify_cli/merge/state.py +185 -0
  91. specify_cli/merge/status_resolver.py +354 -0
  92. specify_cli/mission.py +654 -0
  93. specify_cli/missions/documentation/command-templates/implement.md +309 -0
  94. specify_cli/missions/documentation/command-templates/plan.md +275 -0
  95. specify_cli/missions/documentation/command-templates/review.md +344 -0
  96. specify_cli/missions/documentation/command-templates/specify.md +206 -0
  97. specify_cli/missions/documentation/command-templates/tasks.md +189 -0
  98. specify_cli/missions/documentation/mission.yaml +113 -0
  99. specify_cli/missions/documentation/templates/divio/explanation-template.md +192 -0
  100. specify_cli/missions/documentation/templates/divio/howto-template.md +168 -0
  101. specify_cli/missions/documentation/templates/divio/reference-template.md +179 -0
  102. specify_cli/missions/documentation/templates/divio/tutorial-template.md +146 -0
  103. specify_cli/missions/documentation/templates/generators/jsdoc.json.template +18 -0
  104. specify_cli/missions/documentation/templates/generators/sphinx-conf.py.template +36 -0
  105. specify_cli/missions/documentation/templates/plan-template.md +269 -0
  106. specify_cli/missions/documentation/templates/release-template.md +222 -0
  107. specify_cli/missions/documentation/templates/spec-template.md +172 -0
  108. specify_cli/missions/documentation/templates/task-prompt-template.md +140 -0
  109. specify_cli/missions/documentation/templates/tasks-template.md +159 -0
  110. specify_cli/missions/research/command-templates/merge.md +388 -0
  111. specify_cli/missions/research/command-templates/plan.md +125 -0
  112. specify_cli/missions/research/command-templates/review.md +144 -0
  113. specify_cli/missions/research/command-templates/tasks.md +225 -0
  114. specify_cli/missions/research/mission.yaml +115 -0
  115. specify_cli/missions/research/templates/data-model-template.md +33 -0
  116. specify_cli/missions/research/templates/plan-template.md +161 -0
  117. specify_cli/missions/research/templates/research/evidence-log.csv +18 -0
  118. specify_cli/missions/research/templates/research/source-register.csv +18 -0
  119. specify_cli/missions/research/templates/research-template.md +35 -0
  120. specify_cli/missions/research/templates/spec-template.md +64 -0
  121. specify_cli/missions/research/templates/task-prompt-template.md +148 -0
  122. specify_cli/missions/research/templates/tasks-template.md +114 -0
  123. specify_cli/missions/software-dev/command-templates/accept.md +75 -0
  124. specify_cli/missions/software-dev/command-templates/analyze.md +183 -0
  125. specify_cli/missions/software-dev/command-templates/checklist.md +286 -0
  126. specify_cli/missions/software-dev/command-templates/clarify.md +157 -0
  127. specify_cli/missions/software-dev/command-templates/constitution.md +432 -0
  128. specify_cli/missions/software-dev/command-templates/dashboard.md +101 -0
  129. specify_cli/missions/software-dev/command-templates/implement.md +41 -0
  130. specify_cli/missions/software-dev/command-templates/merge.md +383 -0
  131. specify_cli/missions/software-dev/command-templates/plan.md +171 -0
  132. specify_cli/missions/software-dev/command-templates/review.md +32 -0
  133. specify_cli/missions/software-dev/command-templates/specify.md +321 -0
  134. specify_cli/missions/software-dev/command-templates/tasks.md +566 -0
  135. specify_cli/missions/software-dev/mission.yaml +100 -0
  136. specify_cli/missions/software-dev/templates/plan-template.md +132 -0
  137. specify_cli/missions/software-dev/templates/spec-template.md +116 -0
  138. specify_cli/missions/software-dev/templates/task-prompt-template.md +140 -0
  139. specify_cli/missions/software-dev/templates/tasks-template.md +159 -0
  140. specify_cli/orchestrator/__init__.py +75 -0
  141. specify_cli/orchestrator/agent_config.py +224 -0
  142. specify_cli/orchestrator/agents/__init__.py +170 -0
  143. specify_cli/orchestrator/agents/augment.py +112 -0
  144. specify_cli/orchestrator/agents/base.py +243 -0
  145. specify_cli/orchestrator/agents/claude.py +112 -0
  146. specify_cli/orchestrator/agents/codex.py +106 -0
  147. specify_cli/orchestrator/agents/copilot.py +137 -0
  148. specify_cli/orchestrator/agents/cursor.py +139 -0
  149. specify_cli/orchestrator/agents/gemini.py +115 -0
  150. specify_cli/orchestrator/agents/kilocode.py +94 -0
  151. specify_cli/orchestrator/agents/opencode.py +132 -0
  152. specify_cli/orchestrator/agents/qwen.py +96 -0
  153. specify_cli/orchestrator/config.py +455 -0
  154. specify_cli/orchestrator/executor.py +642 -0
  155. specify_cli/orchestrator/integration.py +1230 -0
  156. specify_cli/orchestrator/monitor.py +898 -0
  157. specify_cli/orchestrator/scheduler.py +832 -0
  158. specify_cli/orchestrator/state.py +508 -0
  159. specify_cli/orchestrator/testing/__init__.py +122 -0
  160. specify_cli/orchestrator/testing/availability.py +346 -0
  161. specify_cli/orchestrator/testing/fixtures.py +684 -0
  162. specify_cli/orchestrator/testing/paths.py +218 -0
  163. specify_cli/plan_validation.py +107 -0
  164. specify_cli/scripts/debug-dashboard-scan.py +61 -0
  165. specify_cli/scripts/tasks/acceptance_support.py +695 -0
  166. specify_cli/scripts/tasks/task_helpers.py +506 -0
  167. specify_cli/scripts/tasks/tasks_cli.py +848 -0
  168. specify_cli/scripts/validate_encoding.py +180 -0
  169. specify_cli/task_metadata_validation.py +274 -0
  170. specify_cli/tasks_support.py +447 -0
  171. specify_cli/template/__init__.py +47 -0
  172. specify_cli/template/asset_generator.py +206 -0
  173. specify_cli/template/github_client.py +334 -0
  174. specify_cli/template/manager.py +193 -0
  175. specify_cli/template/renderer.py +99 -0
  176. specify_cli/templates/AGENTS.md +190 -0
  177. specify_cli/templates/POWERSHELL_SYNTAX.md +229 -0
  178. specify_cli/templates/agent-file-template.md +35 -0
  179. specify_cli/templates/checklist-template.md +42 -0
  180. specify_cli/templates/claudeignore-template +58 -0
  181. specify_cli/templates/command-templates/accept.md +141 -0
  182. specify_cli/templates/command-templates/analyze.md +253 -0
  183. specify_cli/templates/command-templates/checklist.md +352 -0
  184. specify_cli/templates/command-templates/clarify.md +224 -0
  185. specify_cli/templates/command-templates/constitution.md +432 -0
  186. specify_cli/templates/command-templates/dashboard.md +175 -0
  187. specify_cli/templates/command-templates/implement.md +190 -0
  188. specify_cli/templates/command-templates/merge.md +374 -0
  189. specify_cli/templates/command-templates/plan.md +171 -0
  190. specify_cli/templates/command-templates/research.md +88 -0
  191. specify_cli/templates/command-templates/review.md +510 -0
  192. specify_cli/templates/command-templates/specify.md +321 -0
  193. specify_cli/templates/command-templates/status.md +92 -0
  194. specify_cli/templates/command-templates/tasks.md +199 -0
  195. specify_cli/templates/git-hooks/pre-commit +22 -0
  196. specify_cli/templates/git-hooks/pre-commit-agent-check +37 -0
  197. specify_cli/templates/git-hooks/pre-commit-encoding-check +142 -0
  198. specify_cli/templates/plan-template.md +108 -0
  199. specify_cli/templates/spec-template.md +118 -0
  200. specify_cli/templates/task-prompt-template.md +165 -0
  201. specify_cli/templates/tasks-template.md +161 -0
  202. specify_cli/templates/vscode-settings.json +13 -0
  203. specify_cli/text_sanitization.py +225 -0
  204. specify_cli/upgrade/__init__.py +18 -0
  205. specify_cli/upgrade/detector.py +239 -0
  206. specify_cli/upgrade/metadata.py +182 -0
  207. specify_cli/upgrade/migrations/__init__.py +65 -0
  208. specify_cli/upgrade/migrations/base.py +80 -0
  209. specify_cli/upgrade/migrations/m_0_10_0_python_only.py +359 -0
  210. specify_cli/upgrade/migrations/m_0_10_12_constitution_cleanup.py +99 -0
  211. specify_cli/upgrade/migrations/m_0_10_14_update_implement_slash_command.py +176 -0
  212. specify_cli/upgrade/migrations/m_0_10_1_populate_slash_commands.py +174 -0
  213. specify_cli/upgrade/migrations/m_0_10_2_update_slash_commands.py +172 -0
  214. specify_cli/upgrade/migrations/m_0_10_6_workflow_simplification.py +174 -0
  215. specify_cli/upgrade/migrations/m_0_10_8_fix_memory_structure.py +252 -0
  216. specify_cli/upgrade/migrations/m_0_10_9_repair_templates.py +168 -0
  217. specify_cli/upgrade/migrations/m_0_11_0_workspace_per_wp.py +182 -0
  218. specify_cli/upgrade/migrations/m_0_11_1_improved_workflow_templates.py +173 -0
  219. specify_cli/upgrade/migrations/m_0_11_1_update_implement_slash_command.py +160 -0
  220. specify_cli/upgrade/migrations/m_0_11_2_improved_workflow_templates.py +173 -0
  221. specify_cli/upgrade/migrations/m_0_11_3_workflow_agent_flag.py +114 -0
  222. specify_cli/upgrade/migrations/m_0_12_0_documentation_mission.py +155 -0
  223. specify_cli/upgrade/migrations/m_0_12_1_remove_kitty_specs_from_gitignore.py +183 -0
  224. specify_cli/upgrade/migrations/m_0_2_0_specify_to_kittify.py +80 -0
  225. specify_cli/upgrade/migrations/m_0_4_8_gitignore_agents.py +118 -0
  226. specify_cli/upgrade/migrations/m_0_5_0_encoding_hooks.py +141 -0
  227. specify_cli/upgrade/migrations/m_0_6_5_commands_rename.py +169 -0
  228. specify_cli/upgrade/migrations/m_0_6_7_ensure_missions.py +228 -0
  229. specify_cli/upgrade/migrations/m_0_7_2_worktree_commands_dedup.py +89 -0
  230. specify_cli/upgrade/migrations/m_0_7_3_update_scripts.py +114 -0
  231. specify_cli/upgrade/migrations/m_0_8_0_remove_active_mission.py +82 -0
  232. specify_cli/upgrade/migrations/m_0_8_0_worktree_agents_symlink.py +148 -0
  233. specify_cli/upgrade/migrations/m_0_9_0_frontmatter_only_lanes.py +346 -0
  234. specify_cli/upgrade/migrations/m_0_9_1_complete_lane_migration.py +656 -0
  235. specify_cli/upgrade/migrations/m_0_9_2_research_mission_templates.py +221 -0
  236. specify_cli/upgrade/registry.py +121 -0
  237. specify_cli/upgrade/runner.py +284 -0
  238. specify_cli/validators/__init__.py +14 -0
  239. specify_cli/validators/paths.py +154 -0
  240. specify_cli/validators/research.py +428 -0
  241. specify_cli/verify_enhanced.py +270 -0
  242. specify_cli/workspace_context.py +224 -0
@@ -0,0 +1,408 @@
1
+ """Documentation State Management for Spec Kitty.
2
+
3
+ This module manages documentation mission state persistence in feature meta.json files.
4
+ State includes iteration mode, selected Divio types, configured generators, and audit metadata.
5
+
6
+ Documentation State Schema for meta.json
7
+ ========================================
8
+
9
+ The documentation_state field is added to feature meta.json files for
10
+ documentation mission features. It persists state between iterations.
11
+
12
+ Schema:
13
+ {
14
+ "documentation_state": {
15
+ "iteration_mode": "initial" | "gap_filling" | "feature_specific",
16
+ "divio_types_selected": ["tutorial", "how-to", "reference", "explanation"],
17
+ "generators_configured": [
18
+ {
19
+ "name": "sphinx" | "jsdoc" | "rustdoc",
20
+ "language": "python" | "javascript" | "typescript" | "rust",
21
+ "config_path": "relative/path/to/config.py"
22
+ }
23
+ ],
24
+ "target_audience": "developers" | "end-users" | "contributors" | "operators",
25
+ "last_audit_date": "2026-01-12T00:00:00Z" | null,
26
+ "coverage_percentage": 0.75 # 0.0 to 1.0
27
+ }
28
+ }
29
+
30
+ Fields:
31
+ - iteration_mode: How this documentation mission was run
32
+ - divio_types_selected: Which Divio types user chose to include
33
+ - generators_configured: Which generators were set up and where
34
+ - target_audience: Primary documentation audience
35
+ - last_audit_date: When gap analysis last ran (null if never)
36
+ - coverage_percentage: Overall doc coverage from most recent audit (0.0 if initial)
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ import json
42
+ from datetime import datetime
43
+ from pathlib import Path
44
+ from typing import List, Literal, Optional, TypedDict
45
+
46
+
47
+ class GeneratorConfig(TypedDict):
48
+ """Generator configuration entry."""
49
+
50
+ name: Literal["sphinx", "jsdoc", "rustdoc"]
51
+ language: str
52
+ config_path: str
53
+
54
+
55
+ class DocumentationState(TypedDict):
56
+ """Documentation state schema for meta.json."""
57
+
58
+ iteration_mode: Literal["initial", "gap_filling", "feature_specific"]
59
+ divio_types_selected: List[str]
60
+ generators_configured: List[GeneratorConfig]
61
+ target_audience: str
62
+ last_audit_date: Optional[str] # ISO datetime or null
63
+ coverage_percentage: float # 0.0 to 1.0
64
+
65
+
66
+ # ============================================================================
67
+ # Individual Field Setters (T041-T044)
68
+ # ============================================================================
69
+
70
+
71
+ def set_iteration_mode(
72
+ meta_file: Path, iteration_mode: Literal["initial", "gap_filling", "feature_specific"]
73
+ ) -> None:
74
+ """Set iteration mode in feature meta.json.
75
+
76
+ Args:
77
+ meta_file: Path to meta.json
78
+ iteration_mode: Iteration mode to store
79
+
80
+ Raises:
81
+ FileNotFoundError: If meta.json doesn't exist
82
+ ValueError: If iteration_mode is invalid
83
+ """
84
+ valid_modes = {"initial", "gap_filling", "feature_specific"}
85
+ if iteration_mode not in valid_modes:
86
+ raise ValueError(
87
+ f"Invalid iteration_mode: {iteration_mode}. Must be one of: {valid_modes}"
88
+ )
89
+
90
+ # Read existing meta.json
91
+ with open(meta_file, "r") as f:
92
+ meta = json.load(f)
93
+
94
+ # Initialize documentation_state if not present
95
+ if "documentation_state" not in meta:
96
+ meta["documentation_state"] = {}
97
+
98
+ # Set iteration mode
99
+ meta["documentation_state"]["iteration_mode"] = iteration_mode
100
+
101
+ # Write back
102
+ with open(meta_file, "w") as f:
103
+ json.dump(meta, f, indent=2)
104
+
105
+
106
+ def set_divio_types_selected(meta_file: Path, divio_types: List[str]) -> None:
107
+ """Set selected Divio types in feature meta.json.
108
+
109
+ Args:
110
+ meta_file: Path to meta.json
111
+ divio_types: List of Divio types to store
112
+
113
+ Raises:
114
+ FileNotFoundError: If meta.json doesn't exist
115
+ ValueError: If any type is invalid
116
+ """
117
+ valid_types = {"tutorial", "how-to", "reference", "explanation"}
118
+ invalid_types = set(divio_types) - valid_types
119
+ if invalid_types:
120
+ raise ValueError(
121
+ f"Invalid Divio types: {invalid_types}. Must be one of: {valid_types}"
122
+ )
123
+
124
+ # Read existing meta.json
125
+ with open(meta_file, "r") as f:
126
+ meta = json.load(f)
127
+
128
+ # Initialize documentation_state if not present
129
+ if "documentation_state" not in meta:
130
+ meta["documentation_state"] = {}
131
+
132
+ # Set Divio types
133
+ meta["documentation_state"]["divio_types_selected"] = divio_types
134
+
135
+ # Write back
136
+ with open(meta_file, "w") as f:
137
+ json.dump(meta, f, indent=2)
138
+
139
+
140
+ def set_generators_configured(meta_file: Path, generators: List[GeneratorConfig]) -> None:
141
+ """Set configured generators in feature meta.json.
142
+
143
+ Args:
144
+ meta_file: Path to meta.json
145
+ generators: List of generator configs, each with:
146
+ - name: Generator name (sphinx, jsdoc, rustdoc)
147
+ - language: Language (python, javascript, rust)
148
+ - config_path: Path to config file (relative to project root)
149
+
150
+ Raises:
151
+ FileNotFoundError: If meta.json doesn't exist
152
+ ValueError: If generator config is invalid
153
+ """
154
+ # Validate generator configs
155
+ valid_names = {"sphinx", "jsdoc", "rustdoc"}
156
+ for gen in generators:
157
+ if "name" not in gen:
158
+ raise ValueError(f"Generator config missing 'name' field: {gen}")
159
+ if gen["name"] not in valid_names:
160
+ raise ValueError(
161
+ f"Invalid generator name: {gen['name']}. Must be one of: {valid_names}"
162
+ )
163
+ if "language" not in gen:
164
+ raise ValueError(f"Generator config missing 'language' field: {gen}")
165
+ if "config_path" not in gen:
166
+ raise ValueError(f"Generator config missing 'config_path' field: {gen}")
167
+
168
+ # Read existing meta.json
169
+ with open(meta_file, "r") as f:
170
+ meta = json.load(f)
171
+
172
+ # Initialize documentation_state if not present
173
+ if "documentation_state" not in meta:
174
+ meta["documentation_state"] = {}
175
+
176
+ # Set generators
177
+ meta["documentation_state"]["generators_configured"] = generators
178
+
179
+ # Write back
180
+ with open(meta_file, "w") as f:
181
+ json.dump(meta, f, indent=2)
182
+
183
+
184
+ def set_audit_metadata(
185
+ meta_file: Path, last_audit_date: Optional[datetime], coverage_percentage: float
186
+ ) -> None:
187
+ """Set audit metadata in feature meta.json.
188
+
189
+ Args:
190
+ meta_file: Path to meta.json
191
+ last_audit_date: When gap analysis last ran (None if never)
192
+ coverage_percentage: Overall doc coverage (0.0 to 1.0)
193
+
194
+ Raises:
195
+ FileNotFoundError: If meta.json doesn't exist
196
+ ValueError: If coverage_percentage is out of range
197
+ """
198
+ if not (0.0 <= coverage_percentage <= 1.0):
199
+ raise ValueError(
200
+ f"coverage_percentage must be 0.0-1.0, got {coverage_percentage}"
201
+ )
202
+
203
+ # Read existing meta.json
204
+ with open(meta_file, "r") as f:
205
+ meta = json.load(f)
206
+
207
+ # Initialize documentation_state if not present
208
+ if "documentation_state" not in meta:
209
+ meta["documentation_state"] = {}
210
+
211
+ # Set audit metadata
212
+ meta["documentation_state"]["last_audit_date"] = (
213
+ last_audit_date.isoformat() if last_audit_date else None
214
+ )
215
+ meta["documentation_state"]["coverage_percentage"] = coverage_percentage
216
+
217
+ # Write back
218
+ with open(meta_file, "w") as f:
219
+ json.dump(meta, f, indent=2)
220
+
221
+
222
+ # ============================================================================
223
+ # Comprehensive State Read/Write Functions (T045)
224
+ # ============================================================================
225
+
226
+
227
+ def read_documentation_state(meta_file: Path) -> Optional[DocumentationState]:
228
+ """Read documentation state from feature meta.json.
229
+
230
+ Args:
231
+ meta_file: Path to meta.json
232
+
233
+ Returns:
234
+ DocumentationState dict if present, None if not a documentation mission
235
+ or if state is missing (backward compatibility)
236
+
237
+ Raises:
238
+ FileNotFoundError: If meta.json doesn't exist
239
+ json.JSONDecodeError: If meta.json is invalid JSON
240
+ """
241
+ with open(meta_file, "r") as f:
242
+ meta = json.load(f)
243
+
244
+ # Check if this is a documentation mission
245
+ if meta.get("mission") != "documentation":
246
+ return None
247
+
248
+ # Get documentation_state (may be missing for old features)
249
+ return meta.get("documentation_state")
250
+
251
+
252
+ def write_documentation_state(meta_file: Path, state: DocumentationState) -> None:
253
+ """Write complete documentation state to feature meta.json.
254
+
255
+ Args:
256
+ meta_file: Path to meta.json
257
+ state: Complete documentation state to write
258
+
259
+ Raises:
260
+ FileNotFoundError: If meta.json doesn't exist
261
+ ValueError: If state is invalid
262
+ """
263
+ # Validate state structure
264
+ required_fields = {
265
+ "iteration_mode",
266
+ "divio_types_selected",
267
+ "generators_configured",
268
+ "target_audience",
269
+ "last_audit_date",
270
+ "coverage_percentage",
271
+ }
272
+ missing_fields = required_fields - set(state.keys())
273
+ if missing_fields:
274
+ raise ValueError(f"State missing required fields: {missing_fields}")
275
+
276
+ # Read existing meta.json
277
+ with open(meta_file, "r") as f:
278
+ meta = json.load(f)
279
+
280
+ # Update documentation_state
281
+ meta["documentation_state"] = state
282
+
283
+ # Write back
284
+ with open(meta_file, "w") as f:
285
+ json.dump(meta, f, indent=2)
286
+
287
+
288
+ def initialize_documentation_state(
289
+ meta_file: Path,
290
+ iteration_mode: str,
291
+ divio_types: List[str],
292
+ generators: List[GeneratorConfig],
293
+ target_audience: str,
294
+ ) -> DocumentationState:
295
+ """Initialize documentation state for a new documentation mission.
296
+
297
+ Args:
298
+ meta_file: Path to meta.json
299
+ iteration_mode: initial, gap_filling, or feature_specific
300
+ divio_types: Selected Divio types
301
+ generators: Configured generators
302
+ target_audience: Primary documentation audience
303
+
304
+ Returns:
305
+ Initialized DocumentationState
306
+
307
+ Raises:
308
+ FileNotFoundError: If meta.json doesn't exist
309
+ """
310
+ state: DocumentationState = {
311
+ "iteration_mode": iteration_mode, # type: ignore
312
+ "divio_types_selected": divio_types,
313
+ "generators_configured": generators,
314
+ "target_audience": target_audience,
315
+ "last_audit_date": None,
316
+ "coverage_percentage": 0.0,
317
+ }
318
+
319
+ write_documentation_state(meta_file, state)
320
+ return state
321
+
322
+
323
+ def update_documentation_state(meta_file: Path, **updates) -> DocumentationState:
324
+ """Update specific fields in documentation state.
325
+
326
+ Args:
327
+ meta_file: Path to meta.json
328
+ **updates: Fields to update (iteration_mode, divio_types_selected, etc.)
329
+
330
+ Returns:
331
+ Updated DocumentationState
332
+
333
+ Raises:
334
+ FileNotFoundError: If meta.json doesn't exist
335
+ ValueError: If state doesn't exist (call initialize first)
336
+ """
337
+ # Read current state
338
+ state = read_documentation_state(meta_file)
339
+
340
+ if state is None:
341
+ raise ValueError(
342
+ f"No documentation state found in {meta_file}. "
343
+ f"Call initialize_documentation_state() first."
344
+ )
345
+
346
+ # Update fields
347
+ for key, value in updates.items():
348
+ if key in state:
349
+ state[key] = value # type: ignore
350
+
351
+ # Write back
352
+ write_documentation_state(meta_file, state)
353
+ return state
354
+
355
+
356
+ # ============================================================================
357
+ # Backward Compatibility / Migration (T046)
358
+ # ============================================================================
359
+
360
+
361
+ def ensure_documentation_state(meta_file: Path) -> None:
362
+ """Ensure meta.json has documentation_state field.
363
+
364
+ For backward compatibility with old documentation mission features.
365
+ If feature is a documentation mission but lacks documentation_state,
366
+ initialize with sensible defaults.
367
+
368
+ Args:
369
+ meta_file: Path to meta.json
370
+ """
371
+ with open(meta_file, "r") as f:
372
+ meta = json.load(f)
373
+
374
+ # Check if documentation mission
375
+ if meta.get("mission") != "documentation":
376
+ return # Not a documentation mission, nothing to do
377
+
378
+ # Check if state already exists
379
+ if "documentation_state" in meta:
380
+ return # Already has state
381
+
382
+ # Initialize with defaults
383
+ meta["documentation_state"] = {
384
+ "iteration_mode": "initial", # Assume first run
385
+ "divio_types_selected": [], # Unknown, user must specify
386
+ "generators_configured": [], # Unknown, user must configure
387
+ "target_audience": "developers", # Reasonable default
388
+ "last_audit_date": None,
389
+ "coverage_percentage": 0.0,
390
+ }
391
+
392
+ # Write back
393
+ with open(meta_file, "w") as f:
394
+ json.dump(meta, f, indent=2)
395
+
396
+
397
+ def get_state_version(state: DocumentationState) -> int:
398
+ """Get state schema version for future migrations.
399
+
400
+ Currently all states are version 1.
401
+
402
+ Args:
403
+ state: Documentation state
404
+
405
+ Returns:
406
+ Schema version number
407
+ """
408
+ return state.get("_schema_version", 1) # type: ignore