wicked-garden 12.27.0

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 (640) hide show
  1. package/.claude-plugin/archetypes.json +166 -0
  2. package/.claude-plugin/components.json +75 -0
  3. package/.claude-plugin/marketplace.json +15 -0
  4. package/.claude-plugin/plugin.json +174 -0
  5. package/.claude-plugin/specialist.json +48 -0
  6. package/.claude-plugin/stack-registry.json +96 -0
  7. package/AGENTS.md +13 -0
  8. package/CHANGELOG.md +712 -0
  9. package/CONTRIBUTING.md +99 -0
  10. package/ETHOS.md +83 -0
  11. package/LICENSE +21 -0
  12. package/README.md +115 -0
  13. package/WICKED_GARDEN_BUS_EVENTS.md +149 -0
  14. package/agents/agentic/architect.md +449 -0
  15. package/agents/agentic/performance-analyst.md +702 -0
  16. package/agents/agentic/safety-reviewer.md +662 -0
  17. package/agents/crew/implementer.md +163 -0
  18. package/agents/crew/researcher.md +94 -0
  19. package/agents/crew/reviewer.md +184 -0
  20. package/agents/data/data-engineer.md +227 -0
  21. package/agents/engineering/api-documentarian.md +440 -0
  22. package/agents/engineering/migration-engineer.md +301 -0
  23. package/agents/engineering/solution-architect.md +208 -0
  24. package/agents/jam/brainstorm-facilitator.md +297 -0
  25. package/agents/jam/council.md +322 -0
  26. package/agents/jam/quick-facilitator.md +85 -0
  27. package/agents/persona/persona-agent.md +121 -0
  28. package/agents/platform/compliance-officer.md +266 -0
  29. package/agents/platform/privacy-expert.md +496 -0
  30. package/agents/platform/security-engineer.md +287 -0
  31. package/agents/product/a11y-expert.md +323 -0
  32. package/agents/product/requirements-analyst.md +238 -0
  33. package/agents/product/ui-reviewer.md +180 -0
  34. package/agents/product/ux-designer.md +199 -0
  35. package/agents/product/value-strategist.md +269 -0
  36. package/agents/qe/semantic-reviewer.md +78 -0
  37. package/commands/agentic/audit.md +24 -0
  38. package/commands/agentic/design.md +24 -0
  39. package/commands/agentic/frameworks.md +25 -0
  40. package/commands/agentic/review.md +27 -0
  41. package/commands/archetype/build.md +12 -0
  42. package/commands/archetype/decide.md +12 -0
  43. package/commands/archetype/explore.md +12 -0
  44. package/commands/archetype/incident.md +12 -0
  45. package/commands/archetype/migrate.md +12 -0
  46. package/commands/archetype/modernize.md +12 -0
  47. package/commands/archetype/review.md +14 -0
  48. package/commands/archetype/ship.md +12 -0
  49. package/commands/archetype/specify.md +12 -0
  50. package/commands/archetype/triage.md +12 -0
  51. package/commands/compile.md +37 -0
  52. package/commands/data/analyze.md +18 -0
  53. package/commands/data/data.md +21 -0
  54. package/commands/data/ml.md +18 -0
  55. package/commands/data/ontology.md +22 -0
  56. package/commands/data/pipeline.md +17 -0
  57. package/commands/deliberate.md +181 -0
  58. package/commands/engineering/add-field.md +77 -0
  59. package/commands/engineering/apply.md +58 -0
  60. package/commands/engineering/arch.md +26 -0
  61. package/commands/engineering/debug.md +27 -0
  62. package/commands/engineering/docs.md +27 -0
  63. package/commands/engineering/new-generator.md +257 -0
  64. package/commands/engineering/patch-plan.md +59 -0
  65. package/commands/engineering/plan.md +29 -0
  66. package/commands/engineering/remove.md +48 -0
  67. package/commands/engineering/rename.md +44 -0
  68. package/commands/engineering/review.md +32 -0
  69. package/commands/help.md +128 -0
  70. package/commands/install.md +118 -0
  71. package/commands/intent.md +54 -0
  72. package/commands/jam/brainstorm.md +37 -0
  73. package/commands/jam/council.md +49 -0
  74. package/commands/jam/quick.md +21 -0
  75. package/commands/jam/revisit.md +79 -0
  76. package/commands/persona/as.md +136 -0
  77. package/commands/persona/define.md +24 -0
  78. package/commands/persona/list.md +118 -0
  79. package/commands/platform/actions.md +20 -0
  80. package/commands/platform/assert.md +18 -0
  81. package/commands/platform/audit.md +23 -0
  82. package/commands/platform/compliance.md +20 -0
  83. package/commands/platform/gh.md +22 -0
  84. package/commands/platform/health.md +22 -0
  85. package/commands/platform/incident.md +23 -0
  86. package/commands/platform/infra.md +24 -0
  87. package/commands/platform/peer-health.md +103 -0
  88. package/commands/platform/plugin-health.md +58 -0
  89. package/commands/platform/security.md +38 -0
  90. package/commands/platform/toolchain.md +96 -0
  91. package/commands/platform/traces.md +20 -0
  92. package/commands/product/a11y.md +19 -0
  93. package/commands/product/acceptance.md +19 -0
  94. package/commands/product/align.md +20 -0
  95. package/commands/product/analyze.md +23 -0
  96. package/commands/product/elicit.md +19 -0
  97. package/commands/product/listen.md +24 -0
  98. package/commands/product/mockup.md +20 -0
  99. package/commands/product/screenshot.md +19 -0
  100. package/commands/product/strategy.md +40 -0
  101. package/commands/product/synthesize.md +23 -0
  102. package/commands/product/ux-review.md +42 -0
  103. package/commands/product/ux.md +20 -0
  104. package/commands/product/visual-direction.md +34 -0
  105. package/commands/prove.md +47 -0
  106. package/commands/report-issue.md +243 -0
  107. package/commands/reset.md +144 -0
  108. package/commands/search/blast-radius.md +58 -0
  109. package/commands/search/hotspots.md +55 -0
  110. package/commands/search/index.md +38 -0
  111. package/commands/search/lineage.md +47 -0
  112. package/commands/search/service-map.md +79 -0
  113. package/commands/setup.md +363 -0
  114. package/commands/smaht/briefing.md +200 -0
  115. package/commands/smaht/events-import.md +95 -0
  116. package/commands/smaht/state.md +19 -0
  117. package/commands/where-am-i.md +58 -0
  118. package/docs/README.md +32 -0
  119. package/docs/adr/0001-code-relationship-graph-engine.md +116 -0
  120. package/docs/adr/0002-major-cleanup-keep-cut.md +91 -0
  121. package/docs/adr/0003-peers-gate-required-layers-optin.md +41 -0
  122. package/docs/adr/0004-code-graph-moves-to-wicked-brain.md +47 -0
  123. package/docs/brain-chunk-format.md +135 -0
  124. package/docs/compiler.md +95 -0
  125. package/docs/daemon-api.md +70 -0
  126. package/docs/domains.md +197 -0
  127. package/docs/getting-started.md +208 -0
  128. package/docs/plans/2026-06-08-wicked-loom-conduct.md +1337 -0
  129. package/docs/plans/2026-06-08-wicked-loom-contract.md +638 -0
  130. package/docs/plans/2026-06-08-wicked-loom-cutover.md +1300 -0
  131. package/docs/plans/2026-06-08-wicked-loom-expand-compose.md +843 -0
  132. package/docs/plans/2026-06-08-wicked-loom-flow-cutover.md +403 -0
  133. package/docs/plans/2026-06-10-codegraph-to-brain-phase1a-graph-core.md +838 -0
  134. package/docs/plans/2026-06-10-codegraph-to-brain-phase1b-injected-edges.md +44 -0
  135. package/docs/required-peers.md +60 -0
  136. package/docs/specs/2026-06-08-wicked-loom-extraction.md +225 -0
  137. package/docs/specs/2026-06-10-codegraph-to-brain-migration.md +168 -0
  138. package/docs/v11/archetypes.md +231 -0
  139. package/hooks/hooks.json +275 -0
  140. package/hooks/scripts/bootstrap.py +1797 -0
  141. package/hooks/scripts/invoke.py +62 -0
  142. package/hooks/scripts/notification.py +166 -0
  143. package/hooks/scripts/permission_request.py +223 -0
  144. package/hooks/scripts/post_tool.py +1374 -0
  145. package/hooks/scripts/pre_compact.py +272 -0
  146. package/hooks/scripts/pre_tool.py +1278 -0
  147. package/hooks/scripts/prompt_submit.py +1109 -0
  148. package/hooks/scripts/session_end.py +95 -0
  149. package/hooks/scripts/stop.py +624 -0
  150. package/hooks/scripts/subagent_lifecycle.py +464 -0
  151. package/hooks/scripts/subscribers/on_gate_decided.py +84 -0
  152. package/hooks/scripts/task_completed.py +322 -0
  153. package/hooks/scripts/worktree_guard.py +148 -0
  154. package/hooks/subscriptions/on_gate_decided.json +9 -0
  155. package/install.mjs +128 -0
  156. package/package.json +41 -0
  157. package/pyproject.toml +59 -0
  158. package/schemas/README.md +32 -0
  159. package/schemas/assert_contracts.json +37 -0
  160. package/schemas/health_probe.json +46 -0
  161. package/schemas/plugin_status.json +37 -0
  162. package/scripts/_adapters/__init__.py +166 -0
  163. package/scripts/_adapters/jira_adapter.py +18 -0
  164. package/scripts/_adapters/linear_adapter.py +18 -0
  165. package/scripts/_adapters/miro_adapter.py +18 -0
  166. package/scripts/_adapters/notion_adapter.py +18 -0
  167. package/scripts/_agents.py +357 -0
  168. package/scripts/_brain_ingest/__init__.py +2 -0
  169. package/scripts/_brain_ingest/session_fact_extractor.py +341 -0
  170. package/scripts/_brain_port.py +163 -0
  171. package/scripts/_bus.py +1081 -0
  172. package/scripts/_bus_catalog_gen.py +93 -0
  173. package/scripts/_bus_consumers.json +46 -0
  174. package/scripts/_bus_emit.py +43 -0
  175. package/scripts/_capability_registry.py +334 -0
  176. package/scripts/_capability_resolver.py +322 -0
  177. package/scripts/_domain_store.py +559 -0
  178. package/scripts/_event_log_reader.py +154 -0
  179. package/scripts/_event_schema.py +305 -0
  180. package/scripts/_event_store.py +397 -0
  181. package/scripts/_heavy_cadence.py +324 -0
  182. package/scripts/_integration_resolver.py +321 -0
  183. package/scripts/_logger.py +151 -0
  184. package/scripts/_loom.py +482 -0
  185. package/scripts/_paths.py +133 -0
  186. package/scripts/_python.sh +22 -0
  187. package/scripts/_run.py +137 -0
  188. package/scripts/_session.py +502 -0
  189. package/scripts/_validate_registry.py +1042 -0
  190. package/scripts/_wicked_testing_probe.py +277 -0
  191. package/scripts/agentic/analyze_agents.py +391 -0
  192. package/scripts/agentic/detect_framework.py +420 -0
  193. package/scripts/agentic/issue_taxonomy.py +357 -0
  194. package/scripts/agentic/pattern_scorer.py +434 -0
  195. package/scripts/classify/persist.py +169 -0
  196. package/scripts/compiler/compile.py +552 -0
  197. package/scripts/compiler/phase0/VERDICT.md +60 -0
  198. package/scripts/compiler/phase0/detect.py +292 -0
  199. package/scripts/compiler/phase0/emit.py +228 -0
  200. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.bindings.json +30 -0
  201. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.lint-output.txt +17 -0
  202. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/.wg/evidence/failing-tests.txt +4 -0
  203. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/.wg/evidence/honest-tests.txt +2 -0
  204. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/fab-fail.build.md +8 -0
  205. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/fab-missing.build.md +8 -0
  206. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/honest.build.md +8 -0
  207. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/wg_check_claims.py +174 -0
  208. package/scripts/compiler/phase0/evidence/command_iq.bindings.json +45 -0
  209. package/scripts/compiler/phase0/evidence/command_iq.lint-output.txt +17 -0
  210. package/scripts/compiler/phase0/evidence/command_iq.stage/.projects/evidence-domain/phase-1-clarify/evidence/failing-tests.txt +4 -0
  211. package/scripts/compiler/phase0/evidence/command_iq.stage/.projects/evidence-domain/phase-1-clarify/evidence/honest-tests.txt +3 -0
  212. package/scripts/compiler/phase0/evidence/command_iq.stage/fab-fail.build.md +8 -0
  213. package/scripts/compiler/phase0/evidence/command_iq.stage/fab-missing.build.md +8 -0
  214. package/scripts/compiler/phase0/evidence/command_iq.stage/honest.build.md +8 -0
  215. package/scripts/compiler/phase0/evidence/command_iq.stage/wg_check_claims.py +174 -0
  216. package/scripts/compiler/phase0/evidence/memos.bindings.json +28 -0
  217. package/scripts/compiler/phase0/evidence/memos.lint-output.txt +17 -0
  218. package/scripts/compiler/phase0/evidence/memos.stage/.wg/evidence/failing-tests.txt +4 -0
  219. package/scripts/compiler/phase0/evidence/memos.stage/.wg/evidence/honest-tests.txt +2 -0
  220. package/scripts/compiler/phase0/evidence/memos.stage/fab-fail.build.md +8 -0
  221. package/scripts/compiler/phase0/evidence/memos.stage/fab-missing.build.md +8 -0
  222. package/scripts/compiler/phase0/evidence/memos.stage/honest.build.md +8 -0
  223. package/scripts/compiler/phase0/evidence/memos.stage/wg_check_claims.py +174 -0
  224. package/scripts/compiler/phase0/evidence/verdict.json +104 -0
  225. package/scripts/compiler/phase0/evidence/wicked-bus.bindings.json +29 -0
  226. package/scripts/compiler/phase0/evidence/wicked-bus.lint-output.txt +17 -0
  227. package/scripts/compiler/phase0/evidence/wicked-bus.stage/coverage/failing-tests.txt +4 -0
  228. package/scripts/compiler/phase0/evidence/wicked-bus.stage/coverage/honest-tests.txt +3 -0
  229. package/scripts/compiler/phase0/evidence/wicked-bus.stage/fab-fail.build.md +8 -0
  230. package/scripts/compiler/phase0/evidence/wicked-bus.stage/fab-missing.build.md +8 -0
  231. package/scripts/compiler/phase0/evidence/wicked-bus.stage/honest.build.md +8 -0
  232. package/scripts/compiler/phase0/evidence/wicked-bus.stage/wg_check_claims.py +174 -0
  233. package/scripts/compiler/phase0/evidence/wire-vault-transcript.txt +60 -0
  234. package/scripts/compiler/phase0/run_probe.py +234 -0
  235. package/scripts/compiler/phase0/wire_vault.py +310 -0
  236. package/scripts/crew/__init__.py +0 -0
  237. package/scripts/crew/_task_audit_writer.py +243 -0
  238. package/scripts/crew/_task_reader.py +389 -0
  239. package/scripts/crew/archetypes_v11.py +348 -0
  240. package/scripts/crew/detectors/__init__.py +12 -0
  241. package/scripts/crew/flow_compiler.py +121 -0
  242. package/scripts/crew/phase_manager.py +703 -0
  243. package/scripts/crew/scope_delta.py +264 -0
  244. package/scripts/crew/specialist_resolver.py +218 -0
  245. package/scripts/crew/stack_registry.py +189 -0
  246. package/scripts/data/data_profiler.py +271 -0
  247. package/scripts/data/ontology_recommender.py +231 -0
  248. package/scripts/data/schema_validator.py +267 -0
  249. package/scripts/delivery/drift.py +739 -0
  250. package/scripts/delivery/telemetry.py +614 -0
  251. package/scripts/engineering/patch/codegraph_db.py +125 -0
  252. package/scripts/engineering/patch/generators/__init__.py +56 -0
  253. package/scripts/engineering/patch/generators/base.py +373 -0
  254. package/scripts/engineering/patch/generators/csharp_generator.py +494 -0
  255. package/scripts/engineering/patch/generators/go_generator.py +430 -0
  256. package/scripts/engineering/patch/generators/java_generator.py +928 -0
  257. package/scripts/engineering/patch/generators/jsp_generator.py +454 -0
  258. package/scripts/engineering/patch/generators/kotlin_generator.py +561 -0
  259. package/scripts/engineering/patch/generators/perl_generator.py +510 -0
  260. package/scripts/engineering/patch/generators/php_generator.py +644 -0
  261. package/scripts/engineering/patch/generators/propagation_engine.py +929 -0
  262. package/scripts/engineering/patch/generators/python_generator.py +487 -0
  263. package/scripts/engineering/patch/generators/ruby_generator.py +463 -0
  264. package/scripts/engineering/patch/generators/rust_generator.py +400 -0
  265. package/scripts/engineering/patch/generators/sql_generator.py +594 -0
  266. package/scripts/engineering/patch/generators/typescript_generator.py +439 -0
  267. package/scripts/engineering/patch/patch.py +800 -0
  268. package/scripts/engineering/patch/safety.py +429 -0
  269. package/scripts/jam/__init__.py +0 -0
  270. package/scripts/jam/_bus_consumers.py +271 -0
  271. package/scripts/jam/agentic_cli_registry.py +703 -0
  272. package/scripts/jam/consensus.py +737 -0
  273. package/scripts/jam/detect_clis.py +409 -0
  274. package/scripts/jam/jam.py +283 -0
  275. package/scripts/jam/save_transcript.py +171 -0
  276. package/scripts/loom/__init__.py +22 -0
  277. package/scripts/loom/cli.py +170 -0
  278. package/scripts/loom/compose.py +180 -0
  279. package/scripts/loom/gate.py +120 -0
  280. package/scripts/loom/manifest.py +140 -0
  281. package/scripts/loom/resolve.py +59 -0
  282. package/scripts/persona/__init__.py +0 -0
  283. package/scripts/persona/registry.py +688 -0
  284. package/scripts/platform/guard_pipeline.py +1214 -0
  285. package/scripts/platform/guard_profiles.py +216 -0
  286. package/scripts/platform/observability/assert_contracts.py +434 -0
  287. package/scripts/platform/observability/health_probe.py +915 -0
  288. package/scripts/platform/observability/ops_log_viewer.py +209 -0
  289. package/scripts/platform/prereq_doctor.py +567 -0
  290. package/scripts/qe/conditions_manifest.py +260 -0
  291. package/scripts/qe/content_sanitizer.py +160 -0
  292. package/scripts/qe/coverage_tracker.py +535 -0
  293. package/scripts/qe/discover_scenarios.py +243 -0
  294. package/scripts/qe/evidence_tracker.py +232 -0
  295. package/scripts/qe/prove.py +196 -0
  296. package/scripts/qe/registry_lookup.py +80 -0
  297. package/scripts/qe/registry_store.py +176 -0
  298. package/scripts/qe/semantic_review.py +1005 -0
  299. package/scripts/qe/vault_gate.py +382 -0
  300. package/scripts/qe/verdict_audit.py +175 -0
  301. package/scripts/qe/verdict_schema.py +335 -0
  302. package/scripts/reset.py +220 -0
  303. package/scripts/resolve_path.py +22 -0
  304. package/scripts/sentinel/invariants.py +402 -0
  305. package/scripts/sentinel/pre_push.py +73 -0
  306. package/scripts/setup/__init__.py +1 -0
  307. package/scripts/setup/detect_state.py +114 -0
  308. package/scripts/setup/migrate_legacy.py +67 -0
  309. package/scripts/setup/migrate_v6_projects.py +232 -0
  310. package/scripts/setup/onboarding.py +114 -0
  311. package/scripts/smaht/_bus_consumers.py +98 -0
  312. package/scripts/smaht/adapters/__init__.py +83 -0
  313. package/scripts/smaht/adapters/brain_adapter.py +254 -0
  314. package/scripts/smaht/adapters/context7_adapter.py +438 -0
  315. package/scripts/smaht/adapters/delegation_adapter.py +104 -0
  316. package/scripts/smaht/adapters/domain_adapter.py +206 -0
  317. package/scripts/smaht/adapters/events_adapter.py +231 -0
  318. package/scripts/smaht/adapters/tools_adapter.py +49 -0
  319. package/scripts/smaht/cheatsheet_store.py +236 -0
  320. package/scripts/smaht/context_package.py +386 -0
  321. package/scripts/smaht/knowledge_graph.py +395 -0
  322. package/scripts/smaht/propose_skills.py +856 -0
  323. package/scripts/smaht/test_cheatsheet_store.py +428 -0
  324. package/scripts/statusline.py +151 -0
  325. package/scripts/where_am_i.py +236 -0
  326. package/skills/agentic/agentic-patterns/SKILL.md +91 -0
  327. package/skills/agentic/agentic-patterns/refs/anti-patterns-design.md +283 -0
  328. package/skills/agentic/agentic-patterns/refs/anti-patterns-operational.md +216 -0
  329. package/skills/agentic/agentic-patterns/refs/pattern-catalog.md +329 -0
  330. package/skills/agentic/context-engineering/SKILL.md +142 -0
  331. package/skills/agentic/context-engineering/refs/caching-and-optimization.md +251 -0
  332. package/skills/agentic/context-engineering/refs/compression-techniques.md +171 -0
  333. package/skills/agentic/context-engineering/refs/cost-calculation-budget.md +250 -0
  334. package/skills/agentic/context-engineering/refs/cost-optimization-reporting.md +300 -0
  335. package/skills/agentic/context-engineering/refs/selective-loading.md +179 -0
  336. package/skills/agentic/frameworks/SKILL.md +191 -0
  337. package/skills/agentic/frameworks/refs/framework-profiles-1.md +209 -0
  338. package/skills/agentic/frameworks/refs/framework-profiles-2.md +214 -0
  339. package/skills/agentic/frameworks/refs/migration-patterns-paths.md +267 -0
  340. package/skills/agentic/frameworks/refs/migration-patterns-testing.md +208 -0
  341. package/skills/agentic/refs/audit.md +134 -0
  342. package/skills/agentic/refs/design.md +137 -0
  343. package/skills/agentic/refs/frameworks.md +102 -0
  344. package/skills/agentic/refs/review.md +160 -0
  345. package/skills/agentic/review-methodology/SKILL.md +186 -0
  346. package/skills/agentic/review-methodology/refs/deliverable-templates.md +246 -0
  347. package/skills/agentic/review-methodology/refs/issue-taxonomy-quality-testing.md +277 -0
  348. package/skills/agentic/review-methodology/refs/issue-taxonomy-reliability-safety.md +253 -0
  349. package/skills/agentic/trust-and-safety/SKILL.md +177 -0
  350. package/skills/agentic/trust-and-safety/refs/guardrails-actions.md +246 -0
  351. package/skills/agentic/trust-and-safety/refs/guardrails-input-output.md +238 -0
  352. package/skills/agentic/trust-and-safety/refs/guardrails-resources.md +209 -0
  353. package/skills/agentic/trust-and-safety/refs/safety-checklist-advanced.md +209 -0
  354. package/skills/agentic/trust-and-safety/refs/safety-checklist-core.md +215 -0
  355. package/skills/archetype/SKILL.md +104 -0
  356. package/skills/archetype/refs/build.md +142 -0
  357. package/skills/archetype/refs/decide.md +117 -0
  358. package/skills/archetype/refs/explore.md +66 -0
  359. package/skills/archetype/refs/incident.md +149 -0
  360. package/skills/archetype/refs/migrate.md +170 -0
  361. package/skills/archetype/refs/modernize.md +187 -0
  362. package/skills/archetype/refs/review.md +145 -0
  363. package/skills/archetype/refs/ship.md +117 -0
  364. package/skills/archetype/refs/specify.md +108 -0
  365. package/skills/archetype/refs/triage.md +62 -0
  366. package/skills/classify/SKILL.md +157 -0
  367. package/skills/data/analysis/SKILL.md +125 -0
  368. package/skills/data/analysis/refs/templates.md +170 -0
  369. package/skills/data/data/SKILL.md +118 -0
  370. package/skills/data/data/refs/examples.md +235 -0
  371. package/skills/data/ml/SKILL.md +125 -0
  372. package/skills/data/ml/refs/templates.md +172 -0
  373. package/skills/data/pipeline/SKILL.md +136 -0
  374. package/skills/data/pipeline/refs/templates.md +175 -0
  375. package/skills/data/refs/analyze.md +123 -0
  376. package/skills/data/refs/data.md +95 -0
  377. package/skills/data/refs/ml.md +118 -0
  378. package/skills/data/refs/pipeline.md +92 -0
  379. package/skills/deliberate/SKILL.md +125 -0
  380. package/skills/deliberate/refs/architecture-lens.md +37 -0
  381. package/skills/deliberate/refs/assumptions-lens.md +43 -0
  382. package/skills/deliberate/refs/code-lens.md +38 -0
  383. package/skills/deliberate/refs/content-lens.md +37 -0
  384. package/skills/deliberate/refs/data-lens.md +43 -0
  385. package/skills/deliberate/refs/design-lens.md +37 -0
  386. package/skills/deliberate/refs/opportunity-patterns.md +107 -0
  387. package/skills/deliberate/refs/ops-lens.md +43 -0
  388. package/skills/deliberate/refs/product-lens.md +46 -0
  389. package/skills/deliberate/refs/rethink-framework.md +77 -0
  390. package/skills/deliberate/refs/security-lens.md +47 -0
  391. package/skills/deliberate/refs/testing-lens.md +45 -0
  392. package/skills/deliberate/refs/timing-lens.md +42 -0
  393. package/skills/engineering/architecture/SKILL.md +177 -0
  394. package/skills/engineering/architecture/refs/adr-template.md +268 -0
  395. package/skills/engineering/architecture/refs/architecture-template-deployment.md +176 -0
  396. package/skills/engineering/architecture/refs/architecture-template-design.md +300 -0
  397. package/skills/engineering/architecture/refs/examples-ecommerce-analytics.md +299 -0
  398. package/skills/engineering/architecture/refs/examples-saas-trading.md +291 -0
  399. package/skills/engineering/architecture/refs/patterns-advanced.md +238 -0
  400. package/skills/engineering/architecture/refs/patterns-structural.md +303 -0
  401. package/skills/engineering/backend/SKILL.md +102 -0
  402. package/skills/engineering/backend/refs/checklists.md +146 -0
  403. package/skills/engineering/backend/refs/patterns.md +181 -0
  404. package/skills/engineering/debugging/SKILL.md +99 -0
  405. package/skills/engineering/debugging/refs/patterns.md +248 -0
  406. package/skills/engineering/debugging/refs/process.md +203 -0
  407. package/skills/engineering/docs/SKILL.md +97 -0
  408. package/skills/engineering/engineering/SKILL.md +88 -0
  409. package/skills/engineering/engineering/refs/checklists.md +91 -0
  410. package/skills/engineering/engineering/refs/templates.md +71 -0
  411. package/skills/engineering/frontend/SKILL.md +92 -0
  412. package/skills/engineering/frontend/refs/checklists.md +96 -0
  413. package/skills/engineering/frontend/refs/patterns.md +167 -0
  414. package/skills/engineering/integration/SKILL.md +183 -0
  415. package/skills/engineering/integration/refs/error-handling-best-practices.md +139 -0
  416. package/skills/engineering/integration/refs/error-handling-error-classes-typescript.md +212 -0
  417. package/skills/engineering/integration/refs/error-handling-error-response-format.md +301 -0
  418. package/skills/engineering/integration/refs/error-handling-retry-strategies.md +190 -0
  419. package/skills/engineering/integration/refs/event-schemas-best-practices.md +225 -0
  420. package/skills/engineering/integration/refs/event-schemas-event-naming-conventions.md +303 -0
  421. package/skills/engineering/integration/refs/event-schemas-event-structure.md +288 -0
  422. package/skills/engineering/integration/refs/graphql-best-practices.md +138 -0
  423. package/skills/engineering/integration/refs/graphql-error-handling.md +198 -0
  424. package/skills/engineering/integration/refs/graphql-graphql-basics.md +197 -0
  425. package/skills/engineering/integration/refs/graphql-query-examples.md +226 -0
  426. package/skills/engineering/integration/refs/graphql-schema-design-patterns.md +280 -0
  427. package/skills/engineering/integration/refs/grpc-grpc-overview.md +246 -0
  428. package/skills/engineering/integration/refs/grpc-implementation-examples.md +269 -0
  429. package/skills/engineering/integration/refs/grpc-metadata-headers.md +304 -0
  430. package/skills/engineering/integration/refs/grpc-testing.md +49 -0
  431. package/skills/engineering/integration/refs/openapi-template.yaml +682 -0
  432. package/skills/engineering/integration/refs/resilience-best-practices.md +109 -0
  433. package/skills/engineering/integration/refs/resilience-circuit-breaker-pattern.md +302 -0
  434. package/skills/engineering/integration/refs/resilience-rate-limiting.md +245 -0
  435. package/skills/engineering/integration/refs/resilience-timeout-pattern.md +212 -0
  436. package/skills/engineering/integration/refs/rest-api-fundamentals.md +299 -0
  437. package/skills/engineering/integration/refs/rest-api-responses-security.md +302 -0
  438. package/skills/engineering/integration/refs/versioning-breaking-changes.md +237 -0
  439. package/skills/engineering/integration/refs/versioning-migration-strategies.md +257 -0
  440. package/skills/engineering/integration/refs/versioning-versioning-strategies.md +296 -0
  441. package/skills/engineering/large-scale-migration/SKILL.md +65 -0
  442. package/skills/engineering/patch/SKILL.md +184 -0
  443. package/skills/engineering/refs/arch.md +126 -0
  444. package/skills/engineering/refs/debug.md +142 -0
  445. package/skills/engineering/refs/docs.md +190 -0
  446. package/skills/engineering/refs/plan.md +109 -0
  447. package/skills/engineering/refs/review.md +138 -0
  448. package/skills/engineering/system-design/SKILL.md +154 -0
  449. package/skills/engineering/system-design/refs/anti-patterns-big-ball-of-mud.md +254 -0
  450. package/skills/engineering/system-design/refs/anti-patterns-circular-dependencies.md +241 -0
  451. package/skills/engineering/system-design/refs/anti-patterns-feature-envy.md +168 -0
  452. package/skills/engineering/system-design/refs/anti-patterns-god-object.md +247 -0
  453. package/skills/engineering/system-design/refs/component-template-lifecycle.md +210 -0
  454. package/skills/engineering/system-design/refs/component-template-maintenance.md +219 -0
  455. package/skills/engineering/system-design/refs/component-template-structure.md +276 -0
  456. package/skills/engineering/system-design/refs/dependencies-best-practices.md +260 -0
  457. package/skills/engineering/system-design/refs/dependencies-dependency-principles.md +295 -0
  458. package/skills/engineering/system-design/refs/dependencies-dependency-tracking.md +261 -0
  459. package/skills/engineering/system-design/refs/interface-template-lifecycle.md +211 -0
  460. package/skills/engineering/system-design/refs/interface-template-maintenance.md +212 -0
  461. package/skills/engineering/system-design/refs/interface-template-structure.md +269 -0
  462. package/skills/engineering/system-design/refs/patterns-dependency-injection.md +151 -0
  463. package/skills/engineering/system-design/refs/patterns-facade-pattern.md +257 -0
  464. package/skills/engineering/system-design/refs/patterns-layered-pattern.md +204 -0
  465. package/skills/engineering/system-design/refs/patterns-plugin-architecture.md +229 -0
  466. package/skills/engineering/unit-test-quality/SKILL.md +194 -0
  467. package/skills/ground/SKILL.md +130 -0
  468. package/skills/integration-discovery/SKILL.md +168 -0
  469. package/skills/integration-discovery/refs/capabilities.md +282 -0
  470. package/skills/integration-discovery/refs/cli-detection.md +295 -0
  471. package/skills/integration-discovery/refs/discovery-sources.md +233 -0
  472. package/skills/integration-discovery/refs/task-patterns.md +206 -0
  473. package/skills/jam/SKILL.md +45 -0
  474. package/skills/jam/refs/facilitation-patterns.md +125 -0
  475. package/skills/jam/refs/quick.md +64 -0
  476. package/skills/jam/refs/synthesis-patterns.md +114 -0
  477. package/skills/multi-model/SKILL.md +168 -0
  478. package/skills/multi-model/refs/aichat.md +119 -0
  479. package/skills/multi-model/refs/aider.md +100 -0
  480. package/skills/multi-model/refs/auditability.md +238 -0
  481. package/skills/multi-model/refs/codex.md +169 -0
  482. package/skills/multi-model/refs/context.md +306 -0
  483. package/skills/multi-model/refs/copilot.md +161 -0
  484. package/skills/multi-model/refs/examples.md +224 -0
  485. package/skills/multi-model/refs/gemini.md +185 -0
  486. package/skills/multi-model/refs/goose.md +112 -0
  487. package/skills/multi-model/refs/llm.md +124 -0
  488. package/skills/multi-model/refs/opencode.md +196 -0
  489. package/skills/multi-model/refs/orchestration.md +188 -0
  490. package/skills/multi-model/refs/pi.md +208 -0
  491. package/skills/persona/SKILL.md +144 -0
  492. package/skills/persona/refs/define.md +133 -0
  493. package/skills/platform/actions/refs/actions.md +114 -0
  494. package/skills/platform/audit/SKILL.md +174 -0
  495. package/skills/platform/audit/refs/audit.md +145 -0
  496. package/skills/platform/audit/refs/checklists-evidence-operations.md +144 -0
  497. package/skills/platform/audit/refs/checklists-gdpr-pci-evidence.md +213 -0
  498. package/skills/platform/audit/refs/checklists-soc2-hipaa.md +120 -0
  499. package/skills/platform/audit/refs/frameworks-gdpr-pci.md +225 -0
  500. package/skills/platform/audit/refs/frameworks-soc2-hipaa.md +228 -0
  501. package/skills/platform/compliance/SKILL.md +169 -0
  502. package/skills/platform/compliance/refs/checklists.md +303 -0
  503. package/skills/platform/compliance/refs/compliance.md +117 -0
  504. package/skills/platform/compliance/refs/frameworks.md +282 -0
  505. package/skills/platform/errors/SKILL.md +183 -0
  506. package/skills/platform/errors/refs/patterns.md +271 -0
  507. package/skills/platform/errors/refs/severity.md +264 -0
  508. package/skills/platform/gate-benchmark-rebaseline/SKILL.md +108 -0
  509. package/skills/platform/gh/refs/gh.md +105 -0
  510. package/skills/platform/gh-cli/SKILL.md +120 -0
  511. package/skills/platform/gh-cli/refs/automation.md +63 -0
  512. package/skills/platform/gh-cli/refs/gh-ops.md +103 -0
  513. package/skills/platform/gh-cli/refs/patterns.md +132 -0
  514. package/skills/platform/gh-cli/scripts/gh_ops.py +454 -0
  515. package/skills/platform/github-actions/SKILL.md +159 -0
  516. package/skills/platform/github-actions/refs/security.md +150 -0
  517. package/skills/platform/github-actions/refs/templates.md +230 -0
  518. package/skills/platform/github-actions/refs/troubleshooting.md +174 -0
  519. package/skills/platform/github-actions/scripts/workflow_gen.py +215 -0
  520. package/skills/platform/gitlab-ci/SKILL.md +140 -0
  521. package/skills/platform/gitlab-ci/refs/templates.md +223 -0
  522. package/skills/platform/gitlab-ci/refs/troubleshooting.md +213 -0
  523. package/skills/platform/glab-cli/SKILL.md +133 -0
  524. package/skills/platform/glab-cli/refs/ci-debugging.md +95 -0
  525. package/skills/platform/glab-cli/refs/glab-ops.md +78 -0
  526. package/skills/platform/glab-cli/refs/patterns.md +131 -0
  527. package/skills/platform/glab-cli/scripts/glab_ops.py +313 -0
  528. package/skills/platform/health/SKILL.md +177 -0
  529. package/skills/platform/health/refs/health.md +72 -0
  530. package/skills/platform/health/refs/sources.md +302 -0
  531. package/skills/platform/incident/refs/incident.md +136 -0
  532. package/skills/platform/infra/refs/infra.md +104 -0
  533. package/skills/platform/observability/SKILL.md +128 -0
  534. package/skills/platform/observability/refs/toolchain-discovery.md +243 -0
  535. package/skills/platform/prereq-doctor/SKILL.md +57 -0
  536. package/skills/platform/prereq-doctor/refs/tool-registry.md +58 -0
  537. package/skills/platform/traces/refs/traces.md +101 -0
  538. package/skills/product/acceptance-criteria/SKILL.md +193 -0
  539. package/skills/product/acceptance-criteria/refs/ac-anti-patterns.md +291 -0
  540. package/skills/product/acceptance-criteria/refs/ac-examples.md +277 -0
  541. package/skills/product/accessibility/SKILL.md +197 -0
  542. package/skills/product/accessibility/refs/aria-patterns-basics.md +250 -0
  543. package/skills/product/accessibility/refs/aria-patterns-dynamic.md +187 -0
  544. package/skills/product/accessibility/refs/aria-patterns-interactive.md +246 -0
  545. package/skills/product/accessibility/refs/audit-report-findings.md +248 -0
  546. package/skills/product/accessibility/refs/audit-report-results.md +276 -0
  547. package/skills/product/accessibility/refs/common-violations-html-css.md +270 -0
  548. package/skills/product/accessibility/refs/common-violations-interactive.md +303 -0
  549. package/skills/product/accessibility/refs/keyboard-testing-basics.md +222 -0
  550. package/skills/product/accessibility/refs/keyboard-testing-patterns.md +173 -0
  551. package/skills/product/accessibility/refs/screen-reader-testing-advanced.md +294 -0
  552. package/skills/product/accessibility/refs/screen-reader-testing-commands.md +255 -0
  553. package/skills/product/accessibility/refs/wcag-checklist.md +262 -0
  554. package/skills/product/analyze/SKILL.md +164 -0
  555. package/skills/product/analyze/refs/algorithms.md +238 -0
  556. package/skills/product/analyze/refs/sentiment-patterns.md +180 -0
  557. package/skills/product/imagery/SKILL.md +97 -0
  558. package/skills/product/imagery/alter/SKILL.md +105 -0
  559. package/skills/product/imagery/alter/refs/editing-reference.md +146 -0
  560. package/skills/product/imagery/create/SKILL.md +109 -0
  561. package/skills/product/imagery/create/refs/prompt-engineering.md +112 -0
  562. package/skills/product/imagery/create/refs/provider-reference.md +219 -0
  563. package/skills/product/imagery/review/SKILL.md +101 -0
  564. package/skills/product/imagery/review/refs/analysis-lenses.md +136 -0
  565. package/skills/product/imagery/review/refs/quality-gates.md +153 -0
  566. package/skills/product/imagery/scripts/analyze.sh +23 -0
  567. package/skills/product/imagery/scripts/edit.sh +14 -0
  568. package/skills/product/imagery/scripts/generate.sh +13 -0
  569. package/skills/product/imagery/scripts/provider.py +787 -0
  570. package/skills/product/listen/SKILL.md +175 -0
  571. package/skills/product/listen/refs/channels.md +220 -0
  572. package/skills/product/mockup/SKILL.md +106 -0
  573. package/skills/product/mockup/refs/templates.md +95 -0
  574. package/skills/product/refs/a11y.md +109 -0
  575. package/skills/product/refs/acceptance.md +79 -0
  576. package/skills/product/refs/align.md +67 -0
  577. package/skills/product/refs/analyze.md +77 -0
  578. package/skills/product/refs/elicit.md +67 -0
  579. package/skills/product/refs/listen.md +58 -0
  580. package/skills/product/refs/mockup.md +77 -0
  581. package/skills/product/refs/screenshot.md +59 -0
  582. package/skills/product/refs/strategy.md +77 -0
  583. package/skills/product/refs/synthesize.md +54 -0
  584. package/skills/product/refs/ux-review.md +68 -0
  585. package/skills/product/refs/ux.md +101 -0
  586. package/skills/product/requirements-analysis/SKILL.md +136 -0
  587. package/skills/product/requirements-analysis/refs/requirements-example-auth.md +264 -0
  588. package/skills/product/requirements-analysis/refs/requirements-example-export-integration.md +88 -0
  589. package/skills/product/requirements-analysis/refs/requirements-output-format-template.md +247 -0
  590. package/skills/product/requirements-analysis/refs/user-story-guide.md +296 -0
  591. package/skills/product/requirements-graph/SKILL.md +155 -0
  592. package/skills/product/requirements-graph/refs/examples.md +223 -0
  593. package/skills/product/requirements-graph/refs/schema.md +162 -0
  594. package/skills/product/requirements-migrate/SKILL.md +90 -0
  595. package/skills/product/requirements-navigate/SKILL.md +141 -0
  596. package/skills/product/requirements-navigate/refs/meta-templates.md +141 -0
  597. package/skills/product/screenshot/SKILL.md +147 -0
  598. package/skills/product/strategy/SKILL.md +198 -0
  599. package/skills/product/strategy/refs/frameworks.md +295 -0
  600. package/skills/product/strategy/refs/templates-competitive-market.md +299 -0
  601. package/skills/product/strategy/refs/templates-roi-value.md +207 -0
  602. package/skills/product/synthesize/SKILL.md +116 -0
  603. package/skills/product/synthesize/refs/journey-mapping.md +212 -0
  604. package/skills/product/synthesize/refs/prioritization.md +193 -0
  605. package/skills/product/ux-review/SKILL.md +164 -0
  606. package/skills/product/visual-direction/SKILL.md +137 -0
  607. package/skills/product/visual-review/SKILL.md +131 -0
  608. package/skills/runtime-exec/SKILL.md +138 -0
  609. package/skills/search/codebase-narrator/SKILL.md +131 -0
  610. package/skills/search/codebase-narrator/refs/output-template.md +100 -0
  611. package/skills/smaht/SKILL.md +72 -0
  612. package/skills/smaht/discovery/SKILL.md +65 -0
  613. package/skills/smaht/intent/SKILL.md +89 -0
  614. package/skills/smaht/propose-skills/SKILL.md +142 -0
  615. package/skills/smaht/refs/state.md +76 -0
  616. package/skills/swarm/SKILL.md +132 -0
  617. package/skills/swarm/refs/fan-out.md +104 -0
  618. package/skills/swarm/refs/independent-verification.md +86 -0
  619. package/skills/swarm/refs/receipts-and-evidence.md +69 -0
  620. package/skills/swarm/refs/recon-synthesis.md +87 -0
  621. package/skills/swarm/refs/ship-discipline.md +90 -0
  622. package/skills/wickedizer/SKILL.md +147 -0
  623. package/skills/wickedizer/refs/artifact-types.md +238 -0
  624. package/skills/wickedizer/refs/complexity.md +278 -0
  625. package/skills/wickedizer/refs/de-ai-pass.md +140 -0
  626. package/skills/wickedizer/refs/failure-modes.md +249 -0
  627. package/skills/wickedizer/refs/patterns.md +282 -0
  628. package/skills/wickedizer/refs/review.md +221 -0
  629. package/skills/wickedizer/refs/structure.md +282 -0
  630. package/skills/wickedizer/refs/voice-profile.md +125 -0
  631. package/skills/workflow/SKILL.md +132 -0
  632. package/skills/workflow/refs/archetype-adjustments.md +205 -0
  633. package/skills/workflow/refs/evidence.md +58 -0
  634. package/skills/workflow/refs/integration.md +130 -0
  635. package/skills/workflow/refs/risk-dimension-signals.md +260 -0
  636. package/skills/workflow/refs/scoring-rubric.md +49 -0
  637. package/skills/workflow/refs/signal-keywords.md +176 -0
  638. package/skills/workflow/refs/specialist-routing-rules.md +159 -0
  639. package/skills/worktrees/SKILL.md +87 -0
  640. package/skills/worktrees/refs/recipes.md +96 -0
