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,264 @@
1
+ #!/usr/bin/env python3
2
+ """scope_delta.py — Compute scope-delta metrics for the clarify HITL gate.
3
+
4
+ Issue #847: the facilitator's clarify HITL gate previously fired only on
5
+ intent ambiguity (the six triggers in ``skills/propose-process/refs/
6
+ ambiguity.md``). It missed the failure where the rubric silently
7
+ expanded a 24-issue wave plan to absorb a 1M-insertion sibling project
8
+ because no trigger looked at scope-size deltas.
9
+
10
+ This helper computes the four scope-delta metrics that drive the
11
+ seventh trigger added in #847:
12
+
13
+ - ``new_items``: items in the proposed plan not present in the
14
+ baseline plan (matched by id).
15
+ - ``oversize_factor``: ratio of the largest new item's size to the
16
+ median size of items already in the baseline plan. Trips the trigger
17
+ at >= 3.0.
18
+ - ``total_size_ratio``: total proposed-plan size / baseline-plan size.
19
+ Trips the trigger at >= 2.0.
20
+ - ``epic_or_project_added``: True iff any new item carries a label
21
+ matching the epic/project regex.
22
+
23
+ Stdlib-only; designed for the propose-process facilitator agent or
24
+ the slim caller to consume programmatically. Returns a dict; the
25
+ facilitator agent should surface the trigger as a clarifying question
26
+ when any threshold is crossed (per ambiguity.md trigger #7).
27
+
28
+ The "size" of an item is whatever the caller measures consistently —
29
+ LOC, file count, sub-task count, or a fused score. The helper does not
30
+ prescribe a unit; it just compares apples to apples.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import argparse
36
+ import json
37
+ import re
38
+ import sys
39
+ from typing import Any, Dict, Iterable, List, Optional
40
+
41
+
42
+ # Issue #847: items labeled with these patterns are project-sized by
43
+ # convention and must not be silently absorbed into a wave plan.
44
+ EPIC_LABEL_PATTERNS = (
45
+ re.compile(r"^epic$", re.IGNORECASE),
46
+ re.compile(r"^project$", re.IGNORECASE),
47
+ re.compile(r"^large$", re.IGNORECASE),
48
+ re.compile(r"^xlarge$", re.IGNORECASE),
49
+ re.compile(r"^xxl$", re.IGNORECASE),
50
+ )
51
+
52
+ # Threshold defaults — surfaced as constants so callers can override
53
+ # but the trigger doctrine in ambiguity.md stays in sync.
54
+ DEFAULT_OVERSIZE_FACTOR_TRIP = 3.0
55
+ DEFAULT_TOTAL_SIZE_RATIO_TRIP = 2.0
56
+
57
+
58
+ def _median(values: List[float]) -> float:
59
+ if not values:
60
+ return 0.0
61
+ sorted_values = sorted(values)
62
+ mid = len(sorted_values) // 2
63
+ if len(sorted_values) % 2 == 1:
64
+ return float(sorted_values[mid])
65
+ return (sorted_values[mid - 1] + sorted_values[mid]) / 2.0
66
+
67
+
68
+ def _has_epic_label(labels: Optional[Iterable[str]]) -> bool:
69
+ if not labels:
70
+ return False
71
+ for label in labels:
72
+ if not isinstance(label, str):
73
+ continue
74
+ for pattern in EPIC_LABEL_PATTERNS:
75
+ if pattern.match(label.strip()):
76
+ return True
77
+ return False
78
+
79
+
80
+ def _normalize_item(raw: Any) -> Dict[str, Any]:
81
+ """Coerce a raw item entry to the canonical shape.
82
+
83
+ Accepts either a dict ({"id", "size", "labels"}) or a bare string
84
+ (treated as id with size=1, no labels). Anything else is dropped.
85
+ """
86
+ if isinstance(raw, dict):
87
+ item_id = raw.get("id") or raw.get("name") or raw.get("title")
88
+ size = raw.get("size", 1)
89
+ labels = raw.get("labels", [])
90
+ if item_id is None:
91
+ return {}
92
+ try:
93
+ size = float(size)
94
+ except (TypeError, ValueError):
95
+ size = 1.0
96
+ # Labels normalization (Gemini PR #860 review): a bare string must
97
+ # become a single-element list, not be decomposed by list("epic")
98
+ # into ['e','p','i','c']. A non-iterable type (int, None, etc.)
99
+ # falls back to []. Only str/list/tuple yield meaningful labels.
100
+ if isinstance(labels, str):
101
+ labels_list = [labels]
102
+ elif isinstance(labels, (list, tuple)):
103
+ labels_list = list(labels)
104
+ else:
105
+ labels_list = []
106
+ return {"id": str(item_id), "size": size, "labels": labels_list}
107
+ if isinstance(raw, str):
108
+ return {"id": raw, "size": 1.0, "labels": []}
109
+ return {}
110
+
111
+
112
+ def compute_scope_delta(
113
+ baseline: List[Any],
114
+ proposed: List[Any],
115
+ *,
116
+ oversize_factor_trip: float = DEFAULT_OVERSIZE_FACTOR_TRIP,
117
+ total_size_ratio_trip: float = DEFAULT_TOTAL_SIZE_RATIO_TRIP,
118
+ ) -> Dict[str, Any]:
119
+ """Compute scope-delta metrics for the clarify HITL gate (#847).
120
+
121
+ Args:
122
+ baseline: List of items the user explicitly named in the
123
+ original ask. Each entry is a dict
124
+ ``{"id": str, "size": float, "labels": [str]}`` or a bare
125
+ string id.
126
+ proposed: List of items in the proposed plan, same shape.
127
+ oversize_factor_trip: Threshold at or above which the
128
+ oversize_factor trips the trigger (default 3.0).
129
+ total_size_ratio_trip: Threshold at or above which the
130
+ total_size_ratio trips the trigger (default 2.0).
131
+
132
+ Returns:
133
+ Dict with:
134
+ - ``new_items``: list of {id, size, labels} for items in
135
+ proposed but not baseline.
136
+ - ``baseline_median_size`` / ``baseline_total_size``.
137
+ - ``proposed_total_size``.
138
+ - ``oversize_factor``: max(new_item.size) / baseline_median_size,
139
+ or None when baseline_median_size is 0.
140
+ - ``total_size_ratio``: proposed_total / baseline_total, or
141
+ None when baseline_total is 0.
142
+ - ``epic_or_project_added``: True iff any new item has an
143
+ epic-class label.
144
+ - ``triggers``: ordered list of human-readable strings naming
145
+ which thresholds tripped. Empty list = no trigger fired.
146
+
147
+ The trigger fires whenever ``triggers`` is non-empty.
148
+ """
149
+ base = [it for it in (_normalize_item(x) for x in baseline) if it]
150
+ prop = [it for it in (_normalize_item(x) for x in proposed) if it]
151
+
152
+ base_ids = {it["id"] for it in base}
153
+ new_items = [it for it in prop if it["id"] not in base_ids]
154
+
155
+ baseline_sizes = [it["size"] for it in base]
156
+ baseline_median = _median(baseline_sizes)
157
+ baseline_total = sum(baseline_sizes)
158
+ proposed_total = sum(it["size"] for it in prop)
159
+
160
+ if new_items and baseline_median > 0:
161
+ max_new_size = max(it["size"] for it in new_items)
162
+ oversize_factor = max_new_size / baseline_median
163
+ else:
164
+ oversize_factor = None
165
+
166
+ if baseline_total > 0:
167
+ total_size_ratio = proposed_total / baseline_total
168
+ else:
169
+ total_size_ratio = None
170
+
171
+ epic_added = any(_has_epic_label(it.get("labels")) for it in new_items)
172
+
173
+ triggers: List[str] = []
174
+ if oversize_factor is not None and oversize_factor >= oversize_factor_trip:
175
+ triggers.append(
176
+ f"oversize-factor: largest new item is {oversize_factor:.1f}x "
177
+ f"the baseline median (trip threshold: {oversize_factor_trip}x)"
178
+ )
179
+ if total_size_ratio is not None and total_size_ratio >= total_size_ratio_trip:
180
+ triggers.append(
181
+ f"total-size-ratio: proposed plan is {total_size_ratio:.1f}x "
182
+ f"the baseline total size (trip threshold: {total_size_ratio_trip}x)"
183
+ )
184
+ if epic_added:
185
+ epic_ids = [
186
+ it["id"] for it in new_items if _has_epic_label(it.get("labels"))
187
+ ]
188
+ triggers.append(
189
+ f"epic-or-project-label: new item(s) {epic_ids} carry an "
190
+ "epic/project-class label and should be planned separately"
191
+ )
192
+
193
+ return {
194
+ "new_items": new_items,
195
+ "baseline_median_size": baseline_median,
196
+ "baseline_total_size": baseline_total,
197
+ "proposed_total_size": proposed_total,
198
+ "oversize_factor": oversize_factor,
199
+ "total_size_ratio": total_size_ratio,
200
+ "epic_or_project_added": epic_added,
201
+ "triggers": triggers,
202
+ }
203
+
204
+
205
+ def _cli() -> None:
206
+ parser = argparse.ArgumentParser(
207
+ description=(
208
+ "Compute scope-delta metrics for the clarify HITL gate "
209
+ "(Issue #847). Pass --baseline and --proposed as JSON file "
210
+ "paths or '-' to read from stdin."
211
+ )
212
+ )
213
+ parser.add_argument("--baseline", required=True,
214
+ help="Path to JSON file with baseline items, or '-' for stdin.")
215
+ parser.add_argument("--proposed", required=True,
216
+ help="Path to JSON file with proposed items, or '-' for stdin (mutually exclusive with --baseline -).")
217
+ parser.add_argument("--oversize-factor-trip", type=float,
218
+ default=DEFAULT_OVERSIZE_FACTOR_TRIP,
219
+ help=f"Override oversize-factor trip (default {DEFAULT_OVERSIZE_FACTOR_TRIP}).")
220
+ parser.add_argument("--total-size-ratio-trip", type=float,
221
+ default=DEFAULT_TOTAL_SIZE_RATIO_TRIP,
222
+ help=f"Override total-size-ratio trip (default {DEFAULT_TOTAL_SIZE_RATIO_TRIP}).")
223
+ parser.add_argument("--exit-nonzero-on-trip", action="store_true",
224
+ help="Exit 1 if any trigger fires.")
225
+ args = parser.parse_args()
226
+
227
+ def _load(path: str) -> List[Any]:
228
+ if path == "-":
229
+ return json.loads(sys.stdin.read())
230
+ with open(path, "r", encoding="utf-8") as fh:
231
+ return json.load(fh)
232
+
233
+ # Copilot PR #860 review: stdin can only be consumed once. Reject the
234
+ # ambiguous '--baseline - --proposed -' case explicitly so a second
235
+ # empty read doesn't surface as a confusing JSONDecodeError.
236
+ if args.baseline == "-" and args.proposed == "-":
237
+ print(
238
+ "Error: --baseline and --proposed cannot BOTH be '-'. "
239
+ "stdin can only be consumed once. Pass at most one of them as "
240
+ "'-' and the other as a file path.",
241
+ file=sys.stderr,
242
+ )
243
+ sys.exit(2)
244
+
245
+ baseline = _load(args.baseline)
246
+ proposed = _load(args.proposed)
247
+ if not isinstance(baseline, list) or not isinstance(proposed, list):
248
+ print("Error: baseline and proposed must each be a JSON array.",
249
+ file=sys.stderr)
250
+ sys.exit(2)
251
+
252
+ result = compute_scope_delta(
253
+ baseline, proposed,
254
+ oversize_factor_trip=args.oversize_factor_trip,
255
+ total_size_ratio_trip=args.total_size_ratio_trip,
256
+ )
257
+ json.dump(result, sys.stdout, indent=2, default=str)
258
+ sys.stdout.write("\n")
259
+ if args.exit_nonzero_on_trip and result["triggers"]:
260
+ sys.exit(1)
261
+
262
+
263
+ if __name__ == "__main__":
264
+ _cli()
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env python3
2
+ """Specialist name resolver for the wicked-garden plugin (Issue #573).
3
+
4
+ Three naming systems used to collide for the same concept:
5
+
6
+ 1. ``.claude-plugin/specialist.json`` lists the **domains**
7
+ (``engineering``, ``platform``, ``product``, ``qe``, ``data``,
8
+ ``jam``, ``agentic``).
9
+ 2. The facilitator rubric (``skills/propose-process/SKILL.md`` Step 4)
10
+ picks bare **role** names like ``requirements-analyst`` or
11
+ ``solution-architect``.
12
+ 3. Actual subagents are keyed ``wicked-garden:{domain}:{role}`` — for
13
+ example ``wicked-garden:product:requirements-analyst``.
14
+
15
+ This module is the single source of truth that bridges the three. It
16
+ walks ``agents/**/*.md``, parses the YAML-like frontmatter by simple
17
+ line scanning (stdlib only — hooks can import it), and returns lookup
18
+ maps keyed by both bare role and full subagent_type.
19
+
20
+ Public API:
21
+ build_resolver(plugin_root: Path) -> dict
22
+ resolve_role(role_name: str, resolver: dict) -> tuple[str, str] | tuple[None, None]
23
+
24
+ The builder is cached per ``plugin_root`` string so repeated calls from
25
+ hooks or scripts do not re-walk the agent tree.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import functools
31
+ import json
32
+ import re
33
+ from pathlib import Path
34
+ from typing import Optional, Tuple
35
+
36
+ # The canonical subagent_type prefix used throughout the plugin. Callers
37
+ # may pass either a bare role (``requirements-analyst``) or the full
38
+ # prefixed form — the resolver handles both.
39
+ _SUBAGENT_PREFIX = "wicked-garden:"
40
+
41
+ # Frontmatter fences: a leading ``---`` starts the YAML-like block and a
42
+ # trailing ``---`` closes it. We stop at the second fence so nothing in
43
+ # the agent body can accidentally masquerade as frontmatter.
44
+ _FRONTMATTER_FENCE = "---"
45
+
46
+ # Only these two keys are consumed. We deliberately do not pull in a YAML
47
+ # library — hooks are stdlib-only, and the frontmatter in this repo is
48
+ # always a flat, top-level ``key: value`` pattern for these two fields.
49
+ _RE_NAME = re.compile(r"^name:\s*(.+?)\s*$")
50
+ _RE_SUBAGENT_TYPE = re.compile(r"^subagent_type:\s*(.+?)\s*$")
51
+
52
+
53
+ def _parse_agent_frontmatter(path: Path) -> dict:
54
+ """Return ``{"name": ..., "subagent_type": ...}`` for an agent file.
55
+
56
+ Missing keys are omitted from the returned dict (callers treat that
57
+ as "skip this file"). Unreadable files return ``{}``.
58
+
59
+ This is NOT a general YAML parser. It scans line-by-line between the
60
+ first two ``---`` fences, matching only the two keys we care about.
61
+ """
62
+ try:
63
+ text = path.read_text(encoding="utf-8")
64
+ except OSError:
65
+ return {}
66
+
67
+ lines = text.splitlines()
68
+ if not lines or lines[0].strip() != _FRONTMATTER_FENCE:
69
+ return {}
70
+
71
+ result: dict = {}
72
+ for line in lines[1:]:
73
+ stripped = line.strip()
74
+ if stripped == _FRONTMATTER_FENCE:
75
+ break
76
+ name_match = _RE_NAME.match(line)
77
+ if name_match and "name" not in result:
78
+ result["name"] = name_match.group(1).strip()
79
+ continue
80
+ subagent_match = _RE_SUBAGENT_TYPE.match(line)
81
+ if subagent_match and "subagent_type" not in result:
82
+ result["subagent_type"] = subagent_match.group(1).strip()
83
+ continue
84
+ return result
85
+
86
+
87
+ def _load_domains(plugin_root: Path) -> list:
88
+ """Return the list of specialist-domain names from specialist.json.
89
+
90
+ Returns an empty list on any error so the resolver stays usable even
91
+ if the manifest is briefly malformed during development.
92
+ """
93
+ manifest = plugin_root / ".claude-plugin" / "specialist.json"
94
+ try:
95
+ data = json.loads(manifest.read_text(encoding="utf-8"))
96
+ except (OSError, json.JSONDecodeError):
97
+ return []
98
+ names = []
99
+ for entry in data.get("specialists", []):
100
+ if isinstance(entry, dict) and entry.get("name"):
101
+ names.append(entry["name"])
102
+ return names
103
+
104
+
105
+ @functools.lru_cache(maxsize=32)
106
+ def _build_resolver_cached(plugin_root_str: str) -> dict:
107
+ """Cached worker — keyed on the stringified plugin root."""
108
+ plugin_root = Path(plugin_root_str)
109
+ agents_dir = plugin_root / "agents"
110
+
111
+ role_to_subagent: dict = {}
112
+ role_to_domain: dict = {}
113
+ subagent_to_role: dict = {}
114
+ warnings: list = []
115
+
116
+ if agents_dir.is_dir():
117
+ # Sort for deterministic collision resolution: earlier (alphabetical
118
+ # by path) wins, later entries become warnings. ``rglob`` order is
119
+ # filesystem-dependent on some platforms, so we sort explicitly.
120
+ for agent_path in sorted(agents_dir.rglob("*.md")):
121
+ meta = _parse_agent_frontmatter(agent_path)
122
+ name = meta.get("name")
123
+ subagent_type = meta.get("subagent_type")
124
+ if not name or not subagent_type:
125
+ continue
126
+ # The domain is the middle segment of wicked-garden:{domain}:{role}
127
+ # — fall back to the parent directory name if the subagent_type
128
+ # is malformed (keeps lookup working even for drifted files).
129
+ if subagent_type.startswith(_SUBAGENT_PREFIX):
130
+ tail = subagent_type[len(_SUBAGENT_PREFIX):]
131
+ parts = tail.split(":", 1)
132
+ domain = parts[0] if len(parts) == 2 else agent_path.parent.name
133
+ else:
134
+ domain = agent_path.parent.name
135
+
136
+ if name in role_to_subagent:
137
+ existing = role_to_subagent[name]
138
+ if existing != subagent_type:
139
+ warnings.append(
140
+ f"role collision: '{name}' maps to both "
141
+ f"'{existing}' and '{subagent_type}' — keeping first"
142
+ )
143
+ # Do not overwrite — first match wins.
144
+ continue
145
+
146
+ role_to_subagent[name] = subagent_type
147
+ role_to_domain[name] = domain
148
+ subagent_to_role[subagent_type] = name
149
+
150
+ return {
151
+ "role_to_subagent": role_to_subagent,
152
+ "role_to_domain": role_to_domain,
153
+ "subagent_to_role": subagent_to_role,
154
+ "domains": _load_domains(plugin_root),
155
+ "warnings": warnings,
156
+ }
157
+
158
+
159
+ def build_resolver(plugin_root: Path) -> dict:
160
+ """Build the resolver by walking ``agents/**/*.md`` under ``plugin_root``.
161
+
162
+ Returns a dict with:
163
+
164
+ * ``role_to_subagent`` — bare role -> full ``wicked-garden:{domain}:{role}``
165
+ * ``role_to_domain`` — bare role -> domain (e.g. ``product``)
166
+ * ``subagent_to_role`` — full subagent_type -> bare role (reverse map)
167
+ * ``domains`` — list of specialist-domain names from ``specialist.json``
168
+ * ``warnings`` — list of human-readable strings for collisions
169
+
170
+ The result is cached per plugin_root string, so subsequent calls from
171
+ the same process are O(1).
172
+ """
173
+ return _build_resolver_cached(str(plugin_root))
174
+
175
+
176
+ def resolve_role(
177
+ role_name: str, resolver: dict
178
+ ) -> Tuple[Optional[str], Optional[str]]:
179
+ """Return ``(domain, subagent_type)`` for a role, or ``(None, None)``.
180
+
181
+ Accepts either a bare role (``requirements-analyst``) or an already-
182
+ qualified subagent_type (``wicked-garden:product:requirements-analyst``)
183
+ — idempotent for the latter.
184
+
185
+ Unknown roles return ``(None, None)`` without raising. Callers that
186
+ want close-match suggestions should drive ``difflib.get_close_matches``
187
+ off ``resolver["role_to_subagent"].keys()``.
188
+ """
189
+ if not role_name or not isinstance(role_name, str):
190
+ return None, None
191
+ role_name = role_name.strip()
192
+ if not role_name:
193
+ return None, None
194
+
195
+ # Full subagent_type path: look up the bare role via the reverse map
196
+ # and then use the forward maps for the domain. This keeps the return
197
+ # shape consistent regardless of which form the caller passed in.
198
+ if role_name.startswith(_SUBAGENT_PREFIX):
199
+ bare = resolver.get("subagent_to_role", {}).get(role_name)
200
+ if bare is None:
201
+ return None, None
202
+ domain = resolver.get("role_to_domain", {}).get(bare)
203
+ if domain is None:
204
+ return None, None
205
+ return domain, role_name
206
+
207
+ subagent_type = resolver.get("role_to_subagent", {}).get(role_name)
208
+ if subagent_type is None:
209
+ return None, None
210
+ domain = resolver.get("role_to_domain", {}).get(role_name)
211
+ if domain is None:
212
+ return None, None
213
+ return domain, subagent_type
214
+
215
+
216
+ def clear_cache() -> None:
217
+ """Drop the module-level cache. Useful for tests that mutate agents/."""
218
+ _build_resolver_cached.cache_clear()
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env python3
2
+ """stack_registry.py — legacy-stack-class -> dispatch reader for `modernize`.
3
+
4
+ The §B11/G2 seam: `.claude-plugin/stack-registry.json` is the machine-readable
5
+ dispatch truth (stack -> {blueprint, fixes, transform, validate, status}); this
6
+ module is the thin reader the `modernize` archetype's `discover` phase consults.
7
+
8
+ The load-bearing honesty contract (garden ETHOS — "never invent a pass"):
9
+ on an UNKNOWN stack, or a stack whose ``status`` is ``planned``/``none``, the
10
+ reader does NOT fabricate a transform. It returns a **capability-gap task** dict
11
+ (for the caller to hand to ``TaskCreate``) and fires a ``wicked.modernize.stack_gap``
12
+ bus event (fire-and-forget, fail-open). Only ``status: wired`` returns a runnable
13
+ dispatch. This mirrors the loom gate's fail-closed posture in dispatch data.
14
+
15
+ Pure resolution; the only I/O is reading the catalog + a fire-and-forget bus emit.
16
+
17
+ Usage (library):
18
+ from stack_registry import resolve_dispatch
19
+ r = resolve_dispatch("node-legacy-to-modern")
20
+ # r["status"] == "wired" -> r["dispatch"] = {blueprint, transform, validate}
21
+ # else -> r["gap_task"] = {title, body, ...}, dispatch is None
22
+
23
+ Usage (CLI):
24
+ sh "${CLAUDE_PLUGIN_ROOT}/scripts/_python.sh" \\
25
+ "${CLAUDE_PLUGIN_ROOT}/scripts/crew/stack_registry.py" \\
26
+ resolve --stack node-legacy-to-modern
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import argparse
32
+ import json
33
+ import sys
34
+ from pathlib import Path
35
+ from typing import Any, Dict, List, Optional
36
+
37
+ _THIS_DIR = Path(__file__).resolve().parent
38
+ _PLUGIN_ROOT = _THIS_DIR.parents[1]
39
+ _REGISTRY_PATH = _PLUGIN_ROOT / ".claude-plugin" / "stack-registry.json"
40
+
41
+ # The ONLY status that returns a runnable dispatch. Everything else —
42
+ # `planned`, `none`, a typo, or any novel/unknown status — fails closed to a
43
+ # capability-gap (the load-bearing honesty contract: dispatch is wired-only).
44
+ _WIRED = "wired"
45
+
46
+
47
+ def load_registry(path: Optional[Path] = None) -> Dict[str, Any]:
48
+ """Return the stack-registry catalog dict. Raises FileNotFoundError when
49
+ the registry isn't present — callers must not silently fall back."""
50
+ return json.loads((path or _REGISTRY_PATH).read_text(encoding="utf-8"))
51
+
52
+
53
+ def _gap_task(stack_id: str, label: str, status: str, reason: str) -> Dict[str, Any]:
54
+ """Build a capability-gap task dict for the caller to hand to TaskCreate.
55
+ This is what we emit INSTEAD of a fabricated migration."""
56
+ return {
57
+ "title": f"modernize: capability gap for stack '{stack_id}' (status={status})",
58
+ "body": (
59
+ f"{reason}\n\n"
60
+ f"The `modernize` archetype refuses to fabricate a transform for a "
61
+ f"stack it cannot actually handle. Wire a transform + validate for "
62
+ f"'{label}' in .claude-plugin/stack-registry.json (flip status to "
63
+ f"'wired'), or route this work to a stack that is already wired."
64
+ ),
65
+ "stack": stack_id,
66
+ "status": status,
67
+ "kind": "capability-gap",
68
+ }
69
+
70
+
71
+ def resolve_dispatch(
72
+ stack_id: str,
73
+ *,
74
+ registry: Optional[Dict[str, Any]] = None,
75
+ emit: bool = True,
76
+ ) -> Dict[str, Any]:
77
+ """Resolve a legacy stack class to a dispatch decision.
78
+
79
+ Returns a dict ``{stack, status, dispatch, gap_task}`` where exactly one of
80
+ ``dispatch`` / ``gap_task`` is non-None:
81
+ - status == "wired" -> dispatch = {blueprint, fixes, transform, validate}
82
+ - anything else (unknown /
83
+ planned / none / a typo /
84
+ any novel status) -> gap_task = {...}, dispatch = None, and a
85
+ ``wicked.modernize.stack_gap`` event is emitted (when ``emit``).
86
+
87
+ Dispatch is strictly **wired-only**: a stack whose status is anything other
88
+ than ``wired`` — including an unrecognised/misspelled status — fails closed
89
+ to a capability-gap rather than fabricating a migration.
90
+ """
91
+ reg = registry if registry is not None else load_registry()
92
+ stacks = reg.get("stacks", {})
93
+ entry = stacks.get(stack_id)
94
+
95
+ if entry is None:
96
+ gap = _gap_task(stack_id, stack_id, "unknown",
97
+ f"Stack '{stack_id}' is not in the registry.")
98
+ _emit_gap(gap, emit)
99
+ return {"stack": stack_id, "status": "unknown", "dispatch": None, "gap_task": gap}
100
+
101
+ status = entry.get("status") or "none"
102
+ if status != _WIRED:
103
+ # Fail closed: planned/none AND any unknown/typo'd status land here.
104
+ gap = _gap_task(stack_id, entry.get("label", stack_id), status,
105
+ f"Stack '{stack_id}' is registered as status={status} "
106
+ f"(only status='{_WIRED}' returns a runnable dispatch).")
107
+ _emit_gap(gap, emit)
108
+ return {"stack": stack_id, "status": status, "dispatch": None, "gap_task": gap}
109
+
110
+ # status == "wired": return the runnable dispatch. `or default` (not
111
+ # .get(k, default)) so an explicit JSON null coalesces to the empty
112
+ # container instead of crashing list(None) / passing None downstream.
113
+ return {
114
+ "stack": stack_id,
115
+ "status": status,
116
+ "dispatch": {
117
+ "blueprint": entry.get("blueprint"),
118
+ "fixes": list(entry.get("fixes") or []),
119
+ "transform": entry.get("transform") or {},
120
+ "validate": entry.get("validate") or {},
121
+ },
122
+ "gap_task": None,
123
+ }
124
+
125
+
126
+ def _emit_gap(gap_task: Dict[str, Any], emit: bool) -> None:
127
+ """Fire the stack-gap bus event. Fire-and-forget, fail-open — bus absent
128
+ must never block the dispatch decision (the gap_task is the source of truth)."""
129
+ if not emit:
130
+ return
131
+ try:
132
+ scripts_dir = str(_THIS_DIR.parent) # scripts/ for _bus
133
+ if scripts_dir not in sys.path: # guard: don't accumulate dups
134
+ sys.path.insert(0, scripts_dir)
135
+ from _bus import emit_event # type: ignore
136
+ emit_event(
137
+ "wicked.modernize.stack_gap",
138
+ {"stack": gap_task["stack"], "status": gap_task["status"],
139
+ "title": gap_task["title"]},
140
+ chain_id=f"modernize.{gap_task['stack']}.{gap_task['status']}.gap",
141
+ )
142
+ except Exception:
143
+ pass # bus is optional infrastructure
144
+
145
+
146
+ def list_stacks(registry: Optional[Dict[str, Any]] = None) -> List[Dict[str, str]]:
147
+ """Return [{id, label, status}, ...] for roadmap display."""
148
+ reg = registry if registry is not None else load_registry()
149
+ return [
150
+ {"id": sid, "label": s.get("label", sid), "status": s.get("status", "none")}
151
+ for sid, s in reg.get("stacks", {}).items()
152
+ ]
153
+
154
+
155
+ # ---------------------------------------------------------------------------
156
+ # CLI
157
+ # ---------------------------------------------------------------------------
158
+
159
+ def _cli() -> None:
160
+ parser = argparse.ArgumentParser(
161
+ description="modernize stack-registry dispatch reader (fail-closed)."
162
+ )
163
+ sub = parser.add_subparsers(dest="action", required=True)
164
+
165
+ resolve = sub.add_parser("resolve", help="Resolve a stack to a dispatch decision.")
166
+ resolve.add_argument("--stack", required=True)
167
+ resolve.add_argument("--no-emit", action="store_true",
168
+ help="Do not emit the stack-gap bus event (testing/dry-run).")
169
+
170
+ sub.add_parser("list", help="List registered stacks + statuses (roadmap).")
171
+
172
+ args = parser.parse_args()
173
+
174
+ if args.action == "resolve":
175
+ result = resolve_dispatch(args.stack, emit=not args.no_emit)
176
+ json.dump(result, sys.stdout, indent=2, default=str)
177
+ sys.stdout.write("\n")
178
+ # Exit 0 when wired (dispatch ready); exit 3 on a gap (fail-closed,
179
+ # matching prove.py's "backend down / no-pass" convention).
180
+ sys.exit(0 if result["dispatch"] is not None else 3)
181
+
182
+ if args.action == "list":
183
+ json.dump(list_stacks(), sys.stdout, indent=2, default=str)
184
+ sys.stdout.write("\n")
185
+ return
186
+
187
+
188
+ if __name__ == "__main__":
189
+ _cli()