agentic-project-kit 1.0.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 (310) hide show
  1. agentic_project_kit/__init__.py +1 -0
  2. agentic_project_kit/absolute_path_portability_audit.py +241 -0
  3. agentic_project_kit/access_level_policy.py +14 -0
  4. agentic_project_kit/access_levels.py +22 -0
  5. agentic_project_kit/action_registry.py +195 -0
  6. agentic_project_kit/action_specs.py +286 -0
  7. agentic_project_kit/agent_command_runner.py +516 -0
  8. agentic_project_kit/audit_test_matrix.py +36 -0
  9. agentic_project_kit/audit_test_matrix_cli.py +43 -0
  10. agentic_project_kit/chat_bootloader.py +257 -0
  11. agentic_project_kit/chat_entrypoint_contract.py +297 -0
  12. agentic_project_kit/checks.py +511 -0
  13. agentic_project_kit/ci_readiness.py +211 -0
  14. agentic_project_kit/cli.py +147 -0
  15. agentic_project_kit/cli_commands/__init__.py +1 -0
  16. agentic_project_kit/cli_commands/absolute_path_audit.py +25 -0
  17. agentic_project_kit/cli_commands/actions.py +21 -0
  18. agentic_project_kit/cli_commands/artifact_gc.py +240 -0
  19. agentic_project_kit/cli_commands/boot.py +55 -0
  20. agentic_project_kit/cli_commands/chat.py +34 -0
  21. agentic_project_kit/cli_commands/checks.py +305 -0
  22. agentic_project_kit/cli_commands/cockpit.py +103 -0
  23. agentic_project_kit/cli_commands/command_taxonomy.py +25 -0
  24. agentic_project_kit/cli_commands/commands.py +129 -0
  25. agentic_project_kit/cli_commands/dev.py +23 -0
  26. agentic_project_kit/cli_commands/direction.py +92 -0
  27. agentic_project_kit/cli_commands/doc_currency_audit.py +25 -0
  28. agentic_project_kit/cli_commands/doc_registry.py +126 -0
  29. agentic_project_kit/cli_commands/docs.py +223 -0
  30. agentic_project_kit/cli_commands/dpa.py +1338 -0
  31. agentic_project_kit/cli_commands/evidence.py +251 -0
  32. agentic_project_kit/cli_commands/github.py +34 -0
  33. agentic_project_kit/cli_commands/governance.py +14 -0
  34. agentic_project_kit/cli_commands/gui.py +23 -0
  35. agentic_project_kit/cli_commands/gui_readiness_gate.py +27 -0
  36. agentic_project_kit/cli_commands/handoff.py +183 -0
  37. agentic_project_kit/cli_commands/human_workflows.py +430 -0
  38. agentic_project_kit/cli_commands/init.py +132 -0
  39. agentic_project_kit/cli_commands/instruction.py +75 -0
  40. agentic_project_kit/cli_commands/mutation_lock_audit.py +24 -0
  41. agentic_project_kit/cli_commands/ns_legacy_audit.py +25 -0
  42. agentic_project_kit/cli_commands/pass_already_done.py +66 -0
  43. agentic_project_kit/cli_commands/patch_failure_discipline_audit.py +26 -0
  44. agentic_project_kit/cli_commands/patch_preflight.py +37 -0
  45. agentic_project_kit/cli_commands/path_literal_audit.py +39 -0
  46. agentic_project_kit/cli_commands/patterns.py +40 -0
  47. agentic_project_kit/cli_commands/planning_docs_audit.py +25 -0
  48. agentic_project_kit/cli_commands/pr.py +133 -0
  49. agentic_project_kit/cli_commands/pr_hygiene.py +27 -0
  50. agentic_project_kit/cli_commands/profiles.py +26 -0
  51. agentic_project_kit/cli_commands/program_redundancy_audit.py +25 -0
  52. agentic_project_kit/cli_commands/project_direction.py +25 -0
  53. agentic_project_kit/cli_commands/release.py +392 -0
  54. agentic_project_kit/cli_commands/release_publish_orchestration.py +36 -0
  55. agentic_project_kit/cli_commands/remote_branch_hygiene.py +152 -0
  56. agentic_project_kit/cli_commands/remote_next.py +57 -0
  57. agentic_project_kit/cli_commands/removed_ns_commands.py +65 -0
  58. agentic_project_kit/cli_commands/rule_registry.py +129 -0
  59. agentic_project_kit/cli_commands/rules.py +222 -0
  60. agentic_project_kit/cli_commands/scaffold.py +46 -0
  61. agentic_project_kit/cli_commands/slice.py +21 -0
  62. agentic_project_kit/cli_commands/standard_gates_audit_suite.py +27 -0
  63. agentic_project_kit/cli_commands/state.py +51 -0
  64. agentic_project_kit/cli_commands/status_current_state_audit.py +30 -0
  65. agentic_project_kit/cli_commands/todo.py +53 -0
  66. agentic_project_kit/cli_commands/transfer.py +19 -0
  67. agentic_project_kit/cli_commands/transfer_context_flow.py +398 -0
  68. agentic_project_kit/cli_commands/transfer_context_helpers.py +398 -0
  69. agentic_project_kit/cli_commands/transfer_diagnostics.py +767 -0
  70. agentic_project_kit/cli_commands/transfer_evidence_flow.py +368 -0
  71. agentic_project_kit/cli_commands/transfer_handoff_flow.py +594 -0
  72. agentic_project_kit/cli_commands/transfer_order_contract.py +74 -0
  73. agentic_project_kit/cli_commands/transfer_post_merge_complete.py +442 -0
  74. agentic_project_kit/cli_commands/transfer_post_merge_settle.py +171 -0
  75. agentic_project_kit/cli_commands/transfer_pr_closeout_complete.py +124 -0
  76. agentic_project_kit/cli_commands/transfer_pr_create_flow.py +743 -0
  77. agentic_project_kit/cli_commands/transfer_pr_merge_flow.py +547 -0
  78. agentic_project_kit/cli_commands/transfer_repo_after_pr.py +160 -0
  79. agentic_project_kit/cli_commands/transfer_repo_pre_pr.py +664 -0
  80. agentic_project_kit/cli_commands/transfer_runner_flow.py +131 -0
  81. agentic_project_kit/cli_commands/transfer_runtime_initial.py +194 -0
  82. agentic_project_kit/cli_commands/transfer_shared.py +274 -0
  83. agentic_project_kit/cli_commands/validation.py +200 -0
  84. agentic_project_kit/cli_commands/version.py +27 -0
  85. agentic_project_kit/cli_commands/work_orders.py +219 -0
  86. agentic_project_kit/cli_commands/workflow.py +471 -0
  87. agentic_project_kit/cli_commands/workflow_guard.py +26 -0
  88. agentic_project_kit/cli_commands/workspace.py +252 -0
  89. agentic_project_kit/cockpit.py +608 -0
  90. agentic_project_kit/cockpit_readiness.py +67 -0
  91. agentic_project_kit/command_authority_audit.py +192 -0
  92. agentic_project_kit/command_inbox_check.py +103 -0
  93. agentic_project_kit/command_manifest.py +591 -0
  94. agentic_project_kit/command_queue_contract.py +161 -0
  95. agentic_project_kit/command_selector.py +245 -0
  96. agentic_project_kit/command_taxonomy.py +264 -0
  97. agentic_project_kit/commit_guard.py +67 -0
  98. agentic_project_kit/communication_artifact_gc.py +495 -0
  99. agentic_project_kit/communication_rule_context.py +437 -0
  100. agentic_project_kit/communication_state.py +115 -0
  101. agentic_project_kit/composed_wrapper_invariants.py +97 -0
  102. agentic_project_kit/composite_short_circuit.py +140 -0
  103. agentic_project_kit/contract.py +251 -0
  104. agentic_project_kit/control_state.py +83 -0
  105. agentic_project_kit/doc_currency_audit.py +536 -0
  106. agentic_project_kit/doc_lifecycle.py +1179 -0
  107. agentic_project_kit/doc_lifecycle_signals.py +446 -0
  108. agentic_project_kit/doc_lifecycle_sweep.py +639 -0
  109. agentic_project_kit/doc_mesh.py +491 -0
  110. agentic_project_kit/doctor.py +379 -0
  111. agentic_project_kit/documentation_registry.py +1122 -0
  112. agentic_project_kit/documentation_system_audit.py +310 -0
  113. agentic_project_kit/dpa_current_handoff_lifecycle.py +549 -0
  114. agentic_project_kit/dpa_dp2_decision_readiness.py +362 -0
  115. agentic_project_kit/dpa_dp3_dp4_adjudication.py +604 -0
  116. agentic_project_kit/dpa_dp5_block_new_gate.py +294 -0
  117. agentic_project_kit/dpa_dp5_stage_adoption.py +615 -0
  118. agentic_project_kit/dpa_dp5_strict_gate.py +187 -0
  119. agentic_project_kit/dpa_final_closeout.py +627 -0
  120. agentic_project_kit/dpa_fixture_evidence.py +680 -0
  121. agentic_project_kit/dpa_maintainer_record_check.py +711 -0
  122. agentic_project_kit/dpa_post_dp2_scope_assessment.py +734 -0
  123. agentic_project_kit/dpa_probe_002_readiness.py +509 -0
  124. agentic_project_kit/dpa_probe_003_readiness.py +417 -0
  125. agentic_project_kit/dpa_probe_004_readiness.py +437 -0
  126. agentic_project_kit/dpa_readiness.py +505 -0
  127. agentic_project_kit/dpa_readonly_probe_execution.py +500 -0
  128. agentic_project_kit/dpa_registry_contracts.py +312 -0
  129. agentic_project_kit/dpa_renderer_readiness.py +407 -0
  130. agentic_project_kit/dpa_repo_adoption_assessment.py +574 -0
  131. agentic_project_kit/dpa_stable_readiness.py +743 -0
  132. agentic_project_kit/dpa_successor_projection.py +168 -0
  133. agentic_project_kit/dpa_workspace_init_projection.py +93 -0
  134. agentic_project_kit/dpa_wrt_ch001_evidence.py +327 -0
  135. agentic_project_kit/entrypoint_slice_runner.py +153 -0
  136. agentic_project_kit/evidence_clean.py +152 -0
  137. agentic_project_kit/evidence_commit_paths.py +214 -0
  138. agentic_project_kit/evidence_finalize_log.py +204 -0
  139. agentic_project_kit/evidence_guard.py +114 -0
  140. agentic_project_kit/evidence_inspector.py +507 -0
  141. agentic_project_kit/evidence_state_contract.py +99 -0
  142. agentic_project_kit/execution_mode_state.py +148 -0
  143. agentic_project_kit/final_summary_contract.py +85 -0
  144. agentic_project_kit/finalize_guard.py +139 -0
  145. agentic_project_kit/gatekeeper_core.py +232 -0
  146. agentic_project_kit/github.py +25 -0
  147. agentic_project_kit/governance.py +59 -0
  148. agentic_project_kit/gui_action_execution.py +105 -0
  149. agentic_project_kit/gui_action_renderer.py +81 -0
  150. agentic_project_kit/gui_action_run_intent.py +98 -0
  151. agentic_project_kit/gui_action_views.py +195 -0
  152. agentic_project_kit/gui_activity_log.py +76 -0
  153. agentic_project_kit/gui_button_catalog.py +605 -0
  154. agentic_project_kit/gui_cockpit.py +365 -0
  155. agentic_project_kit/gui_cockpit_actions.py +695 -0
  156. agentic_project_kit/gui_cockpit_common.py +172 -0
  157. agentic_project_kit/gui_cockpit_header.py +501 -0
  158. agentic_project_kit/gui_cockpit_sidebar.py +387 -0
  159. agentic_project_kit/gui_cockpit_task.py +748 -0
  160. agentic_project_kit/gui_command_projection.py +355 -0
  161. agentic_project_kit/gui_communication_modes.py +130 -0
  162. agentic_project_kit/gui_dry_run.py +219 -0
  163. agentic_project_kit/gui_gatekeeper_status.py +259 -0
  164. agentic_project_kit/gui_layout_plan.py +148 -0
  165. agentic_project_kit/gui_open_folder.py +18 -0
  166. agentic_project_kit/gui_output_status_panel.py +61 -0
  167. agentic_project_kit/gui_panel_state.py +47 -0
  168. agentic_project_kit/gui_presenter.py +62 -0
  169. agentic_project_kit/gui_readiness_gate.py +206 -0
  170. agentic_project_kit/gui_release_flow.py +59 -0
  171. agentic_project_kit/gui_task_editor.py +1077 -0
  172. agentic_project_kit/gui_tk_widgets.py +177 -0
  173. agentic_project_kit/gui_tkinter_renderer.py +87 -0
  174. agentic_project_kit/gui_tkinter_shell.py +1176 -0
  175. agentic_project_kit/gui_transfer_contract.py +28 -0
  176. agentic_project_kit/gui_viewmodel.py +698 -0
  177. agentic_project_kit/gui_window_guard.py +46 -0
  178. agentic_project_kit/handoff_freshness.py +390 -0
  179. agentic_project_kit/handoff_prompt.py +180 -0
  180. agentic_project_kit/handoff_state.py +254 -0
  181. agentic_project_kit/instruction_lint.py +455 -0
  182. agentic_project_kit/llm_context_carriers.py +66 -0
  183. agentic_project_kit/llm_execution_context.py +285 -0
  184. agentic_project_kit/local_command_stack.py +133 -0
  185. agentic_project_kit/local_feature_gate.py +82 -0
  186. agentic_project_kit/local_garbage_collector.py +269 -0
  187. agentic_project_kit/models.py +18 -0
  188. agentic_project_kit/mutation_lock_audit.py +421 -0
  189. agentic_project_kit/next_turn_evidence.py +253 -0
  190. agentic_project_kit/next_turn_merge_if_green.py +459 -0
  191. agentic_project_kit/next_turn_pr_status.py +255 -0
  192. agentic_project_kit/next_turn_result.py +183 -0
  193. agentic_project_kit/next_turn_runner.py +159 -0
  194. agentic_project_kit/next_turn_slot.py +107 -0
  195. agentic_project_kit/next_turn_status.py +274 -0
  196. agentic_project_kit/ns_legacy_reference_audit.py +258 -0
  197. agentic_project_kit/ns_slice_runner.py +16 -0
  198. agentic_project_kit/operational_handoff_projection.py +124 -0
  199. agentic_project_kit/output_contract.py +80 -0
  200. agentic_project_kit/output_repair.py +61 -0
  201. agentic_project_kit/pass_already_done.py +216 -0
  202. agentic_project_kit/patch_artifact_preflight.py +147 -0
  203. agentic_project_kit/patch_cycle_workflow.py +440 -0
  204. agentic_project_kit/patch_failure_discipline_audit.py +223 -0
  205. agentic_project_kit/patch_preflight.py +215 -0
  206. agentic_project_kit/path_literal_audit.py +612 -0
  207. agentic_project_kit/patterns.py +99 -0
  208. agentic_project_kit/planning_docs_consolidation_audit.py +313 -0
  209. agentic_project_kit/portability_shell_gate.py +40 -0
  210. agentic_project_kit/post_merge_handoff_refresh.py +56 -0
  211. agentic_project_kit/post_release.py +252 -0
  212. agentic_project_kit/post_release_closeout.py +616 -0
  213. agentic_project_kit/pr_cleanup.py +172 -0
  214. agentic_project_kit/pr_closeout.py +50 -0
  215. agentic_project_kit/pr_closeout_complete.py +344 -0
  216. agentic_project_kit/pr_create_or_skip.py +71 -0
  217. agentic_project_kit/pr_hygiene.py +210 -0
  218. agentic_project_kit/program_redundancy_audit.py +251 -0
  219. agentic_project_kit/project_direction.py +840 -0
  220. agentic_project_kit/protected_change_planner.py +194 -0
  221. agentic_project_kit/release.py +401 -0
  222. agentic_project_kit/release_doi_safety.py +24 -0
  223. agentic_project_kit/release_gate_core.py +174 -0
  224. agentic_project_kit/release_metadata_authority_gate.py +328 -0
  225. agentic_project_kit/release_metadata_prep.py +43 -0
  226. agentic_project_kit/release_notes.py +823 -0
  227. agentic_project_kit/release_prep_core.py +204 -0
  228. agentic_project_kit/release_prepare.py +369 -0
  229. agentic_project_kit/release_process_guardrails.py +247 -0
  230. agentic_project_kit/release_publish_core.py +114 -0
  231. agentic_project_kit/release_publish_orchestration.py +469 -0
  232. agentic_project_kit/release_state.py +552 -0
  233. agentic_project_kit/release_verify_core.py +175 -0
  234. agentic_project_kit/remote_branch_hygiene.py +223 -0
  235. agentic_project_kit/remote_next.py +89 -0
  236. agentic_project_kit/remote_next_closeout.py +101 -0
  237. agentic_project_kit/remote_next_order_contract.py +391 -0
  238. agentic_project_kit/removed_ns_commands.py +458 -0
  239. agentic_project_kit/removed_source_audit.py +225 -0
  240. agentic_project_kit/rendering.py +13 -0
  241. agentic_project_kit/repair_report.py +79 -0
  242. agentic_project_kit/repo_identity.py +59 -0
  243. agentic_project_kit/result_report_classifier.py +57 -0
  244. agentic_project_kit/rule_ack.py +105 -0
  245. agentic_project_kit/rule_preservation.py +58 -0
  246. agentic_project_kit/rule_refresh.py +174 -0
  247. agentic_project_kit/rule_registry_registration.py +322 -0
  248. agentic_project_kit/rule_registry_report.py +206 -0
  249. agentic_project_kit/rule_registry_validator.py +704 -0
  250. agentic_project_kit/rule_snapshot.py +98 -0
  251. agentic_project_kit/rule_source_validator.py +133 -0
  252. agentic_project_kit/run_summary.py +68 -0
  253. agentic_project_kit/run_summary_renderer.py +238 -0
  254. agentic_project_kit/runtime_validator.py +68 -0
  255. agentic_project_kit/safe_file_replace.py +35 -0
  256. agentic_project_kit/safe_patch.py +47 -0
  257. agentic_project_kit/safe_push.py +218 -0
  258. agentic_project_kit/safe_remove_diagnostic.py +51 -0
  259. agentic_project_kit/scaffold.py +69 -0
  260. agentic_project_kit/site_claims.py +444 -0
  261. agentic_project_kit/site_generator.py +1140 -0
  262. agentic_project_kit/slice_gate.py +225 -0
  263. agentic_project_kit/slice_runner.py +75 -0
  264. agentic_project_kit/standard_gates_audit_suite.py +167 -0
  265. agentic_project_kit/state_freshness.py +135 -0
  266. agentic_project_kit/status_current_state_audit.py +762 -0
  267. agentic_project_kit/successor_handoff_package.py +1763 -0
  268. agentic_project_kit/templates.py +292 -0
  269. agentic_project_kit/terminal_block_guard.py +166 -0
  270. agentic_project_kit/terminal_logging.py +275 -0
  271. agentic_project_kit/todo.py +97 -0
  272. agentic_project_kit/transfer_closeout.py +136 -0
  273. agentic_project_kit/transfer_continue.py +215 -0
  274. agentic_project_kit/transfer_local_runner.py +101 -0
  275. agentic_project_kit/transfer_operation_monitor.py +236 -0
  276. agentic_project_kit/transfer_post_merge_lifecycle.py +356 -0
  277. agentic_project_kit/transfer_post_merge_settle.py +383 -0
  278. agentic_project_kit/transfer_pr_actions.py +164 -0
  279. agentic_project_kit/transfer_remote_next.py +959 -0
  280. agentic_project_kit/transfer_repo_actions.py +2253 -0
  281. agentic_project_kit/transfer_runner.py +347 -0
  282. agentic_project_kit/transfer_safety_context.py +291 -0
  283. agentic_project_kit/transfer_state.py +483 -0
  284. agentic_project_kit/transfer_uplink.py +471 -0
  285. agentic_project_kit/transfer_workflow_next.py +148 -0
  286. agentic_project_kit/typed_work_order_evidence.py +58 -0
  287. agentic_project_kit/typed_work_order_queue.py +166 -0
  288. agentic_project_kit/typed_work_order_runner.py +168 -0
  289. agentic_project_kit/work_cycle.py +317 -0
  290. agentic_project_kit/work_discard_changes.py +129 -0
  291. agentic_project_kit/work_order_patch.py +199 -0
  292. agentic_project_kit/work_order_runner.py +120 -0
  293. agentic_project_kit/work_order_uploader.py +220 -0
  294. agentic_project_kit/work_order_validator.py +185 -0
  295. agentic_project_kit/work_orders.py +246 -0
  296. agentic_project_kit/workflow_guard.py +262 -0
  297. agentic_project_kit/workflow_summary_runner.py +124 -0
  298. agentic_project_kit/workspace.py +665 -0
  299. agentic_project_kit/workspace_adopt.py +471 -0
  300. agentic_project_kit/workspace_dpa_intake.py +406 -0
  301. agentic_project_kit/workspace_init.py +487 -0
  302. agentic_project_kit/workspace_lock.py +129 -0
  303. agentic_project_kit/workspace_remove.py +381 -0
  304. agentic_project_kit/workspace_upgrade.py +324 -0
  305. agentic_project_kit/wrapper_live_status.py +104 -0
  306. agentic_project_kit-1.0.1.dist-info/METADATA +724 -0
  307. agentic_project_kit-1.0.1.dist-info/RECORD +310 -0
  308. agentic_project_kit-1.0.1.dist-info/WHEEL +4 -0
  309. agentic_project_kit-1.0.1.dist-info/entry_points.txt +3 -0
  310. agentic_project_kit-1.0.1.dist-info/licenses/LICENSE +10 -0