@@ -0,0 +1,1005 @@
1
+ #!/usr/bin/env python3
2
+ """Spec-to-code TRACEABILITY heuristic (issue #444) — NOT a semantic judgment.
3
+
4
+ Autonomous post-implementation pass that extracts numbered acceptance criteria
5
+ (AC-*) and functional requirements (FR-*/REQ-*) from clarify-phase specs
6
+ (``acceptance-criteria.md``, ``objective.md``) and checks whether each one is
7
+ *referenced* in the build-phase implementation + test corpus.
8
+
9
+ Emits a structured Gap Report (JSON) per spec item with ``status`` in
10
+ ``{aligned, divergent, missing}``, confidence score, evidence, and reason.
11
+
12
+ **Important — what this does NOT do.** It matches the AC *identifier* (e.g.
13
+ ``AC-1``) and surrounding keywords, not the AC's *meaning*. An untagged-but-
14
+ correct implementation reads as MISSING (the code below `subtract` fully
15
+ implements "AC-2: subtract" but, lacking an ``AC-2`` reference, is reported
16
+ missing); a tagged-but-wrong one can read ALIGNED. So this is a traceability
17
+ pre-filter — "is each AC referenced?" — that complements
18
+ ``verification_protocol.py`` check #6.
19
+
20
+ The genuinely *semantic* question — "does the code correctly implement what the
21
+ AC described?" — is answered by the ``agents/qe/semantic-reviewer`` agent (LLM):
22
+ it reads the code, judges each AC by **meaning** (behavior, not id occurrence),
23
+ and records its verdict as an independent vault **attestation**. A gate declared
24
+ with ``require_attestation`` then enforces it (UNATTESTED/REJECT until the
25
+ independent reviewer attests pass). This script is that agent's cheap pre-filter
26
+ — do not gate on its verdict alone.
27
+
28
+ CLI::
29
+
30
+ semantic_review.py review \\
31
+ --project-dir <dir> [--ac-file <path>] [--objective-file <path>] \\
32
+ [--impl-dir <dir>] [--test-dir <dir>] [--output <json-path>]
33
+
34
+ semantic_review.py constraints \\
35
+ --adr-dir <dir> [--output <json-path>]
36
+
37
+ Python API::
38
+
39
+ from semantic_review import (
40
+ extract_spec_items, generate_gap_report, extract_adr_constraints,
41
+ )
42
+
43
+ Design notes
44
+ ------------
45
+ - **Stdlib-only**. No external deps. Cross-platform.
46
+ - **Heuristic-based** — LLM-free. Uses keyword overlap + reference counting
47
+ and a curated list of ``constraint signals`` (numeric limits, scoping
48
+ phrases like "per session", modal verbs).
49
+ - **Conservative** on missing — only reports MISSING when the AC identifier
50
+ literal is absent from both the implementation corpus and the test corpus.
51
+ - **Conservative** on aligned — requires the AC identifier in BOTH corpora
52
+ AND a minimum keyword overlap with the code context around the reference.
53
+ - **Divergent** — everything else where the AC is referenced but signals
54
+ don't line up. Confidence score reports how sure we are.
55
+
56
+ The agent consuming this output (``agents/qe/semantic-reviewer.md``) can
57
+ promote MISSING/DIVERGENT findings to gate conditions when complexity >= 3.
58
+ """
59
+ from __future__ import annotations
60
+
61
+ import argparse
62
+ import json
63
+ import re
64
+ import sys
65
+ from dataclasses import asdict, dataclass, field
66
+ from pathlib import Path
67
+ from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple
68
+
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # Constants
72
+ # ---------------------------------------------------------------------------
73
+
74
+ # Spec-id extraction — accept AC-*, FR-*, REQ-* (with optional domain infix,
75
+ # e.g. REQ-auth-3). Matches existing patterns used by verification_protocol.py
76
+ # and traceability_generator.
77
+ _SPEC_ID_RE = re.compile(
78
+ r"\b(?:AC|FR|REQ|NFR|US)-[A-Za-z]*-?\d+\b",
79
+ )
80
+
81
+ # Table-row AC extraction (from verification_protocol._AC_TABLE_ROW_RE)
82
+ _AC_TABLE_ROW_RE = re.compile(
83
+ r"\|\s*(?P<id>AC-\d+|[A-Z]{1,4}-\d+|\d+)\s*\|"
84
+ r"\s*(?P<desc>[^|]+)\s*\|"
85
+ r"\s*(?P<test>[^|]*)\s*\|",
86
+ re.IGNORECASE,
87
+ )
88
+
89
+ # List-style AC extraction
90
+ _AC_LIST_RE = re.compile(
91
+ r"[-*]\s+(?:\*\*)?(?P<id>AC-\d+|[A-Z]{1,4}-\d+)(?:\*\*)?\s*[:\-]\s*(?P<desc>.+)",
92
+ re.IGNORECASE,
93
+ )
94
+
95
+ # Inline AC extraction (e.g. "AC1: Given ... When ... Then ...")
96
+ _AC_INLINE_RE = re.compile(
97
+ r"(?P<id>AC\d+|[A-Z]{1,4}-\d+|AC-\d+)\s*[:\-]\s*(?P<desc>Given[^\n]+)",
98
+ re.IGNORECASE,
99
+ )
100
+
101
+ # Constraint signals — numeric quantifiers, scoping qualifiers, and modal
102
+ # verbs that frequently appear in ACs and must be honored verbatim.
103
+ _NUMERIC_CONSTRAINT_RE = re.compile(
104
+ r"\b(\d+)\s*(ms|s|seconds?|min(?:utes?)?|hours?|days?|%|requests?|"
105
+ r"attempts?|tries|sessions?|users?|bytes?|kb|mb|gb|tokens?|items?)\b",
106
+ re.IGNORECASE,
107
+ )
108
+
109
+ _SCOPE_PHRASE_RE = re.compile(
110
+ r"\bper\s+(session|request|user|tenant|account|minute|hour|day|page|call|token|device|ip)\b",
111
+ re.IGNORECASE,
112
+ )
113
+
114
+ _MODAL_DIRECTIVE_RE = re.compile(
115
+ r"\b(MUST NOT|MUST|SHALL NOT|SHALL|SHOULD NOT|SHOULD|REQUIRED|FORBIDDEN)\b",
116
+ )
117
+
118
+ # Word extraction for overlap scoring — alphanumeric tokens of length 3+,
119
+ # lowercase normalised, minus stopwords.
120
+ _WORD_RE = re.compile(r"[A-Za-z][A-Za-z0-9]{2,}")
121
+
122
+ _STOPWORDS = frozenset({
123
+ "the", "and", "for", "when", "then", "given", "that", "with", "this",
124
+ "has", "have", "will", "shall", "should", "must", "not", "are", "was",
125
+ "were", "from", "user", "users", "system", "code", "test", "case",
126
+ "example", "into", "than", "less", "more", "which", "while", "its",
127
+ "any", "all", "one", "two", "three", "four", "five", "also", "their",
128
+ "been", "being", "upon", "other", "some", "such", "each", "them",
129
+ "they", "these", "those", "there", "here", "above", "below",
130
+ })
131
+
132
+ # Context window around AC references (chars on either side) for overlap
133
+ # scoring.
134
+ _CONTEXT_WINDOW = 400
135
+
136
+ # Alignment thresholds.
137
+ _ALIGNED_OVERLAP_THRESHOLD = 0.35 # >= this ratio of AC keywords seen in context
138
+ _DIVERGENT_MIN_OVERLAP = 0.10 # below this & referenced => divergent
139
+
140
+ _DEFAULT_IMPL_EXTENSIONS = (
141
+ ".py", ".js", ".jsx", ".ts", ".tsx", ".go", ".rs", ".java", ".kt",
142
+ ".swift", ".rb", ".c", ".h", ".cpp", ".hpp", ".md", ".json", ".yaml",
143
+ ".yml", ".sh",
144
+ )
145
+ _DEFAULT_TEST_DIR_NAMES = ("tests", "test", "__tests__", "spec", "specs")
146
+
147
+ # Complexity threshold at/above which semantic alignment is MANDATORY.
148
+ SEMANTIC_ALIGNMENT_COMPLEXITY_THRESHOLD = 3
149
+
150
+ # Gate-result verdicts
151
+ _VERDICT_APPROVE = "APPROVE"
152
+ _VERDICT_CONDITIONAL = "CONDITIONAL"
153
+ _VERDICT_REJECT = "REJECT"
154
+
155
+
156
+ # ---------------------------------------------------------------------------
157
+ # Data classes
158
+ # ---------------------------------------------------------------------------
159
+
160
+ @dataclass
161
+ class SpecItem:
162
+ """A single extracted acceptance criterion / functional requirement."""
163
+ id: str
164
+ description: str
165
+ source_file: str
166
+ priority: Optional[str] = None # P0/P1/P2 when detected
167
+
168
+ def keywords(self) -> List[str]:
169
+ """Return deduped, lowercased, stopword-filtered keywords."""
170
+ return _keywords(self.description)
171
+
172
+ def constraints(self) -> List[str]:
173
+ """Return concrete constraint literals from the description."""
174
+ return _extract_constraint_literals(self.description)
175
+
176
+
177
+ @dataclass
178
+ class Finding:
179
+ """One Gap Report finding for a single spec item."""
180
+ id: str
181
+ description: str
182
+ source_file: str
183
+ status: str # aligned | divergent | missing
184
+ confidence: float # 0.0 - 1.0
185
+ evidence: List[str] # file paths / snippets supporting the verdict
186
+ reason: str # human-readable why
187
+ expected_constraints: List[str] = field(default_factory=list)
188
+ matched_constraints: List[str] = field(default_factory=list)
189
+ unmatched_constraints: List[str] = field(default_factory=list)
190
+ in_impl: bool = False
191
+ in_tests: bool = False
192
+
193
+
194
+ @dataclass
195
+ class GapReport:
196
+ """Full Gap Report payload."""
197
+ project: str
198
+ complexity: int
199
+ total: int
200
+ aligned: int
201
+ divergent: int
202
+ missing: int
203
+ verdict: str # APPROVE | CONDITIONAL | REJECT
204
+ score: float # aligned / total
205
+ findings: List[Finding]
206
+ summary: str
207
+ # Emit schema_version so downstream consumers can evolve safely.
208
+ schema_version: str = "1.0.0"
209
+
210
+
211
+ # ---------------------------------------------------------------------------
212
+ # Helpers
213
+ # ---------------------------------------------------------------------------
214
+
215
+ def _read_text(path: Path) -> str:
216
+ """Read file as UTF-8 text, replacing bad bytes. Returns "" on error."""
217
+ try:
218
+ return path.read_text(encoding="utf-8", errors="replace")
219
+ except (OSError, ValueError):
220
+ return ""
221
+
222
+
223
+ def _keywords(text: str) -> List[str]:
224
+ """Return deduped, lowercased, stopword-filtered tokens from text."""
225
+ seen: set = set()
226
+ out: List[str] = []
227
+ for tok in _WORD_RE.findall(text):
228
+ low = tok.lower()
229
+ if low in _STOPWORDS:
230
+ continue
231
+ if low not in seen:
232
+ seen.add(low)
233
+ out.append(low)
234
+ return out
235
+
236
+
237
+ def _extract_constraint_literals(text: str) -> List[str]:
238
+ """Extract concrete numeric + scope constraints from a description.
239
+
240
+ Example: "3 failed attempts per session" -> ["3 attempts", "per session"].
241
+ Returned literals are normalised-lowercase and deduped preserving order.
242
+ """
243
+ found: List[str] = []
244
+ seen: set = set()
245
+
246
+ for match in _NUMERIC_CONSTRAINT_RE.finditer(text):
247
+ literal = f"{match.group(1)} {match.group(2).lower()}"
248
+ if literal not in seen:
249
+ seen.add(literal)
250
+ found.append(literal)
251
+
252
+ for match in _SCOPE_PHRASE_RE.finditer(text):
253
+ literal = f"per {match.group(1).lower()}"
254
+ if literal not in seen:
255
+ seen.add(literal)
256
+ found.append(literal)
257
+
258
+ return found
259
+
260
+
261
+ def _iter_source_files(
262
+ root: Path,
263
+ extensions: Sequence[str] = _DEFAULT_IMPL_EXTENSIONS,
264
+ exclude_paths: Optional[Sequence[Path]] = None,
265
+ ) -> Iterable[Path]:
266
+ """Yield source files under root with matching extensions.
267
+
268
+ Skips common build / vcs / dep dirs so we don't index node_modules.
269
+ Also skips any path inside the explicit ``exclude_paths`` set (used to
270
+ prevent the spec files themselves from being scanned as implementation,
271
+ which otherwise self-matches every AC).
272
+ """
273
+ if not root.is_dir():
274
+ return
275
+ skip_dirs = {
276
+ ".git", "node_modules", "__pycache__", ".venv", "venv", "dist",
277
+ "build", ".pytest_cache", ".mypy_cache", "target", ".next",
278
+ "coverage", ".claude",
279
+ }
280
+ # The ``phases/`` tree of a crew project holds spec artefacts — never
281
+ # scan those as implementation. Using a ``parts`` check so we catch
282
+ # phases/ at any depth relative to ``root``.
283
+ skip_top_segments = {"phases"}
284
+ resolved_excludes = {Path(p).resolve() for p in (exclude_paths or [])}
285
+
286
+ for path in root.rglob("*"):
287
+ if not path.is_file():
288
+ continue
289
+ rel_parts = path.relative_to(root).parts[:-1]
290
+ if any(part in skip_dirs for part in rel_parts):
291
+ continue
292
+ if rel_parts and rel_parts[0] in skip_top_segments:
293
+ continue
294
+ if path.resolve() in resolved_excludes:
295
+ continue
296
+ if path.suffix.lower() in extensions:
297
+ yield path
298
+
299
+
300
+ # ---------------------------------------------------------------------------
301
+ # Spec extraction
302
+ # ---------------------------------------------------------------------------
303
+
304
+ def extract_spec_items(
305
+ ac_file: Optional[Path] = None,
306
+ objective_file: Optional[Path] = None,
307
+ extra_sources: Optional[Sequence[Path]] = None,
308
+ ) -> List[SpecItem]:
309
+ """Extract numbered spec items (AC / FR / REQ) from given files.
310
+
311
+ Args:
312
+ ac_file: Path to ``acceptance-criteria.md`` (preferred primary source).
313
+ objective_file: Path to ``objective.md`` (secondary source; FR lines).
314
+ extra_sources: Additional markdown files to scan (e.g. ADRs).
315
+
316
+ Returns:
317
+ Deduped list of SpecItem ordered by first appearance.
318
+ """
319
+ sources: List[Path] = []
320
+ if ac_file and ac_file.exists():
321
+ sources.append(ac_file)
322
+ if objective_file and objective_file.exists():
323
+ sources.append(objective_file)
324
+ if extra_sources:
325
+ for src in extra_sources:
326
+ if src and src.exists():
327
+ sources.append(src)
328
+
329
+ items: List[SpecItem] = []
330
+ seen_ids: set = set()
331
+
332
+ for src in sources:
333
+ text = _read_text(src)
334
+ if not text:
335
+ continue
336
+ for item in _extract_from_text(text, str(src)):
337
+ # Normalise id casing — AC-1 and ac-1 should dedupe.
338
+ key = item.id.upper().replace(" ", "")
339
+ if key in seen_ids:
340
+ # Enrich: merge description if we had a shorter one before.
341
+ continue
342
+ seen_ids.add(key)
343
+ items.append(SpecItem(
344
+ id=item.id.upper().replace(" ", ""),
345
+ description=item.description,
346
+ source_file=item.source_file,
347
+ priority=item.priority,
348
+ ))
349
+
350
+ return items
351
+
352
+
353
+ def _extract_from_text(text: str, source_file: str) -> List[SpecItem]:
354
+ """Pull AC/FR items out of a single markdown body."""
355
+ out: List[SpecItem] = []
356
+ seen: set = set()
357
+
358
+ # Strategy 1 — table rows
359
+ for match in _AC_TABLE_ROW_RE.finditer(text):
360
+ ac_id = match.group("id").strip()
361
+ desc = match.group("desc").strip()
362
+ if "criterion" in desc.lower() or "description" in desc.lower():
363
+ continue # header row
364
+ if ac_id and ac_id not in seen:
365
+ seen.add(ac_id)
366
+ out.append(SpecItem(
367
+ id=ac_id,
368
+ description=desc,
369
+ source_file=source_file,
370
+ priority=_detect_priority(desc),
371
+ ))
372
+
373
+ # Strategy 2 — bulleted list "- AC-1: Given ..., When ..., Then ..."
374
+ for match in _AC_LIST_RE.finditer(text):
375
+ ac_id = match.group("id").strip()
376
+ desc = match.group("desc").strip()
377
+ if ac_id and ac_id not in seen:
378
+ seen.add(ac_id)
379
+ out.append(SpecItem(
380
+ id=ac_id,
381
+ description=desc,
382
+ source_file=source_file,
383
+ priority=_detect_priority(desc),
384
+ ))
385
+
386
+ # Strategy 3 — inline "AC1: Given ... When ... Then ..."
387
+ for match in _AC_INLINE_RE.finditer(text):
388
+ ac_id = match.group("id").strip()
389
+ desc = match.group("desc").strip()
390
+ if ac_id and ac_id not in seen:
391
+ seen.add(ac_id)
392
+ out.append(SpecItem(
393
+ id=ac_id,
394
+ description=desc,
395
+ source_file=source_file,
396
+ priority=_detect_priority(desc),
397
+ ))
398
+
399
+ # Strategy 4 — bare id mentions as last resort, pair with surrounding line
400
+ if not out:
401
+ for match in _SPEC_ID_RE.finditer(text):
402
+ ac_id = match.group(0)
403
+ if ac_id in seen:
404
+ continue
405
+ # Capture up to 240 chars of context after the id as description.
406
+ start = match.end()
407
+ tail = text[start:start + 240].strip()
408
+ if not tail:
409
+ continue
410
+ # Cut at next newline-newline (end of paragraph) if present.
411
+ end_idx = tail.find("\n\n")
412
+ if end_idx >= 0:
413
+ tail = tail[:end_idx].strip()
414
+ seen.add(ac_id)
415
+ out.append(SpecItem(
416
+ id=ac_id,
417
+ description=tail[:240],
418
+ source_file=source_file,
419
+ priority=_detect_priority(tail),
420
+ ))
421
+
422
+ return out
423
+
424
+
425
+ _PRIORITY_RE = re.compile(r"\bP([012])\b")
426
+
427
+
428
+ def _detect_priority(text: str) -> Optional[str]:
429
+ match = _PRIORITY_RE.search(text)
430
+ return f"P{match.group(1)}" if match else None
431
+
432
+
433
+ # ---------------------------------------------------------------------------
434
+ # Alignment scoring
435
+ # ---------------------------------------------------------------------------
436
+
437
+ def _find_references(corpus: Dict[str, str], spec_id: str) -> List[Tuple[str, str]]:
438
+ """Return list of (file_path, context_snippet) for occurrences of spec_id.
439
+
440
+ Uses case-insensitive literal search. Returns up to 20 hits per id to
441
+ keep reports bounded.
442
+ """
443
+ needle = spec_id.upper()
444
+ needle_lower = spec_id.lower()
445
+ results: List[Tuple[str, str]] = []
446
+ for file_path, text in corpus.items():
447
+ if not text:
448
+ continue
449
+ # Quick reject — case-insensitive substring check.
450
+ lower = text.lower()
451
+ if needle_lower not in lower:
452
+ continue
453
+ # Collect each occurrence's context window.
454
+ idx = 0
455
+ while True:
456
+ pos = lower.find(needle_lower, idx)
457
+ if pos < 0:
458
+ break
459
+ start = max(0, pos - _CONTEXT_WINDOW)
460
+ end = min(len(text), pos + len(needle) + _CONTEXT_WINDOW)
461
+ snippet = text[start:end]
462
+ results.append((file_path, snippet))
463
+ idx = pos + len(needle)
464
+ if len(results) >= 20:
465
+ return results
466
+ return results
467
+
468
+
469
+ def _keyword_overlap(spec_keywords: Sequence[str], context: str) -> float:
470
+ """Ratio of spec keywords that appear in context text. Range 0.0-1.0."""
471
+ if not spec_keywords:
472
+ return 1.0
473
+ context_lower = context.lower()
474
+ hits = sum(1 for kw in spec_keywords if kw in context_lower)
475
+ return hits / len(spec_keywords)
476
+
477
+
478
+ def _best_overlap(
479
+ spec_keywords: Sequence[str],
480
+ refs: Sequence[Tuple[str, str]],
481
+ ) -> float:
482
+ """Return highest keyword-overlap ratio across all references."""
483
+ if not refs:
484
+ return 0.0
485
+ return max(_keyword_overlap(spec_keywords, snippet) for _, snippet in refs)
486
+
487
+
488
+ def _constraints_matched(
489
+ constraints: Sequence[str],
490
+ context: str,
491
+ ) -> Tuple[List[str], List[str]]:
492
+ """Return (matched, unmatched) constraint literals against context."""
493
+ matched: List[str] = []
494
+ unmatched: List[str] = []
495
+ lower = context.lower()
496
+ for literal in constraints:
497
+ if literal.lower() in lower:
498
+ matched.append(literal)
499
+ else:
500
+ unmatched.append(literal)
501
+ return matched, unmatched
502
+
503
+
504
+ def _aggregate_constraints(
505
+ constraints: Sequence[str],
506
+ refs: Sequence[Tuple[str, str]],
507
+ ) -> Tuple[List[str], List[str]]:
508
+ """Aggregate constraint matching across ALL reference contexts.
509
+
510
+ A constraint is counted as matched if it appears in ANY reference snippet.
511
+ """
512
+ if not constraints:
513
+ return [], []
514
+ combined = "\n".join(snippet for _, snippet in refs)
515
+ return _constraints_matched(constraints, combined)
516
+
517
+
518
+ def _classify(
519
+ item: SpecItem,
520
+ impl_refs: Sequence[Tuple[str, str]],
521
+ test_refs: Sequence[Tuple[str, str]],
522
+ ) -> Finding:
523
+ """Produce a Finding for a single spec item given impl + test references."""
524
+ in_impl = bool(impl_refs)
525
+ in_tests = bool(test_refs)
526
+
527
+ spec_keywords = item.keywords()
528
+ all_refs = list(impl_refs) + list(test_refs)
529
+
530
+ overlap = _best_overlap(spec_keywords, all_refs)
531
+ matched_c, unmatched_c = _aggregate_constraints(item.constraints(), all_refs)
532
+
533
+ evidence = [fp for fp, _ in all_refs[:8]]
534
+
535
+ # MISSING — literal id absent from both corpora.
536
+ if not in_impl and not in_tests:
537
+ return Finding(
538
+ id=item.id,
539
+ description=item.description,
540
+ source_file=item.source_file,
541
+ status="missing",
542
+ confidence=0.9,
543
+ evidence=[],
544
+ reason=(
545
+ f"Spec item {item.id} has no occurrence in implementation "
546
+ f"corpus or test corpus. Expected at least a comment or "
547
+ f"test-name reference."
548
+ ),
549
+ expected_constraints=item.constraints(),
550
+ matched_constraints=[],
551
+ unmatched_constraints=list(item.constraints()),
552
+ in_impl=False,
553
+ in_tests=False,
554
+ )
555
+
556
+ # DIVERGENT path 1 — referenced in one corpus only.
557
+ partial = (in_impl and not in_tests) or (in_tests and not in_impl)
558
+
559
+ # DIVERGENT path 2 — has numeric/scope constraints that aren't matched.
560
+ has_unmatched_constraints = bool(unmatched_c) and bool(item.constraints())
561
+
562
+ # DIVERGENT path 3 — keyword overlap below threshold
563
+ low_overlap = overlap < _ALIGNED_OVERLAP_THRESHOLD
564
+
565
+ if partial or has_unmatched_constraints or low_overlap:
566
+ # Build specific reason.
567
+ reasons: List[str] = []
568
+ if partial:
569
+ if in_impl and not in_tests:
570
+ reasons.append(
571
+ f"{item.id} referenced in implementation but no matching "
572
+ f"test asserts it"
573
+ )
574
+ else:
575
+ reasons.append(
576
+ f"{item.id} referenced in tests but not in implementation"
577
+ )
578
+ if has_unmatched_constraints:
579
+ reasons.append(
580
+ f"constraint(s) {unmatched_c} from spec not found near "
581
+ f"{item.id} references"
582
+ )
583
+ if low_overlap and not (partial or has_unmatched_constraints):
584
+ reasons.append(
585
+ f"implementation context near {item.id} overlaps only "
586
+ f"{overlap:.0%} with spec keywords — likely divergent intent"
587
+ )
588
+ # Confidence: high when we have concrete constraint gaps, medium
589
+ # when only overlap is low.
590
+ confidence = 0.85 if has_unmatched_constraints else (
591
+ 0.7 if partial else 0.6
592
+ )
593
+ return Finding(
594
+ id=item.id,
595
+ description=item.description,
596
+ source_file=item.source_file,
597
+ status="divergent",
598
+ confidence=confidence,
599
+ evidence=evidence,
600
+ reason="; ".join(reasons),
601
+ expected_constraints=item.constraints(),
602
+ matched_constraints=matched_c,
603
+ unmatched_constraints=unmatched_c,
604
+ in_impl=in_impl,
605
+ in_tests=in_tests,
606
+ )
607
+
608
+ # ALIGNED — in both corpora, overlap >= threshold, all constraints matched.
609
+ return Finding(
610
+ id=item.id,
611
+ description=item.description,
612
+ source_file=item.source_file,
613
+ status="aligned",
614
+ confidence=min(0.95, 0.5 + overlap * 0.5),
615
+ evidence=evidence,
616
+ reason=(
617
+ f"{item.id} referenced in implementation ({sum(1 for _ in impl_refs)} "
618
+ f"hit(s)) and tests ({sum(1 for _ in test_refs)} hit(s)); "
619
+ f"keyword overlap {overlap:.0%} >= "
620
+ f"{_ALIGNED_OVERLAP_THRESHOLD:.0%}"
621
+ ),
622
+ expected_constraints=item.constraints(),
623
+ matched_constraints=matched_c,
624
+ unmatched_constraints=[],
625
+ in_impl=True,
626
+ in_tests=True,
627
+ )
628
+
629
+
630
+ # ---------------------------------------------------------------------------
631
+ # Report assembly
632
+ # ---------------------------------------------------------------------------
633
+
634
+ def _build_corpus(
635
+ directory: Path,
636
+ extensions: Sequence[str] = _DEFAULT_IMPL_EXTENSIONS,
637
+ file_limit: int = 5000,
638
+ exclude_paths: Optional[Sequence[Path]] = None,
639
+ ) -> Dict[str, str]:
640
+ """Build a {path: text} corpus for a directory, bounded by file_limit.
641
+
642
+ ``exclude_paths`` lets callers keep the spec files (``acceptance-criteria.md``,
643
+ ``objective.md``) out of the implementation scan — otherwise every AC
644
+ self-matches and every project looks 100% aligned.
645
+ """
646
+ corpus: Dict[str, str] = {}
647
+ if not directory.is_dir():
648
+ return corpus
649
+ count = 0
650
+ for path in _iter_source_files(directory, extensions, exclude_paths=exclude_paths):
651
+ text = _read_text(path)
652
+ if not text:
653
+ continue
654
+ corpus[str(path)] = text
655
+ count += 1
656
+ if count >= file_limit:
657
+ break
658
+ return corpus
659
+
660
+
661
+ def _build_corpus_from_files(files: Sequence[Path]) -> Dict[str, str]:
662
+ """Build a {path: text} corpus from an explicit file list."""
663
+ corpus: Dict[str, str] = {}
664
+ for path in files:
665
+ if not path.exists() or not path.is_file():
666
+ continue
667
+ text = _read_text(path)
668
+ if text:
669
+ corpus[str(path)] = text
670
+ return corpus
671
+
672
+
673
+ def _split_impl_and_tests(corpus: Dict[str, str]) -> Tuple[Dict[str, str], Dict[str, str]]:
674
+ """Partition an arbitrary corpus into (impl_corpus, test_corpus).
675
+
676
+ A file is considered a test file when any ancestor directory matches
677
+ a known test-dir name (``tests``, ``test``, ``spec``, etc.) or when
678
+ the filename starts with ``test_`` or ends with ``_test.py`` /
679
+ ``.test.ts`` / ``.spec.ts``.
680
+ """
681
+ impl: Dict[str, str] = {}
682
+ tests: Dict[str, str] = {}
683
+
684
+ for path_str, text in corpus.items():
685
+ p = Path(path_str)
686
+ is_test = False
687
+ for part in p.parts:
688
+ if part.lower() in _DEFAULT_TEST_DIR_NAMES:
689
+ is_test = True
690
+ break
691
+ if not is_test:
692
+ name = p.name.lower()
693
+ if (name.startswith("test_") or name.endswith("_test.py") or
694
+ name.endswith(".test.ts") or name.endswith(".test.tsx") or
695
+ name.endswith(".test.js") or name.endswith(".spec.ts") or
696
+ name.endswith(".spec.js")):
697
+ is_test = True
698
+ if is_test:
699
+ tests[path_str] = text
700
+ else:
701
+ impl[path_str] = text
702
+
703
+ return impl, tests
704
+
705
+
706
+ def generate_gap_report(
707
+ spec_items: Sequence[SpecItem],
708
+ impl_corpus: Dict[str, str],
709
+ test_corpus: Dict[str, str],
710
+ project: str = "unknown",
711
+ complexity: int = 0,
712
+ ) -> GapReport:
713
+ """Produce a complete Gap Report from spec items + impl/test corpora.
714
+
715
+ Args:
716
+ spec_items: Extracted AC/FR items from clarify-phase specs.
717
+ impl_corpus: {file_path: text} for non-test source files.
718
+ test_corpus: {file_path: text} for test files.
719
+ project: Project identifier (for the report header).
720
+ complexity: Project complexity score (0-7).
721
+
722
+ Returns:
723
+ GapReport dataclass — ``asdict`` it for JSON output.
724
+ """
725
+ findings: List[Finding] = []
726
+
727
+ for item in spec_items:
728
+ impl_refs = _find_references(impl_corpus, item.id)
729
+ test_refs = _find_references(test_corpus, item.id)
730
+ findings.append(_classify(item, impl_refs, test_refs))
731
+
732
+ total = len(findings)
733
+ aligned = sum(1 for f in findings if f.status == "aligned")
734
+ divergent = sum(1 for f in findings if f.status == "divergent")
735
+ missing = sum(1 for f in findings if f.status == "missing")
736
+
737
+ # Score = aligned ratio (1.0 when no specs — vacuously aligned).
738
+ score = (aligned / total) if total > 0 else 1.0
739
+
740
+ # Verdict: APPROVE only when all aligned; REJECT when anything missing;
741
+ # CONDITIONAL when only divergent findings.
742
+ if total == 0:
743
+ verdict = _VERDICT_APPROVE
744
+ summary = "No numbered spec items found — advisory skip."
745
+ elif missing > 0:
746
+ verdict = _VERDICT_REJECT
747
+ summary = (
748
+ f"{missing} spec item(s) missing from implementation. "
749
+ f"Hard reject — implement referenced AC before approval."
750
+ )
751
+ elif divergent > 0:
752
+ verdict = _VERDICT_CONDITIONAL
753
+ summary = (
754
+ f"{divergent} spec item(s) diverge from stated intent. "
755
+ f"Fix divergences or explicitly document the deviation."
756
+ )
757
+ else:
758
+ verdict = _VERDICT_APPROVE
759
+ summary = f"All {aligned} spec items aligned with implementation."
760
+
761
+ return GapReport(
762
+ project=project,
763
+ complexity=complexity,
764
+ total=total,
765
+ aligned=aligned,
766
+ divergent=divergent,
767
+ missing=missing,
768
+ verdict=verdict,
769
+ score=round(score, 3),
770
+ findings=findings,
771
+ summary=summary,
772
+ )
773
+
774
+
775
+ # ---------------------------------------------------------------------------
776
+ # ADR constraint extraction (conservative MVP)
777
+ # ---------------------------------------------------------------------------
778
+
779
+ def extract_adr_constraints(adr_dir: Path) -> List[Dict[str, str]]:
780
+ """Extract MUST/SHALL directives from ADR markdown files.
781
+
782
+ Conservative regex-only approach: pulls every line containing a modal
783
+ directive (MUST / MUST NOT / SHALL / SHALL NOT / SHOULD NOT) and returns
784
+ ``{source, directive, statement}``. A follow-up iteration could run these
785
+ statements against the implementation corpus and flag violations.
786
+
787
+ Returns:
788
+ List of dicts; empty if dir missing or no directives.
789
+
790
+ Limitations:
791
+ - Line-granular only (no multi-line parsing).
792
+ - No negation / scope reasoning — downstream code must still decide
793
+ whether a match is a violation or a legitimate exception.
794
+ - Treats every modal-directive hit as equally important.
795
+ """
796
+ out: List[Dict[str, str]] = []
797
+ if not adr_dir.is_dir():
798
+ return out
799
+
800
+ for md in sorted(adr_dir.rglob("*.md")):
801
+ text = _read_text(md)
802
+ if not text:
803
+ continue
804
+ for line_no, line in enumerate(text.splitlines(), start=1):
805
+ match = _MODAL_DIRECTIVE_RE.search(line)
806
+ if not match:
807
+ continue
808
+ # Skip section headers and front-matter noise.
809
+ stripped = line.strip()
810
+ if stripped.startswith("#") or stripped.startswith("---"):
811
+ continue
812
+ out.append({
813
+ "source": str(md),
814
+ "line": str(line_no),
815
+ "directive": match.group(1),
816
+ "statement": stripped[:240],
817
+ })
818
+
819
+ return out
820
+
821
+
822
+ # ---------------------------------------------------------------------------
823
+ # Orchestration — project-dir convention
824
+ # ---------------------------------------------------------------------------
825
+
826
+ def review_project(
827
+ project_dir: Path,
828
+ project_name: str = "",
829
+ complexity: int = 0,
830
+ ac_file: Optional[Path] = None,
831
+ objective_file: Optional[Path] = None,
832
+ impl_dir: Optional[Path] = None,
833
+ test_dir: Optional[Path] = None,
834
+ ) -> GapReport:
835
+ """Run a full semantic review over a project directory.
836
+
837
+ Expected layout (overrideable via args)::
838
+
839
+ <project_dir>/
840
+ phases/
841
+ clarify/
842
+ acceptance-criteria.md <-- ac_file default
843
+ objective.md <-- objective_file default
844
+ <impl sources> <-- impl_dir default = project_dir
845
+ tests/ <-- test_dir default
846
+
847
+ Args:
848
+ project_dir: Root dir containing phases/ and source.
849
+ project_name: Name for the report header.
850
+ complexity: Project complexity 0-7.
851
+ ac_file: Override path to acceptance-criteria.md.
852
+ objective_file: Override path to objective.md.
853
+ impl_dir: Override implementation-scan root. Defaults to project_dir.
854
+ test_dir: Override test-scan root. When None, impl_dir is scanned and
855
+ files are partitioned by filename + dir heuristic.
856
+
857
+ Returns:
858
+ GapReport.
859
+ """
860
+ ac_path = ac_file or (project_dir / "phases" / "clarify" / "acceptance-criteria.md")
861
+ obj_path = objective_file or (project_dir / "phases" / "clarify" / "objective.md")
862
+
863
+ spec_items = extract_spec_items(ac_file=ac_path, objective_file=obj_path)
864
+
865
+ impl_root = impl_dir or project_dir
866
+
867
+ # Never scan the spec files themselves — otherwise every AC
868
+ # self-matches and overlap is trivially 100%.
869
+ spec_excludes: List[Path] = []
870
+ if ac_path and ac_path.exists():
871
+ spec_excludes.append(ac_path)
872
+ if obj_path and obj_path.exists():
873
+ spec_excludes.append(obj_path)
874
+
875
+ if test_dir:
876
+ impl_corpus = _build_corpus(impl_root, exclude_paths=spec_excludes)
877
+ test_corpus = _build_corpus(test_dir, exclude_paths=spec_excludes)
878
+ # Avoid double-counting: strip test files from impl_corpus.
879
+ test_paths = set(test_corpus.keys())
880
+ impl_corpus = {p: t for p, t in impl_corpus.items() if p not in test_paths}
881
+ else:
882
+ full_corpus = _build_corpus(impl_root, exclude_paths=spec_excludes)
883
+ impl_corpus, test_corpus = _split_impl_and_tests(full_corpus)
884
+
885
+ project = project_name or project_dir.name
886
+
887
+ return generate_gap_report(
888
+ spec_items=spec_items,
889
+ impl_corpus=impl_corpus,
890
+ test_corpus=test_corpus,
891
+ project=project,
892
+ complexity=complexity,
893
+ )
894
+
895
+
896
+ # ---------------------------------------------------------------------------
897
+ # JSON serialisation
898
+ # ---------------------------------------------------------------------------
899
+
900
+ def report_to_dict(report: GapReport) -> Dict[str, Any]:
901
+ """Convert a GapReport (incl. nested Findings) to a JSON-safe dict."""
902
+ return asdict(report)
903
+
904
+
905
+ def run_semantic_review(project_dir: Path, complexity: int = 3) -> Dict[str, Any]:
906
+ """Public entry point used by ``scripts/platform/guard_pipeline.py``.
907
+
908
+ Thin wrapper that runs ``review_project`` on a directory and returns the
909
+ JSON-safe dict ``GapReport`` representation. Returns the dict shape the
910
+ guard pipeline expects — keys: ``verdict``, ``findings`` (list of dicts
911
+ with ``message`` / ``rule_id`` / ``file`` / ``line``), ``summary``.
912
+
913
+ Fail-open responsibility lives in the caller — if the project layout
914
+ has no clarify-phase spec files (the common case for the wicked-garden
915
+ repo itself), ``review_project`` returns a vacuously-APPROVE report
916
+ with zero findings, which is exactly what the guard pipeline needs to
917
+ silently no-op.
918
+ """
919
+ report = review_project(project_dir=project_dir, complexity=complexity)
920
+ return report_to_dict(report)
921
+
922
+
923
+ # ---------------------------------------------------------------------------
924
+ # CLI
925
+ # ---------------------------------------------------------------------------
926
+
927
+ def _build_parser() -> argparse.ArgumentParser:
928
+ parser = argparse.ArgumentParser(
929
+ prog="semantic_review.py",
930
+ description="Semantic reviewer — spec-to-code alignment (issue #444).",
931
+ )
932
+ sub = parser.add_subparsers(dest="command", required=True)
933
+
934
+ p_review = sub.add_parser("review", help="Run full semantic review")
935
+ p_review.add_argument("--project-dir", required=True)
936
+ p_review.add_argument("--project-name", default="")
937
+ p_review.add_argument("--complexity", type=int, default=0)
938
+ p_review.add_argument("--ac-file", default=None)
939
+ p_review.add_argument("--objective-file", default=None)
940
+ p_review.add_argument("--impl-dir", default=None)
941
+ p_review.add_argument("--test-dir", default=None)
942
+ p_review.add_argument("--output", default=None, help="Write JSON report to this path")
943
+
944
+ p_constraints = sub.add_parser("constraints", help="Extract ADR constraints (MVP)")
945
+ p_constraints.add_argument("--adr-dir", required=True)
946
+ p_constraints.add_argument("--output", default=None)
947
+
948
+ return parser
949
+
950
+
951
+ def _cli_review(args: argparse.Namespace) -> int:
952
+ project_dir = Path(args.project_dir).resolve()
953
+ if not project_dir.is_dir():
954
+ print(f"error: project-dir not found: {project_dir}", file=sys.stderr)
955
+ return 2
956
+
957
+ report = review_project(
958
+ project_dir=project_dir,
959
+ project_name=args.project_name,
960
+ complexity=args.complexity,
961
+ ac_file=Path(args.ac_file).resolve() if args.ac_file else None,
962
+ objective_file=Path(args.objective_file).resolve() if args.objective_file else None,
963
+ impl_dir=Path(args.impl_dir).resolve() if args.impl_dir else None,
964
+ test_dir=Path(args.test_dir).resolve() if args.test_dir else None,
965
+ )
966
+ payload = report_to_dict(report)
967
+ data = json.dumps(payload, indent=2)
968
+ if args.output:
969
+ out_path = Path(args.output).resolve()
970
+ out_path.parent.mkdir(parents=True, exist_ok=True)
971
+ out_path.write_text(data)
972
+ else:
973
+ sys.stdout.write(data + "\n")
974
+ # Non-zero exit only when verdict is REJECT — CONDITIONAL is advisory.
975
+ if report.verdict == _VERDICT_REJECT:
976
+ return 1
977
+ return 0
978
+
979
+
980
+ def _cli_constraints(args: argparse.Namespace) -> int:
981
+ adr_dir = Path(args.adr_dir).resolve()
982
+ constraints = extract_adr_constraints(adr_dir)
983
+ data = json.dumps({"constraints": constraints, "count": len(constraints)}, indent=2)
984
+ if args.output:
985
+ out_path = Path(args.output).resolve()
986
+ out_path.parent.mkdir(parents=True, exist_ok=True)
987
+ out_path.write_text(data)
988
+ else:
989
+ sys.stdout.write(data + "\n")
990
+ return 0
991
+
992
+
993
+ def main(argv: Optional[Sequence[str]] = None) -> int:
994
+ parser = _build_parser()
995
+ args = parser.parse_args(argv)
996
+ if args.command == "review":
997
+ return _cli_review(args)
998
+ if args.command == "constraints":
999
+ return _cli_constraints(args)
1000
+ parser.error(f"unknown command: {args.command}")
1001
+ return 2
1002
+
1003
+
1004
+ if __name__ == "__main__":
1005
+ raise SystemExit(main())