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,99 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import yaml
8
+
9
+
10
+ CATALOG_PATH = Path(".agentic/patterns/catalog.yaml")
11
+ VALID_KINDS = {"pattern", "anti-pattern"}
12
+
13
+
14
+ class PatternCatalogError(ValueError):
15
+ """Raised when the local pattern catalog is missing or invalid."""
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class PatternEntry:
20
+ id: str
21
+ title: str
22
+ kind: str
23
+ summary: str
24
+ doc: Path
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class PatternCatalog:
29
+ version: int
30
+ patterns: list[PatternEntry]
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class PatternDetail:
35
+ entry: PatternEntry
36
+ markdown: str
37
+
38
+
39
+ def load_pattern_catalog(root: Path) -> PatternCatalog:
40
+ catalog_path = root / CATALOG_PATH
41
+ if not catalog_path.exists():
42
+ raise PatternCatalogError(f"missing pattern catalog: {CATALOG_PATH}")
43
+
44
+ data = yaml.safe_load(catalog_path.read_text(encoding="utf-8"))
45
+ if not isinstance(data, dict):
46
+ raise PatternCatalogError("pattern catalog must be a mapping")
47
+
48
+ version = data.get("version")
49
+ if version != 1:
50
+ raise PatternCatalogError(f"unsupported pattern catalog version: {version}")
51
+
52
+ raw_patterns = data.get("patterns")
53
+ if not isinstance(raw_patterns, list):
54
+ raise PatternCatalogError("pattern catalog must contain a patterns list")
55
+
56
+ seen: set[str] = set()
57
+ entries: list[PatternEntry] = []
58
+ for raw in raw_patterns:
59
+ if not isinstance(raw, dict):
60
+ raise PatternCatalogError("pattern entry must be a mapping")
61
+ entry_id = _required_string(raw, "id")
62
+ if entry_id in seen:
63
+ raise PatternCatalogError(f"duplicate pattern id: {entry_id}")
64
+ seen.add(entry_id)
65
+ kind = _required_string(raw, "kind")
66
+ if kind not in VALID_KINDS:
67
+ raise PatternCatalogError(f"invalid pattern kind: {kind}")
68
+ entries.append(
69
+ PatternEntry(
70
+ id=entry_id,
71
+ title=_required_string(raw, "title"),
72
+ kind=kind,
73
+ summary=_required_string(raw, "summary"),
74
+ doc=Path(_required_string(raw, "doc")),
75
+ )
76
+ )
77
+
78
+ return PatternCatalog(version=version, patterns=entries)
79
+
80
+
81
+ def load_pattern_detail(root: Path, entry: PatternEntry) -> PatternDetail:
82
+ doc_path = root / entry.doc
83
+ if not doc_path.exists():
84
+ raise PatternCatalogError(f"missing pattern document: {entry.doc}")
85
+ return PatternDetail(entry=entry, markdown=doc_path.read_text(encoding="utf-8"))
86
+
87
+
88
+ def find_pattern(catalog: PatternCatalog, pattern_id: str) -> PatternEntry:
89
+ for entry in catalog.patterns:
90
+ if entry.id == pattern_id:
91
+ return entry
92
+ raise PatternCatalogError(f"unknown pattern id: {pattern_id}")
93
+
94
+
95
+ def _required_string(raw: dict[str, Any], key: str) -> str:
96
+ value = raw.get(key)
97
+ if not isinstance(value, str) or not value.strip():
98
+ raise PatternCatalogError(f"missing or invalid pattern field: {key}")
99
+ return value
@@ -0,0 +1,313 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import asdict, dataclass
4
+ from pathlib import Path
5
+
6
+
7
+ PLANNING_ROOTS = (
8
+ "docs/planning",
9
+ "docs/handoff",
10
+ )
11
+
12
+ ACTIVE_MARKERS = (
13
+ "active",
14
+ "current",
15
+ "next",
16
+ "pending",
17
+ "todo",
18
+ "roadmap",
19
+ "slice",
20
+ "gate",
21
+ "pre-gui",
22
+ "gui",
23
+ )
24
+
25
+ STALE_MARKERS = (
26
+ "obsolete",
27
+ "superseded",
28
+ "archived",
29
+ "deprecated",
30
+ "removed",
31
+ "legacy",
32
+ "done",
33
+ "completed",
34
+ "closed",
35
+ )
36
+
37
+ POST_V049_MARKERS = (
38
+ "v0.4.9",
39
+ "0.4.9",
40
+ "post-v0.4.9",
41
+ "release command authority",
42
+ "audit-doc-currency",
43
+ "audit-absolute-path",
44
+ "audit-ns",
45
+ "gui",
46
+ "0.5.0",
47
+ )
48
+
49
+ AUTHORITATIVE_PLANNING_DOCS = {
50
+ "docs/planning/PROJECT_DIRECTION.yaml",
51
+ }
52
+
53
+ PLANNING_VIEW_DOCS = {
54
+ "docs/planning/PROJECT_DIRECTION.md",
55
+ }
56
+
57
+ SCOPED_AUTHORITATIVE_PLANNING_DOCS = {
58
+ "docs/archive/PRE_GUI_HARDENING_TASKS.md",
59
+ }
60
+
61
+ HANDOFF_PROJECTION_DOCS = {
62
+ "docs/handoff/NEXT_CHAT_BOOTSTRAP.md",
63
+ "docs/handoff/START_NEW_CHAT_PROMPT.md",
64
+ }
65
+
66
+ HISTORICAL_PLANNING_DOCS = {
67
+ "docs/archive/WORKFLOW_REDUCTION_FOCUS.md",
68
+ "docs/planning/WORKFLOW_REDUCTION_FOCUS.md",
69
+ "docs/planning/GUI_DETERMINISTIC_GATEKEEPER_PLAN.md",
70
+ "docs/planning/V0.3.8_SCOPE.md",
71
+ }
72
+
73
+ KNOWN_ACTIVE_PLANNING_DOCS = set()
74
+
75
+
76
+ LEGACY_REVIEW_DOCS = {
77
+ "docs/handoff/CLOSEOUT_BEFORE_CHAT_SWITCH_PROMPT.md",
78
+ "docs/governance/WORK_ORDER_WORKFLOW_CONTRACT.md",
79
+ "docs/archive/FAILURE_MODE_REVIEW_AUTOMATION_PLAN.md",
80
+ "docs/archive/RULE_REFRESH_HANDSHAKE_PLAN.md",
81
+ }
82
+
83
+
84
+
85
+ DUPLICATION_TOPICS = {
86
+ "release_command_authority": (
87
+ "release command authority",
88
+ "release metadata",
89
+ "release-prep",
90
+ "release publish",
91
+ "doi closeout",
92
+ ),
93
+ "ns_migration": (
94
+ "./ns",
95
+ "ns-menu",
96
+ "ns migration",
97
+ "legacy ns",
98
+ ),
99
+ "handoff": (
100
+ "handoff",
101
+ "successor",
102
+ "next chat",
103
+ "bootstrap",
104
+ ),
105
+ "gui": (
106
+ "gui",
107
+ "cockpit",
108
+ "tkinter",
109
+ ),
110
+ "docs_currency": (
111
+ "doc currency",
112
+ "documentation currency",
113
+ "current state",
114
+ "status doc",
115
+ ),
116
+ }
117
+
118
+
119
+ @dataclass(frozen=True)
120
+ class PlanningDocRecord:
121
+ path: str
122
+ title: str | None
123
+ line_count: int
124
+ active_score: int
125
+ stale_score: int
126
+ post_v049_score: int
127
+ topics: tuple[str, ...]
128
+ classification: str
129
+ reason: str
130
+
131
+ def as_dict(self) -> dict[str, object]:
132
+ return asdict(self)
133
+
134
+
135
+ @dataclass(frozen=True)
136
+ class PlanningDocsConsolidationAuditResult:
137
+ root: str
138
+ records: tuple[PlanningDocRecord, ...]
139
+ blockers: tuple[PlanningDocRecord, ...]
140
+
141
+ @property
142
+ def ok(self) -> bool:
143
+ return not self.blockers
144
+
145
+ @property
146
+ def status(self) -> str:
147
+ return "PASS" if self.ok else "FAIL"
148
+
149
+ @property
150
+ def returncode(self) -> int:
151
+ return 0 if self.ok else 1
152
+
153
+ def as_dict(self) -> dict[str, object]:
154
+ return {
155
+ "schema_version": 1,
156
+ "kind": "planning_docs_consolidation_audit",
157
+ "root": self.root,
158
+ "status": self.status,
159
+ "record_count": len(self.records),
160
+ "blocker_count": len(self.blockers),
161
+ "records": [item.as_dict() for item in self.records],
162
+ "blockers": [item.as_dict() for item in self.blockers],
163
+ }
164
+
165
+
166
+ def _title(lines: list[str]) -> str | None:
167
+ for line in lines[:40]:
168
+ stripped = line.strip()
169
+ if stripped.startswith("#"):
170
+ return stripped.lstrip("#").strip() or None
171
+ return None
172
+
173
+
174
+ def _score(text: str, markers: tuple[str, ...]) -> int:
175
+ lowered = text.lower()
176
+ return sum(lowered.count(marker.lower()) for marker in markers)
177
+
178
+
179
+ def _topics(text: str) -> tuple[str, ...]:
180
+ lowered = text.lower()
181
+ found: list[str] = []
182
+ for topic, markers in DUPLICATION_TOPICS.items():
183
+ if any(marker.lower() in lowered for marker in markers):
184
+ found.append(topic)
185
+ return tuple(found)
186
+
187
+
188
+ def _classify(relative: str, text: str, active_score: int, stale_score: int, post_v049_score: int, topics: tuple[str, ...]) -> tuple[str, str]:
189
+ lowered = text.lower()
190
+
191
+ if relative.endswith("CURRENT_HANDOFF.md"):
192
+ return "authoritative_current_handoff", "current handoff is an authoritative state carrier"
193
+
194
+ if relative in AUTHORITATIVE_PLANNING_DOCS:
195
+ return "authoritative_planning_anchor", "explicit current planning authority"
196
+
197
+ if relative in PLANNING_VIEW_DOCS:
198
+ return "planning_authority_view", "human-readable view of the current planning authority"
199
+
200
+ if relative in SCOPED_AUTHORITATIVE_PLANNING_DOCS:
201
+ return "authoritative_scoped_planning_anchor", "explicit scoped planning authority"
202
+
203
+ if relative in HANDOFF_PROJECTION_DOCS:
204
+ return "handoff_projection", "generated handoff prompt projection, not planning authority"
205
+
206
+ if relative in HISTORICAL_PLANNING_DOCS:
207
+ return "historical_planning_doc", "known historical planning/handoff artifact"
208
+
209
+ if relative in LEGACY_REVIEW_DOCS:
210
+ return "legacy_review_candidate", "known follow-up review candidate, not current authority"
211
+
212
+ if relative in KNOWN_ACTIVE_PLANNING_DOCS:
213
+ return "active_planning_candidate", "known active planning document, not current overall authority"
214
+
215
+ if "current source of truth" in lowered or "source of truth" in lowered:
216
+ return "possible_authoritative_plan", "document claims source-of-truth status"
217
+
218
+ if stale_score > active_score and post_v049_score == 0:
219
+ return "stale_or_archival_candidate", "stale markers dominate and no current release marker was found"
220
+
221
+ if post_v049_score > 0 and active_score > 0:
222
+ return "active_planning_candidate", "current release and active planning markers found"
223
+
224
+ if len(topics) >= 3 and active_score > 0:
225
+ return "consolidation_candidate", "multiple active planning topics in one document"
226
+
227
+ if active_score > 0:
228
+ return "active_but_needs_review", "active markers found but no current release anchor"
229
+
230
+ return "low_priority_reference", "no strong active or stale signal"
231
+
232
+
233
+ def _iter_docs(root: Path) -> list[Path]:
234
+ docs: list[Path] = []
235
+ for planning_root in PLANNING_ROOTS:
236
+ base = root / planning_root
237
+ if not base.exists():
238
+ continue
239
+ docs.extend(path for path in base.rglob("*") if path.is_file() and path.suffix in {".md", ".yaml", ".yml"})
240
+ return sorted(docs)
241
+
242
+
243
+ def audit_planning_docs_consolidation(root: Path = Path(".")) -> PlanningDocsConsolidationAuditResult:
244
+ root = root.resolve()
245
+ records: list[PlanningDocRecord] = []
246
+
247
+ for path in _iter_docs(root):
248
+ relative = path.relative_to(root).as_posix()
249
+ text = path.read_text(encoding="utf-8", errors="replace")
250
+ lines = text.splitlines()
251
+ active_score = _score(text, ACTIVE_MARKERS)
252
+ stale_score = _score(text, STALE_MARKERS)
253
+ post_v049_score = _score(text, POST_V049_MARKERS)
254
+ topics = _topics(text)
255
+ classification, reason = _classify(
256
+ relative,
257
+ text,
258
+ active_score,
259
+ stale_score,
260
+ post_v049_score,
261
+ topics,
262
+ )
263
+ records.append(
264
+ PlanningDocRecord(
265
+ path=relative,
266
+ title=_title(lines),
267
+ line_count=len(lines),
268
+ active_score=active_score,
269
+ stale_score=stale_score,
270
+ post_v049_score=post_v049_score,
271
+ topics=topics,
272
+ classification=classification,
273
+ reason=reason,
274
+ )
275
+ )
276
+
277
+ blockers = tuple(
278
+ record
279
+ for record in records
280
+ if record.classification in {
281
+ "possible_authoritative_plan",
282
+ "consolidation_candidate",
283
+ "active_but_needs_review",
284
+ }
285
+ )
286
+
287
+ return PlanningDocsConsolidationAuditResult(
288
+ root=root.as_posix(),
289
+ records=tuple(records),
290
+ blockers=blockers,
291
+ )
292
+
293
+
294
+ def render_planning_docs_consolidation_audit(result: PlanningDocsConsolidationAuditResult) -> str:
295
+ lines = [
296
+ "PLANNING_DOCS_CONSOLIDATION_AUDIT",
297
+ f"STATUS={result.status}",
298
+ f"RECORD_COUNT={len(result.records)}",
299
+ f"BLOCKER_COUNT={len(result.blockers)}",
300
+ ]
301
+ for item in result.blockers:
302
+ lines.append(
303
+ "BLOCKER="
304
+ f"{item.classification}|{item.path}|topics={','.join(item.topics)}|"
305
+ f"active={item.active_score}|stale={item.stale_score}|post_v049={item.post_v049_score}|{item.title}"
306
+ )
307
+ for item in result.records:
308
+ lines.append(
309
+ "RECORD="
310
+ f"{item.classification}|{item.path}|topics={','.join(item.topics)}|"
311
+ f"active={item.active_score}|stale={item.stale_score}|post_v049={item.post_v049_score}|{item.title}"
312
+ )
313
+ return "\n".join(lines) + "\n"
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+
6
+ DEFAULT_EXCLUDED_PARTS = {".git", ".venv", "dist", "build", "__pycache__"}
7
+ DEFAULT_ALLOWED_PREFIXES = ("tmp/",)
8
+
9
+ @dataclass(frozen=True)
10
+ class PortabilityShellGateResult:
11
+ ok: bool
12
+ shell_files: tuple[str, ...]
13
+ blocking_shell_files: tuple[str, ...]
14
+
15
+ def _is_excluded(path: Path) -> bool:
16
+ return any(part in DEFAULT_EXCLUDED_PARTS for part in path.parts)
17
+
18
+ def _is_allowed(path_text: str, allowed_prefixes: tuple[str, ...]) -> bool:
19
+ normalized = path_text.removeprefix("./")
20
+ return any(normalized.startswith(prefix) for prefix in allowed_prefixes)
21
+
22
+ def build_portability_shell_gate_report(project_root: Path, allowed_prefixes: tuple[str, ...] = DEFAULT_ALLOWED_PREFIXES) -> PortabilityShellGateResult:
23
+ shell_files: list[str] = []
24
+ for path in sorted(project_root.rglob("*.sh")):
25
+ rel = path.relative_to(project_root)
26
+ if _is_excluded(rel):
27
+ continue
28
+ shell_files.append(rel.as_posix())
29
+ blocking = tuple(item for item in shell_files if not _is_allowed(item, allowed_prefixes))
30
+ return PortabilityShellGateResult(ok=not blocking, shell_files=tuple(shell_files), blocking_shell_files=blocking)
31
+
32
+ def render_portability_shell_gate_report(result: PortabilityShellGateResult) -> str:
33
+ lines = ["Portability shell gate report"]
34
+ lines.append(f"shell_file_count={len(result.shell_files)}")
35
+ lines.append(f"blocking_shell_file_count={len(result.blocking_shell_files)}")
36
+ if result.blocking_shell_files:
37
+ lines.append("Blocking shell files:")
38
+ lines.extend(f"- {item}" for item in result.blocking_shell_files)
39
+ lines.append("### RESULT: PASS ###" if result.ok else "### RESULT: FAIL ###")
40
+ return "\n".join(lines)
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+ import subprocess
5
+ from typing import Any
6
+ from agentic_project_kit.handoff_freshness import assess_handoff_prompt_freshness
7
+ from agentic_project_kit.handoff_prompt import render_handoff_prompt
8
+ from agentic_project_kit.handoff_state import load_handoff_state
9
+
10
+ @dataclass(frozen=True)
11
+ class PostMergeHandoffRefreshStatus:
12
+ current_head: str
13
+ freshness_warning_present: bool
14
+ refresh_required: bool
15
+ latest_successor_prompt: str | None
16
+ result: str
17
+ next_safe_action: str
18
+
19
+ def evaluate_post_merge_handoff_refresh(project_root: Path = Path('.'), *, state_path: str = '.agentic/handoff_state.yaml') -> PostMergeHandoffRefreshStatus:
20
+ state_file = project_root / state_path
21
+ data = load_handoff_state(str(state_file))
22
+ rendered_prompt = render_handoff_prompt(data)
23
+ current_head = _git_short_head(project_root)
24
+ current_subject = _git_commit_subject(project_root)
25
+ warnings = assess_handoff_prompt_freshness(
26
+ data,
27
+ str(state_file),
28
+ current_head=current_head,
29
+ current_subject=current_subject,
30
+ successor_prompt_text=rendered_prompt,
31
+ )
32
+ warning = bool(warnings)
33
+ return PostMergeHandoffRefreshStatus(current_head, warning, warning, _latest_successor_prompt(data), 'REFRESH_REQUIRED' if warning else 'NOOP', 'create_administrative_handoff_refresh' if warning else 'continue_without_post_merge_handoff_refresh')
34
+
35
+ def render_post_merge_handoff_refresh_status(status: PostMergeHandoffRefreshStatus) -> str:
36
+ return '\n'.join(['POST_MERGE_HANDOFF_REFRESH', f'current_head={status.current_head}', f'freshness_warning_present={str(status.freshness_warning_present)}', f'refresh_required={str(status.refresh_required)}', f'latest_successor_prompt={status.latest_successor_prompt or ""}', f'result={status.result}', f'next_safe_action={status.next_safe_action}']) + '\n'
37
+
38
+ def _git_short_head(project_root: Path) -> str:
39
+ try:
40
+ return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'], cwd=project_root, stderr=subprocess.DEVNULL, text=True).strip()
41
+ except (OSError, subprocess.CalledProcessError):
42
+ return ''
43
+
44
+ def _git_commit_subject(project_root: Path) -> str:
45
+ try:
46
+ return subprocess.check_output(['git', 'log', '-1', '--pretty=%s'], cwd=project_root, stderr=subprocess.DEVNULL, text=True).strip()
47
+ except (OSError, subprocess.CalledProcessError):
48
+ return ''
49
+
50
+
51
+ def _latest_successor_prompt(data: dict[str, Any]) -> str | None:
52
+ for key in ('handoff_maintenance', 'administrative_evidence_state'):
53
+ section = data.get(key, {})
54
+ if isinstance(section, dict) and section.get('latest_successor_prompt'):
55
+ return str(section['latest_successor_prompt'])
56
+ return None