@@ -0,0 +1,511 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+ from typing import Any
4
+ import re
5
+ import yaml
6
+
7
+ from agentic_project_kit.documentation_registry import check_documentation_registry
8
+ from agentic_project_kit.workspace import Workspace, load_workspace
9
+
10
+
11
+ STATE_GATE_DOCUMENTS = (
12
+ "docs/STATUS.md",
13
+ "docs/TEST_GATES.md",
14
+ "docs/handoff/CURRENT_HANDOFF.md",
15
+ "docs/architecture/ARCHITECTURE_CONTRACT.md",
16
+ "docs/DOCUMENTATION_COVERAGE.yaml",
17
+ )
18
+
19
+ STATE_GATE_SECTIONS = {
20
+ "docs/STATUS.md": ("## Current Goal", "## Next Safe Step"),
21
+ "docs/TEST_GATES.md": ("## Gate Matrix", "## Outcome Reporting"),
22
+ "docs/handoff/CURRENT_HANDOFF.md": ("# Current Handoff", "## Current", "## Next"),
23
+ "docs/architecture/ARCHITECTURE_CONTRACT.md": (
24
+ "## 1. Executive Summary",
25
+ "## 2. How to Use This Document",
26
+ "## 4. Decision Rules",
27
+ "## 7. Architectural Contract",
28
+ "## 17. Acceptance Criteria for Future Work",
29
+ ),
30
+ }
31
+
32
+ WORKSPACE_STATE_GATE_DOCUMENTS = (
33
+ ".agentic/state/status.md",
34
+ ".agentic/state/handoff/README.md",
35
+ ".agentic/registries/documentation.yaml",
36
+ ".agentic/registries/rules.yaml",
37
+ )
38
+
39
+ WORKSPACE_STATE_GATE_SECTIONS = {
40
+ ".agentic/state/status.md": ("# Workspace Status",),
41
+ ".agentic/state/handoff/README.md": ("# Workspace Handoff",),
42
+ }
43
+
44
+ STALE_HANDOFF_MARKERS = (
45
+ "Create project-level docs",
46
+ "Commit documentation-state files",
47
+ "Run the local gate, inspect the diff, then commit the documentation-state update",
48
+ )
49
+
50
+ CHANGELOG_QUALITY_MIN_VERSION = (0, 3, 36)
51
+ CHANGELOG_RELEASE_HEADING = re.compile(
52
+ r"^##\s+v(?P<version>\d+\.\d+\.\d+)(?:\s+-\s+(?P<date>\d{4}-\d{2}-\d{2}))?.*$",
53
+ re.MULTILINE,
54
+ )
55
+ CHANGELOG_GENERIC_BULLET_PATTERNS = (
56
+ re.compile(r"^- prepare release metadata for v\d+\.\d+\.\d+\.?$", re.IGNORECASE),
57
+ re.compile(r"^- post-release verification complete:.*$", re.IGNORECASE),
58
+ )
59
+ CHANGELOG_SUBSTANCE_CATEGORIES = {
60
+ "release-evidence": (
61
+ "zenodo",
62
+ "doi",
63
+ "post-release",
64
+ "github release",
65
+ "release metadata",
66
+ "verified",
67
+ ),
68
+ "implementation-or-doc-change": (
69
+ "add",
70
+ "added",
71
+ "harden",
72
+ "hardened",
73
+ "close",
74
+ "closed",
75
+ "repair",
76
+ "repaired",
77
+ "wire",
78
+ "wired",
79
+ "extract",
80
+ "extracted",
81
+ "refresh",
82
+ "refreshed",
83
+ "record",
84
+ "recorded",
85
+ "translate",
86
+ "translated",
87
+ "preserve",
88
+ "preserved",
89
+ ),
90
+ "safety-or-scope": (
91
+ "bounded",
92
+ "read-only",
93
+ "disabled",
94
+ "guard",
95
+ "safety",
96
+ "without",
97
+ "no tag",
98
+ "no release",
99
+ "deferred",
100
+ "scope",
101
+ ),
102
+ "continuity-or-governance": (
103
+ "evidence",
104
+ "handoff",
105
+ "governance",
106
+ "contract",
107
+ "regression",
108
+ "successor",
109
+ "current-state",
110
+ "documentation boundary",
111
+ ),
112
+ }
113
+
114
+
115
+ def _open_marker_pattern(marker: str) -> re.Pattern[str]:
116
+ escaped_marker = re.escape(marker)
117
+ return re.compile(rf"(?:^|[\s\[{{(<]){escaped_marker}(?:\s*[:\-]|[\]}}>)])")
118
+
119
+
120
+ QUALITY_PLACEHOLDER_PATTERNS = {
121
+ "TODO": _open_marker_pattern("TODO"),
122
+ "TBD": _open_marker_pattern("TBD"),
123
+ "FIXME": _open_marker_pattern("FIXME"),
124
+ "Lorem ipsum": re.compile(r"Lorem ipsum", re.IGNORECASE),
125
+ "coming soon": re.compile(r"coming soon", re.IGNORECASE),
126
+ "to be written": re.compile(r"to be written", re.IGNORECASE),
127
+ "to be defined": re.compile(r"to be defined", re.IGNORECASE),
128
+ "placeholder marker": re.compile(r"placeholder\s+(text|section|content|value)", re.IGNORECASE),
129
+ }
130
+
131
+
132
+ SEMANTIC_QUALITY_BOUNDARY = (
133
+ "Deterministic checks can detect placeholders, stale markers, coverage gaps, "
134
+ "and structural drift. They cannot prove semantic perfection. LLM review, "
135
+ "if added later, must remain advisory and separate from hard doctor gates."
136
+ )
137
+
138
+
139
+ @dataclass(frozen=True)
140
+ class CheckContext:
141
+ root: str
142
+ mode: str
143
+ external_manifest_workspace: bool
144
+ gate_family: str
145
+ gate_documents: tuple[str, ...]
146
+ check_renders_statuses: bool
147
+ skip_status_renderer: str
148
+ invalid_manifest_error: str = ""
149
+
150
+ def as_dict(self) -> dict[str, object]:
151
+ return {
152
+ "root": self.root,
153
+ "mode": self.mode,
154
+ "external_manifest_workspace": self.external_manifest_workspace,
155
+ "gate_family": self.gate_family,
156
+ "gate_documents": list(self.gate_documents),
157
+ "check_renders_statuses": self.check_renders_statuses,
158
+ "skip_status_renderer": self.skip_status_renderer,
159
+ "invalid_manifest_error": self.invalid_manifest_error,
160
+ }
161
+
162
+
163
+ def load_yaml(path: Path) -> dict[str, Any]:
164
+ if not path.exists():
165
+ raise FileNotFoundError(f"Missing config file: {path}")
166
+ data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
167
+ if not isinstance(data, dict):
168
+ raise ValueError(f"YAML root must be a mapping: {path}")
169
+ return data
170
+
171
+
172
+ def load_optional_yaml(path: Path) -> dict[str, Any]:
173
+ if not path.exists():
174
+ return {}
175
+ return load_yaml(path)
176
+
177
+
178
+ def count_words(text: str) -> int:
179
+ return len(text.split())
180
+
181
+
182
+ def check_docs(project_root: Path) -> list[str]:
183
+ config = load_optional_yaml(project_root / "sentinel.yaml")
184
+ errors: list[str] = []
185
+
186
+ for doc in config.get("documents", []):
187
+ path = project_root / doc["path"]
188
+ if not path.exists():
189
+ errors.append(f"Missing document: {doc['path']}")
190
+ continue
191
+
192
+ content = path.read_text(encoding="utf-8")
193
+ for section in doc.get("required_sections", []):
194
+ if section not in content:
195
+ errors.append(f"{doc['path']}: missing required section {section!r}")
196
+
197
+ max_words = doc.get("max_words")
198
+ if max_words is not None:
199
+ words = count_words(content)
200
+ if words > int(max_words):
201
+ errors.append(f"{doc['path']}: too long ({words}/{max_words} words)")
202
+
203
+ min_words = doc.get("min_words")
204
+ if min_words is not None:
205
+ words = count_words(content)
206
+ if words < int(min_words):
207
+ errors.append(f"{doc['path']}: too short ({words}/{min_words} words)")
208
+
209
+ if bool(doc.get("quality_checks", True)):
210
+ errors.extend(check_document_quality(doc["path"], content))
211
+
212
+ context = build_check_context(project_root)
213
+ if context.invalid_manifest_error:
214
+ errors.append(f"Invalid workspace manifest: {context.invalid_manifest_error}")
215
+ elif context.external_manifest_workspace:
216
+ errors.extend(check_workspace_state_gate_docs(project_root))
217
+ else:
218
+ errors.extend(check_state_gate_docs(project_root))
219
+ errors.extend(check_documentation_coverage(project_root))
220
+ errors.extend(check_documentation_registry(project_root))
221
+ errors.extend(check_changelog_quality(project_root))
222
+ return errors
223
+
224
+
225
+ def _load_manifest_workspace_for_checks(project_root: Path) -> Workspace | None | ValueError:
226
+ if _is_agentic_project_kit_development_checkout(project_root):
227
+ return None
228
+ if not (project_root / ".agentic/config.yaml").exists():
229
+ return None
230
+ try:
231
+ return load_workspace(project_root, suppress_legacy_profile_warning=True)
232
+ except RuntimeError as exc:
233
+ return ValueError(str(exc))
234
+
235
+
236
+ def build_check_context(project_root: Path) -> CheckContext:
237
+ project_root = project_root.resolve()
238
+ workspace = _load_manifest_workspace_for_checks(project_root)
239
+ if isinstance(workspace, ValueError):
240
+ return CheckContext(
241
+ root=project_root.as_posix(),
242
+ mode="invalid_manifest",
243
+ external_manifest_workspace=False,
244
+ gate_family="invalid_manifest",
245
+ gate_documents=(),
246
+ check_renders_statuses=False,
247
+ skip_status_renderer="agentic-kit doctor",
248
+ invalid_manifest_error=str(workspace),
249
+ )
250
+ if workspace is not None:
251
+ return CheckContext(
252
+ root=project_root.as_posix(),
253
+ mode="external_manifest_workspace",
254
+ external_manifest_workspace=True,
255
+ gate_family="workspace_state",
256
+ gate_documents=WORKSPACE_STATE_GATE_DOCUMENTS,
257
+ check_renders_statuses=False,
258
+ skip_status_renderer="agentic-kit doctor",
259
+ )
260
+ mode = (
261
+ "selfhosting_checkout"
262
+ if _is_agentic_project_kit_development_checkout(project_root)
263
+ else "repository_state"
264
+ )
265
+ return CheckContext(
266
+ root=project_root.as_posix(),
267
+ mode=mode,
268
+ external_manifest_workspace=False,
269
+ gate_family="repository_state",
270
+ gate_documents=STATE_GATE_DOCUMENTS,
271
+ check_renders_statuses=False,
272
+ skip_status_renderer="agentic-kit doctor",
273
+ )
274
+
275
+
276
+ def _is_agentic_project_kit_development_checkout(project_root: Path) -> bool:
277
+ return (
278
+ (project_root / "src" / "agentic_project_kit").is_dir()
279
+ and (project_root / "docs" / "reference" / "agentic-kit-commands.json").exists()
280
+ and (project_root / "pyproject.toml").exists()
281
+ )
282
+
283
+
284
+ def check_document_quality(relative_path: str, content: str) -> list[str]:
285
+ """Return deterministic document-quality findings.
286
+
287
+ These checks intentionally cover only machine-checkable quality signals.
288
+ They are not a claim of semantic perfection.
289
+ """
290
+ errors: list[str] = []
291
+ for marker, pattern in QUALITY_PLACEHOLDER_PATTERNS.items():
292
+ if pattern.search(content):
293
+ errors.append(f"{relative_path}: unresolved placeholder marker {marker!r}")
294
+ return errors
295
+
296
+
297
+ def _release_version_tuple(version: str) -> tuple[int, int, int]:
298
+ major, minor, patch = version.split(".")
299
+ return int(major), int(minor), int(patch)
300
+
301
+
302
+ def _release_sections(content: str) -> list[tuple[str, str | None, str]]:
303
+ matches = list(CHANGELOG_RELEASE_HEADING.finditer(content))
304
+ sections: list[tuple[str, str | None, str]] = []
305
+ for index, match in enumerate(matches):
306
+ start = match.end()
307
+ end = matches[index + 1].start() if index + 1 < len(matches) else len(content)
308
+ sections.append((match.group("version"), match.group("date"), content[start:end].strip()))
309
+ return sections
310
+
311
+
312
+ def _substance_categories(section_text: str) -> set[str]:
313
+ normalized = section_text.lower()
314
+ return {
315
+ category
316
+ for category, terms in CHANGELOG_SUBSTANCE_CATEGORIES.items()
317
+ if any(term in normalized for term in terms)
318
+ }
319
+
320
+
321
+ def _substantive_changelog_bullets(section_text: str) -> list[str]:
322
+ bullets = [line.strip() for line in section_text.splitlines() if line.strip().startswith("- ")]
323
+ return [
324
+ bullet
325
+ for bullet in bullets
326
+ if not any(pattern.match(bullet) for pattern in CHANGELOG_GENERIC_BULLET_PATTERNS)
327
+ ]
328
+
329
+
330
+ def check_changelog_quality(project_root: Path) -> list[str]:
331
+ """Check recent release-history quality without pretending to judge prose quality.
332
+
333
+ The guard is intentionally structural and category-based. It prevents recent
334
+ release entries from collapsing into generic metadata-only bullets, but it does
335
+ not require a naive minimum bullet count and does not rewrite historical evidence.
336
+ """
337
+ path = project_root / "CHANGELOG.md"
338
+ if not path.exists():
339
+ return []
340
+
341
+ content = path.read_text(encoding="utf-8")
342
+ errors: list[str] = []
343
+ seen_versions: set[str] = set()
344
+
345
+ for version, date, body in _release_sections(content):
346
+ if version in seen_versions:
347
+ errors.append(f"CHANGELOG.md: duplicate release section v{version}")
348
+ seen_versions.add(version)
349
+
350
+ if _release_version_tuple(version) < CHANGELOG_QUALITY_MIN_VERSION:
351
+ continue
352
+
353
+ section_text = f"v{version}\n{body}"
354
+ normalized = section_text.lower()
355
+ if date is None:
356
+ errors.append(f"CHANGELOG.md: v{version} missing release date in heading")
357
+
358
+ has_zenodo_state = "zenodo" in normalized and ("doi" in normalized or "pending" in normalized)
359
+ if not has_zenodo_state:
360
+ errors.append(f"CHANGELOG.md: v{version} missing Zenodo DOI or pending verification marker")
361
+
362
+ if not _substantive_changelog_bullets(body):
363
+ errors.append(f"CHANGELOG.md: v{version} has no substantive release bullet beyond generic metadata")
364
+
365
+ categories = _substance_categories(section_text)
366
+ if len(categories) < 3:
367
+ found = ", ".join(sorted(categories)) or "none"
368
+ errors.append(f"CHANGELOG.md: v{version} lacks enough release-quality categories (found: {found})")
369
+
370
+ return errors
371
+
372
+
373
+ def check_state_gate_docs(project_root: Path) -> list[str]:
374
+ errors: list[str] = []
375
+
376
+ for relative_path in STATE_GATE_DOCUMENTS:
377
+ path = project_root / relative_path
378
+ if not path.exists():
379
+ errors.append(f"Missing state gate document: {relative_path}")
380
+ continue
381
+
382
+ content = path.read_text(encoding="utf-8")
383
+ for section in STATE_GATE_SECTIONS.get(relative_path, ()): # defensive for future docs
384
+ if section not in content:
385
+ errors.append(f"{relative_path}: missing state gate section {section!r}")
386
+
387
+ handoff_path = project_root / "docs/handoff/CURRENT_HANDOFF.md"
388
+ if handoff_path.exists():
389
+ handoff = handoff_path.read_text(encoding="utf-8")
390
+ for marker in STALE_HANDOFF_MARKERS:
391
+ if marker in handoff:
392
+ errors.append(f"docs/handoff/CURRENT_HANDOFF.md: stale handoff marker {marker!r}")
393
+
394
+ return errors
395
+
396
+
397
+ def check_workspace_state_gate_docs(project_root: Path) -> list[str]:
398
+ errors: list[str] = []
399
+
400
+ for relative_path in WORKSPACE_STATE_GATE_DOCUMENTS:
401
+ path = project_root / relative_path
402
+ if not path.exists():
403
+ errors.append(f"Missing workspace state document: {relative_path}")
404
+ continue
405
+
406
+ content = path.read_text(encoding="utf-8")
407
+ for section in WORKSPACE_STATE_GATE_SECTIONS.get(relative_path, ()):
408
+ if section not in content:
409
+ errors.append(f"{relative_path}: missing workspace state section {section!r}")
410
+
411
+ return errors
412
+
413
+
414
+ def check_documentation_coverage(project_root: Path) -> list[str]:
415
+ matrix_path = project_root / "docs/DOCUMENTATION_COVERAGE.yaml"
416
+ if not matrix_path.exists():
417
+ return ["Missing state gate document: docs/DOCUMENTATION_COVERAGE.yaml"]
418
+
419
+ try:
420
+ matrix = load_yaml(matrix_path)
421
+ except (FileNotFoundError, ValueError, yaml.YAMLError) as exc:
422
+ return [f"docs/DOCUMENTATION_COVERAGE.yaml: invalid coverage matrix ({exc})"]
423
+
424
+ errors: list[str] = []
425
+ rules = matrix.get("rules", [])
426
+ if not isinstance(rules, list) or not rules:
427
+ return ["docs/DOCUMENTATION_COVERAGE.yaml: rules must be a non-empty list"]
428
+
429
+ for rule_index, rule in enumerate(rules, start=1):
430
+ if not isinstance(rule, dict):
431
+ errors.append(f"docs/DOCUMENTATION_COVERAGE.yaml: rule {rule_index} must be a mapping")
432
+ continue
433
+ rule_id = str(rule.get("id") or f"rule-{rule_index}")
434
+ documents = rule.get("documents", [])
435
+ if not isinstance(documents, list) or not documents:
436
+ errors.append(f"docs/DOCUMENTATION_COVERAGE.yaml: {rule_id} documents must be a non-empty list")
437
+ continue
438
+
439
+ for doc_index, doc in enumerate(documents, start=1):
440
+ if not isinstance(doc, dict):
441
+ errors.append(f"docs/DOCUMENTATION_COVERAGE.yaml: {rule_id} document {doc_index} must be a mapping")
442
+ continue
443
+ relative_path = str(doc.get("path") or "").strip()
444
+ if not relative_path:
445
+ errors.append(f"docs/DOCUMENTATION_COVERAGE.yaml: {rule_id} document {doc_index} missing path")
446
+ continue
447
+ path = project_root / relative_path
448
+ if not path.exists():
449
+ errors.append(f"documentation coverage {rule_id}: missing document {relative_path}")
450
+ continue
451
+
452
+ terms = doc.get("terms", [])
453
+ if not isinstance(terms, list) or not terms:
454
+ errors.append(f"docs/DOCUMENTATION_COVERAGE.yaml: {rule_id} {relative_path} terms must be a non-empty list")
455
+ continue
456
+
457
+ content = path.read_text(encoding="utf-8")
458
+ for term in terms:
459
+ needle = str(term)
460
+ if needle not in content:
461
+ errors.append(f"documentation coverage {rule_id}: {relative_path} missing term {needle!r}")
462
+
463
+ return errors
464
+
465
+
466
+ def check_todo(project_root: Path) -> list[str]:
467
+ workspace = _load_manifest_workspace_for_checks(project_root)
468
+ if isinstance(workspace, ValueError) and not (project_root / "sentinel.yaml").exists():
469
+ return [f"Invalid workspace manifest: {workspace}"]
470
+ if workspace is not None and not (project_root / "sentinel.yaml").exists():
471
+ return []
472
+ config = load_yaml(project_root / "sentinel.yaml")
473
+ todo_path = project_root / config.get("todo", {}).get("path", ".agentic/todo.yaml")
474
+ data = load_yaml(todo_path)
475
+ errors: list[str] = []
476
+
477
+ items = data.get("items", [])
478
+ if not isinstance(items, list):
479
+ return [f"{todo_path}: items must be a list"]
480
+
481
+ required = ["id", "title", "owner", "priority", "status", "evidence_required"]
482
+ seen_ids: set[str] = set()
483
+
484
+ for index, item in enumerate(items, start=1):
485
+ if not isinstance(item, dict):
486
+ errors.append(f"{todo_path}: item {index} must be a mapping")
487
+ continue
488
+
489
+ item_id = str(item.get("id", "")).strip()
490
+ if not item_id:
491
+ errors.append(f"{todo_path}: item {index} has no id")
492
+ elif item_id in seen_ids:
493
+ errors.append(f"{todo_path}: duplicate id {item_id}")
494
+ else:
495
+ seen_ids.add(item_id)
496
+
497
+ for field in required:
498
+ if not str(item.get(field, "")).strip():
499
+ errors.append(f"{todo_path}: {item_id or index} missing {field}")
500
+
501
+ if str(item.get("status", "")).strip() not in {"open", "done", "blocked"}:
502
+ errors.append(f"{todo_path}: {item_id or index} has invalid status")
503
+
504
+ return errors
505
+
506
+
507
+ def check_all(project_root: Path) -> list[str]:
508
+ errors: list[str] = []
509
+ errors.extend(check_docs(project_root))
510
+ errors.extend(check_todo(project_root))
511
+ return errors