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,1081 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ _bus.py — wicked-bus integration shim for wicked-garden.
4
+
5
+ Fire-and-forget event emission + poll-on-invoke consumption.
6
+ Bus unavailable = no-op. Never blocks the caller. Never raises.
7
+
8
+ Usage:
9
+ from _bus import emit_event, poll_pending, BUS_EVENT_MAP
10
+
11
+ # Emit (fire-and-forget, returns immediately)
12
+ emit_event("wicked.phase.transitioned", {
13
+ "project_id": "abc", "phase_from": "clarify", "phase_to": "design",
14
+ }, chain_id="abc123.root")
15
+
16
+ # Poll (returns list of events, acks after processing)
17
+ events = poll_pending(event_type_prefix="wicked.gate.")
18
+ """
19
+
20
+ import json
21
+ import logging
22
+ import os
23
+ import shutil
24
+ import subprocess
25
+ import threading
26
+ import time
27
+ from typing import Any, Dict, List, Optional
28
+
29
+ logger = logging.getLogger("wicked-garden.bus")
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Static event catalog — Phase 1 events only (those with wired consumers).
33
+ # New events are added here when their emit points + consumers ship.
34
+ # ---------------------------------------------------------------------------
35
+
36
+ BUS_EVENT_MAP: Dict[str, Dict[str, str]] = {
37
+ # Crew domain — phase_manager.py + propose-process facilitator skill
38
+ "wicked.project.created": {
39
+ "domain": "wicked-garden",
40
+ "subdomain": "crew.project",
41
+ "description": "New crew project created with complexity scoring",
42
+ },
43
+ "wicked.project.completed": {
44
+ "domain": "wicked-garden",
45
+ "subdomain": "crew.project",
46
+ "description": "Crew project completed (final phase approved)",
47
+ },
48
+ # v11.1.1 archetype lifecycle events. The v11.0.0/v11.1.0 phase_manager
49
+ # rewrote the project state but didn't register any v11 event types,
50
+ # so emit_event(...) was returning early on Unknown event type.
51
+ # These four events let v11 archetype-mode projects flow through the
52
+ # bus exactly the way v6 universal-pipeline projects did, without
53
+ # the v6 gate ceremony.
54
+ "wicked.archetype.created": {
55
+ "domain": "wicked-garden",
56
+ "subdomain": "crew.archetype",
57
+ "description": "v11 archetype-mode project created (carries v11_archetype + initial phase_plan)",
58
+ },
59
+ "wicked.archetype.advanced": {
60
+ "domain": "wicked-garden",
61
+ "subdomain": "crew.archetype",
62
+ "description": "v11 archetype phase approved + (when present) next phase named",
63
+ },
64
+ "wicked.archetype.completed": {
65
+ "domain": "wicked-garden",
66
+ "subdomain": "crew.archetype",
67
+ "description": "v11 archetype final phase approved (project is_complete)",
68
+ },
69
+ "wicked.archetype.hard_gate_passed": {
70
+ "domain": "wicked-garden",
71
+ "subdomain": "crew.archetype",
72
+ "description": "v11 archetype hard gate (cutover/mitigate/etc.) passed with confirmed_by + evidence",
73
+ },
74
+ "wicked.archetype.classified": {
75
+ "domain": "wicked-garden",
76
+ "subdomain": "crew.classify",
77
+ "description": "v11 prompt classified into work-shape archetype set (LLM or regex tier)",
78
+ },
79
+ # modernize archetype — stack_registry.py dispatch. Emitted when the
80
+ # `modernize` discover phase resolves a legacy stack class whose status
81
+ # is planned/none, OR an unknown stack. The reader NEVER fabricates a
82
+ # migration for these — it emits this gap event + returns a gap-task dict
83
+ # for the caller to hand to TaskCreate, then STOPS (fail-closed ETHOS).
84
+ "wicked.modernize.stack_gap": {
85
+ "domain": "wicked-garden",
86
+ "subdomain": "crew.modernize",
87
+ "description": "Legacy stack class is planned/none/unknown — capability-gap task emitted instead of a fabricated migration",
88
+ },
89
+ "wicked.project.complexity_scored": {
90
+ "domain": "wicked-garden",
91
+ "subdomain": "crew.scoring",
92
+ "description": "Complexity score computed for a project",
93
+ },
94
+ "wicked.phase.transitioned": {
95
+ "domain": "wicked-garden",
96
+ "subdomain": "crew.phase",
97
+ "description": "Phase approved and advanced to next",
98
+ },
99
+ "wicked.gate.decided": {
100
+ "domain": "wicked-garden",
101
+ "subdomain": "crew.gate",
102
+ "description": "Gate returned APPROVE, CONDITIONAL, or REJECT",
103
+ },
104
+ "wicked.gate.blocked": {
105
+ "domain": "wicked-garden",
106
+ "subdomain": "crew.gate",
107
+ "description": "Gate returned REJECT — phase advancement blocked",
108
+ },
109
+ "wicked.rework.triggered": {
110
+ "domain": "wicked-garden",
111
+ "subdomain": "crew.rework",
112
+ "description": "Rework initiated after gate REJECT or CONDITIONAL",
113
+ },
114
+ # Crew domain — issue #717 (resolve skill: classify-don't-retry path).
115
+ # Emitted per accepted resolution by `crew:resolve --accept`. The verdict
116
+ # on gate-result.json is NEVER mutated by this event — the resolution is
117
+ # surfaced as a first-class bus event so downstream consumers can audit
118
+ # who accepted what without crawling sidecar files. Pairs with the
119
+ # "bus-as-truth" decision from #732.
120
+ "wicked.gate.condition.resolved": {
121
+ "domain": "wicked-garden",
122
+ "subdomain": "crew.condition",
123
+ "description": "Mechanical CONDITIONAL finding resolved via crew:resolve skill (verdict unchanged)",
124
+ },
125
+ # Site 5 of bus-cutover (#746): condition verification flip.
126
+ # Emitted from conditions_manifest.mark_cleared() BEFORE the disk
127
+ # writes (sidecar + manifest). The projector handler
128
+ # _condition_marked_cleared in daemon/projector.py replays the same
129
+ # atomic two-step write order: sidecar first, then manifest flip.
130
+ # chain_id MUST include condition_id for per-condition idempotency
131
+ # (see ``memory/bus-chain-id-must-include-uniqueness-segment-gotcha.md``).
132
+ "wicked.condition.marked_cleared": {
133
+ "domain": "wicked-garden",
134
+ "subdomain": "crew.condition",
135
+ "description": "Condition verification flipped to verified=True via mark_cleared() (Site 5 cutover)",
136
+ },
137
+ # Site W1 of bus-cutover wave-2 (#787): solo_mode inline-HITL
138
+ # evidence record. Emitted from solo_mode.dispatch_human_inline()
139
+ # BEFORE the disk write at phases/{phase}/inline-review-context.md.
140
+ # Solo-mode also fires wicked.gate.decided for the same gate which
141
+ # carries the verdict + conditions; this event is for the markdown
142
+ # evidence file specifically (separate artifact, separate event).
143
+ # chain_id format: {project}.{phase}.gate (one inline-review-context
144
+ # per gate per phase, no per-condition split).
145
+ "wicked.crew.inline_review_context_recorded": {
146
+ "domain": "wicked-garden",
147
+ "subdomain": "crew.solo_mode",
148
+ "description": "Inline-HITL gate review evidence recorded by solo_mode (Site W1 cutover)",
149
+ },
150
+ # Wave-2 Tranche A summary emits — these are AUDIT MARKERS, not file
151
+ # projection events. The corresponding scripts (adopt_legacy.py,
152
+ # migrate_qe_evaluator_name.py, log_retention.py) are EXEMPT from
153
+ # full bus-cutover per docs/v9/wave-2-cutover-plan.md §W2/W3/W4: the
154
+ # writes themselves are migration/maintenance side-effects (one-shot
155
+ # transformations or rotation) that don't fit the projector replay
156
+ # model. These summary emits give the audit log a marker so future
157
+ # forensics can identify projects that went through legacy adoption
158
+ # / qe-evaluator rename / log rotation. No projector handlers; no
159
+ # entries in _PROJECTION_RESOLVERS.
160
+ "wicked.crew.legacy_adopted": {
161
+ "domain": "wicked-garden",
162
+ "subdomain": "crew.migration",
163
+ "description": "Legacy beta.3 → v6.0 project migration applied via adopt_legacy.py (audit marker)",
164
+ },
165
+ "wicked.crew.qe_evaluator_migrated": {
166
+ "domain": "wicked-garden",
167
+ "subdomain": "crew.migration",
168
+ "description": "qe-evaluator → gate-adjudicator rename applied via migrate_qe_evaluator_name.py (audit marker)",
169
+ },
170
+ "wicked.log.rotated": {
171
+ "domain": "wicked-garden",
172
+ "subdomain": "platform.log_retention",
173
+ "description": "Log file rotated by log_retention.rotate_if_needed (audit marker)",
174
+ },
175
+ # Wave-2 Tranche B (#746): JSONL append-stream cutovers.
176
+ # Each event mirrors a JSONL append into a per-phase log file; the
177
+ # projector handler replays the append from raw_payload. Same shape
178
+ # as wave-1 Site 1 (dispatch-log) but for four additional logs.
179
+ "wicked.amendment.appended": {
180
+ "domain": "wicked-garden",
181
+ "subdomain": "crew.amendment",
182
+ "description": "Phase amendment appended to amendments.jsonl (Site W6 cutover)",
183
+ },
184
+ "wicked.reeval.addendum_appended": {
185
+ "domain": "wicked-garden",
186
+ "subdomain": "crew.reeval",
187
+ "description": "Re-eval addendum appended to per-phase + project-root JSONL logs (Site W7 cutover; dual-file projection)",
188
+ },
189
+ "wicked.convergence.transition_recorded": {
190
+ "domain": "wicked-garden",
191
+ "subdomain": "crew.convergence",
192
+ "description": "Convergence-log transition recorded for an artifact (Site W8 cutover)",
193
+ },
194
+ "wicked.review.semantic_gap_recorded": {
195
+ "domain": "wicked-garden",
196
+ "subdomain": "crew.review",
197
+ "description": "Semantic-gap report persisted at review phase (Site W10a cutover)",
198
+ },
199
+ # Wave-2 Tranche C (#746):
200
+ "wicked.hitl.decision_recorded": {
201
+ "domain": "wicked-garden",
202
+ "subdomain": "crew.hitl",
203
+ "description": "HITL pause-decision evidence recorded by hitl_judge.write_hitl_decision_evidence (Site W5 cutover)",
204
+ },
205
+ "wicked.subagent.engaged": {
206
+ "domain": "wicked-garden",
207
+ "subdomain": "crew.subagent",
208
+ "description": "Specialist subagent engagement recorded by subagent_lifecycle (Site W9b cutover)",
209
+ },
210
+ # Jam domain — jam.py
211
+ "wicked.session.started": {
212
+ "domain": "wicked-garden",
213
+ "subdomain": "jam.session",
214
+ "description": "Brainstorm or council session started",
215
+ },
216
+ "wicked.session.synthesized": {
217
+ "domain": "wicked-garden",
218
+ "subdomain": "jam.session",
219
+ "description": "Session synthesis completed",
220
+ },
221
+ "wicked.session.synthesis_ready": {
222
+ "domain": "wicked-garden",
223
+ "subdomain": "jam.session",
224
+ "description": "All expected Round 1 personas contributed or timeout elapsed — facilitator may synthesize",
225
+ },
226
+ "wicked.council.voted": {
227
+ "domain": "wicked-garden",
228
+ "subdomain": "jam.council",
229
+ "description": "Council evaluation completed with model votes",
230
+ },
231
+ "wicked.persona.contributed": {
232
+ "domain": "wicked-garden",
233
+ "subdomain": "jam.persona",
234
+ "description": "Persona contributed a perspective in a brainstorm round",
235
+ },
236
+ # QE domain
237
+ "wicked.scenario.run": {
238
+ "domain": "wicked-garden",
239
+ "subdomain": "qe.scenario",
240
+ "description": "Test scenario executed with pass/fail result",
241
+ },
242
+ "wicked.coverage.changed": {
243
+ "domain": "wicked-garden",
244
+ "subdomain": "qe.coverage",
245
+ "description": "Test coverage metrics changed",
246
+ },
247
+ # Platform domain
248
+ "wicked.security.finding_raised": {
249
+ "domain": "wicked-garden",
250
+ "subdomain": "platform.security",
251
+ "description": "Security review raised a finding",
252
+ },
253
+ "wicked.guard.findings": {
254
+ "domain": "wicked-garden",
255
+ "subdomain": "platform.guard",
256
+ "description": "Autonomous session-close guard pipeline surfaced findings (Issue #448)",
257
+ },
258
+ "wicked.compliance.passed": {
259
+ "domain": "wicked-garden",
260
+ "subdomain": "platform.compliance",
261
+ "description": "Compliance check passed for a framework",
262
+ },
263
+ "wicked.compliance.failed": {
264
+ "domain": "wicked-garden",
265
+ "subdomain": "platform.compliance",
266
+ "description": "Compliance check failed for a framework",
267
+ },
268
+ # Auto-advance audit event
269
+ "wicked.phase.auto_advanced": {
270
+ "domain": "wicked-garden",
271
+ "subdomain": "crew.phase",
272
+ "description": "Phase auto-advanced for low-complexity project (audit trail)",
273
+ },
274
+ # Yolo scope-increase revoke — emitted by _apply_scope_increase_revoke when
275
+ # an augment/re-tier-up mutation flips yolo_approved_by_user to False.
276
+ "wicked.crew.yolo_revoked": {
277
+ "domain": "wicked-garden",
278
+ "subdomain": "crew.yolo",
279
+ "description": "Yolo auto-approval revoked due to scope-increase mutation (audit + observability)",
280
+ },
281
+ # Smaht domain — fact_extractor.py → brain auto-memorize subscriber
282
+ "wicked.fact.extracted": {
283
+ "domain": "smaht",
284
+ "subdomain": "facts",
285
+ "description": "Structured fact extracted from conversation (consumed by wicked-brain auto-memorize)",
286
+ },
287
+ # wicked-testing integration — verdict events (#549, AC-25)
288
+ "wicked.verdict.recorded": {
289
+ "domain": "wicked-testing",
290
+ "subdomain": "gate.verdict",
291
+ "description": "wicked-testing reviewer recorded a gate verdict (PASS/FAIL/N-A/SKIP)",
292
+ },
293
+ # Delivery domain — telemetry.py + drift.py (Issue #443)
294
+ "wicked.quality.drift_detected": {
295
+ "domain": "wicked-garden",
296
+ "subdomain": "delivery.telemetry",
297
+ "description": "Cross-session quality metric drifted past baseline threshold (special-cause or >=15% drop)",
298
+ },
299
+ # Crew domain — Part C of #734 (bus-as-truth emit additions paired with
300
+ # the load-bearing artifact writes the resume projector + bus-emit lint
301
+ # need to track. See PR #735 audit for the silent-write inventory.)
302
+ "wicked.dispatch.log_entry_appended": {
303
+ "domain": "wicked-garden",
304
+ "subdomain": "crew.dispatch",
305
+ "description": "HMAC-signed dispatch-log.jsonl entry appended (orphan-check sentinel)",
306
+ },
307
+ "wicked.consensus.report_created": {
308
+ "domain": "wicked-garden",
309
+ "subdomain": "crew.consensus",
310
+ "description": "Consensus gate report written to consensus-report.json",
311
+ },
312
+ "wicked.consensus.evidence_recorded": {
313
+ "domain": "wicked-garden",
314
+ "subdomain": "crew.consensus",
315
+ "description": "Consensus rejection evidence written to consensus-evidence.json (audit trail)",
316
+ },
317
+ # Site 3 of bus-cutover (#746): reviewer-report.md write sites
318
+ # (hooks/scripts/post_tool.py lines 963, 970, 984).
319
+ # Two events, not three:
320
+ # gate_completed — emitted after each _write_reviewer_report call
321
+ # (both the append-to-existing and create-new branches)
322
+ # gate_pending — emitted after _write_pending_reviewer_report
323
+ # (failure path: no consensus_result available)
324
+ "wicked.consensus.gate_completed": {
325
+ "domain": "wicked-garden",
326
+ "subdomain": "crew.consensus",
327
+ "description": "Consensus gate verdict written to reviewer-report.md (append or create)",
328
+ },
329
+ "wicked.consensus.gate_pending": {
330
+ "domain": "wicked-garden",
331
+ "subdomain": "crew.consensus",
332
+ "description": "Pending consensus gate placeholder written to reviewer-report.md (evaluation failed)",
333
+ },
334
+ }
335
+
336
+ # Payload deny-list — these fields must NEVER appear in bus payloads.
337
+ # Enforced by _sanitize_payload() before emission.
338
+ _PAYLOAD_DENY_LIST = frozenset({
339
+ "content", "body", "diff", "patch", "raw_text", "thinking",
340
+ "memory_content", "file_content", "source_code", "prompt",
341
+ "password", "secret", "token", "api_key", "credential",
342
+ })
343
+
344
+ # Per-event-type allow-list overrides — explicit exceptions where a normally
345
+ # denied key is part of the event's contract. Event types listed here get their
346
+ # named fields passed through the deny-list unchanged. Keep this list short and
347
+ # justified: every entry is a deliberate carve-out auditable at review time.
348
+ #
349
+ # wicked.fact.extracted — content is the whole point of the event. The brain
350
+ # auto-memorize subscriber requires payload.content to produce a memory.
351
+ # wicked.dispatch.log_entry_appended — `raw_payload` is the canonical JSONL
352
+ # bytes the projector replays into the `dispatch_log_entries` table under
353
+ # Site 1 of the bus-cutover (#746). Without it the projector cannot
354
+ # reproduce the on-disk line. Audit note in the PR body: this carve-out
355
+ # only ships an already-on-disk dispatch record (HMAC-signed), so payload
356
+ # inspection here is bounded by what the orphan check already trusts.
357
+ _PAYLOAD_ALLOW_OVERRIDES: Dict[str, frozenset] = {
358
+ "wicked.fact.extracted": frozenset({"content"}),
359
+ "wicked.dispatch.log_entry_appended": frozenset({"raw_payload"}),
360
+ # Site 2 of bus-cutover (#746): the consensus report and evidence emits
361
+ # carry the canonical on-disk JSON bytes via `raw_payload` so the
362
+ # projector can reproduce the file byte-for-byte. Without the carve-out
363
+ # the deny-list strips it as if it were generic file content. Council
364
+ # Condition C10 — `raw_payload` is REQUIRED for both emits.
365
+ "wicked.consensus.report_created": frozenset({"raw_payload"}),
366
+ "wicked.consensus.evidence_recorded": frozenset({"raw_payload"}),
367
+ # Site 3 of bus-cutover (#746): reviewer-report.md emits also ship
368
+ # raw_payload so the projector can reproduce the file byte-for-byte.
369
+ "wicked.consensus.gate_completed": frozenset({"raw_payload"}),
370
+ "wicked.consensus.gate_pending": frozenset({"raw_payload"}),
371
+ # Wave-2 Tranche B (#746): JSONL append-stream cutovers carry the
372
+ # canonical JSONL line via `raw_payload` so the projector can replay
373
+ # the append byte-for-byte. Same pattern as Site 1 dispatch-log.
374
+ # Each per-event audit note: the line bytes describe an append-only
375
+ # log entry (already validated by the source module's _validate_*
376
+ # call), so payload inspection is bounded by the schema check upstream.
377
+ "wicked.amendment.appended": frozenset({"raw_payload"}),
378
+ "wicked.reeval.addendum_appended": frozenset({"raw_payload"}),
379
+ "wicked.convergence.transition_recorded": frozenset({"raw_payload"}),
380
+ "wicked.review.semantic_gap_recorded": frozenset({"raw_payload"}),
381
+ # Wave-2 Tranche C (#746): hitl + subagent_engagement carry the
382
+ # canonical bytes via raw_payload so the projector can reproduce
383
+ # the file byte-for-byte. hitl additionally carries `filename`
384
+ # (caller-supplied evidence file name) — the projector validates it
385
+ # against a whitelist before writing to phases/{phase}/{filename}.
386
+ "wicked.hitl.decision_recorded": frozenset({"raw_payload", "filename"}),
387
+ "wicked.subagent.engaged": frozenset({"raw_payload"}),
388
+ }
389
+
390
+ # ---------------------------------------------------------------------------
391
+ # Emit health counters — module-level, protected by _emit_counter_lock.
392
+ #
393
+ # _EMIT_ATTEMPTED: incremented AFTER _check_available() + BUS_EVENT_MAP
394
+ # validation pass (bus-absent no-ops must NOT inflate the denominator).
395
+ # _EMIT_SUCCEEDED: incremented inside _fire() on returncode == 0.
396
+ #
397
+ # Public accessor: bus_emit_stats() — pure reader, no side effects.
398
+ # Test-teardown helper: _bus_reset_stats() — resets both to 0 atomically.
399
+ #
400
+ # Threshold for the 99.9% SLO lives in .claude-plugin/gate-policy.json
401
+ # under bus_health.emit_success_threshold — NOT as a constant here.
402
+ # See Site 3 cutover decision memory:
403
+ # site-3-threshold-lives-in-gate-policy-user-override-2026-05-02.md
404
+ # ---------------------------------------------------------------------------
405
+
406
+ _EMIT_ATTEMPTED: int = 0
407
+ _EMIT_SUCCEEDED: int = 0
408
+ _emit_counter_lock: threading.Lock = threading.Lock()
409
+
410
+ # ---------------------------------------------------------------------------
411
+ # Availability cache — 60s TTL, reset on failure
412
+ # ---------------------------------------------------------------------------
413
+
414
+ _bus_available: Optional[bool] = None
415
+ _bus_checked_at: float = 0.0
416
+ _bus_binary: Optional[str] = None
417
+ _CACHE_TTL_SECONDS = 60.0
418
+ _EMIT_TIMEOUT_SECONDS = 5.0
419
+
420
+ # Cursor ID for poll-on-invoke (set on first registration)
421
+ _cursor_id: Optional[str] = None
422
+ _CURSOR_FILE = os.path.join(
423
+ os.path.expanduser("~"),
424
+ ".something-wicked", "wicked-garden", "local",
425
+ "wicked-garden", "_bus_cursor.json",
426
+ )
427
+
428
+
429
+ def _is_disabled() -> bool:
430
+ """Check if bus is disabled via env var."""
431
+ return os.environ.get("WICKED_BUS_DISABLED", "").strip() in ("1", "true", "yes")
432
+
433
+
434
+ # ---------------------------------------------------------------------------
435
+ # Default-ON set for _bus_as_truth_enabled — shipped cutover sites whose
436
+ # flag falls through to the default map. All five planned sites (1-5) have
437
+ # shipped. Keep this frozenset in sync with PROJECTION_FILE_FLAGS in
438
+ # scripts/crew/reconcile_v2.py.
439
+ # ---------------------------------------------------------------------------
440
+
441
+ _BUS_AS_TRUTH_DEFAULT_ON: frozenset = frozenset({
442
+ "DISPATCH_LOG", # Site 1 — dispatch-log.jsonl (PR #751)
443
+ "CONSENSUS_REPORT", # Site 2 — consensus-report.json (PR #758)
444
+ "CONSENSUS_EVIDENCE", # Site 2 — consensus-evidence.json (PR #758)
445
+ "REVIEWER_REPORT", # Site 3 — reviewer-report.md (PR #776)
446
+ "GATE_RESULT", # Site 4 — gate-result.json (PR #782 + #784)
447
+ "CONDITIONS_MANIFEST", # Site 5 — conditions-manifest.json (PR #785)
448
+ "INLINE_REVIEW_CONTEXT", # Site W1 — inline-review-context.md (PR #788)
449
+ # Wave-2 Tranche B (PR #790):
450
+ "AMENDMENTS", # Site W6
451
+ "REEVAL_ADDENDUM", # Site W7
452
+ "CONVERGENCE", # Site W8
453
+ "SEMANTIC_GAP", # Site W10a
454
+ # Wave-2 Tranche C (this PR):
455
+ "HITL_DECISION", # Site W5 — phases/{phase}/hitl-*.json + council-*.json
456
+ "SUBAGENT_ENGAGEMENT", # Site W9b — phases/{phase}/specialist-engagement.jsonl
457
+ "SKIPPED_PHASE_STATUS", # Site W10b — phases/{phase}/status.md (SKIPPED only)
458
+ })
459
+
460
+
461
+ def _bus_as_truth_enabled(site: str = "DISPATCH_LOG") -> bool:
462
+ """Resolve flag state for ``site`` token.
463
+
464
+ Site 1 (#751) introduced this helper hardcoded to ``DISPATCH_LOG``.
465
+ Site 2 (#746) added the ``site`` parameter so multiple cutover handlers
466
+ can each gate on their own flag while sharing one helper. Default keeps
467
+ Site 1 callers working unchanged.
468
+
469
+ Reading the env var directly in projector handlers or emitters is
470
+ forbidden — every read MUST go through this helper so we have one place
471
+ to flip and one place to audit. Cutover Sites 3-5 pass their own site
472
+ name (``REVIEWER_REPORT``, ``GATE_RESULT``, ``CONDITIONS_MANIFEST``).
473
+
474
+ Resolution order (flag-fold PR #777):
475
+ 1. Explicit ``"on"`` (case/whitespace normalised) → True.
476
+ 2. Explicit ``"off"`` (case/whitespace normalised) → False.
477
+ Operator opt-out beats the default map — ``OFF``, ``Off``, `` off ``
478
+ all opt out correctly.
479
+ 3. Empty / any other value → default map.
480
+ Shipped sites (``_BUS_AS_TRUTH_DEFAULT_ON``) return True;
481
+ unshipped sites return False.
482
+
483
+ This resolves four findings from the dual-bot review (#777):
484
+ Finding #1 — non-"on"/"off" values now fall through to the default map
485
+ consistently for both shipped and unshipped sites.
486
+ Finding #3 — ``.strip().lower()`` normalises case + whitespace.
487
+ Finding #4 — asymmetric truthy-value handling eliminated; shipped and
488
+ unshipped sites both go through the same fall-through path.
489
+
490
+ Args:
491
+ site: Cutover site identifier (uppercase token). Composed into the
492
+ env var name as ``WG_BUS_AS_TRUTH_<site>``. Site 2 callers pass
493
+ ``"CONSENSUS_REPORT"`` and ``"CONSENSUS_EVIDENCE"``.
494
+ """
495
+ raw = os.environ.get(f"WG_BUS_AS_TRUTH_{site}", "").strip().lower()
496
+ if raw == "on":
497
+ return True
498
+ if raw == "off":
499
+ return False
500
+ return site in _BUS_AS_TRUTH_DEFAULT_ON
501
+
502
+
503
+ def _resolve_binary() -> Optional[str]:
504
+ """Resolve the wicked-bus binary path once, cache it."""
505
+ global _bus_binary
506
+ if _bus_binary is not None:
507
+ return _bus_binary
508
+
509
+ # Try direct binary first (faster than npx)
510
+ path = shutil.which("wicked-bus")
511
+ if path:
512
+ _bus_binary = path
513
+ return _bus_binary
514
+
515
+ # Fallback: verify npx can find it
516
+ try:
517
+ result = subprocess.run(
518
+ ["npx", "wicked-bus", "status", "--json"],
519
+ capture_output=True, text=True,
520
+ timeout=_EMIT_TIMEOUT_SECONDS,
521
+ )
522
+ if result.returncode == 0:
523
+ _bus_binary = "npx"
524
+ return _bus_binary
525
+ except Exception:
526
+ pass # fail open
527
+
528
+ return None
529
+
530
+
531
+ def _check_available() -> bool:
532
+ """Check bus availability with 60s TTL cache."""
533
+ global _bus_available, _bus_checked_at
534
+
535
+ now = time.monotonic()
536
+ if _bus_available is not None and (now - _bus_checked_at) < _CACHE_TTL_SECONDS:
537
+ return _bus_available
538
+
539
+ if _is_disabled():
540
+ _bus_available = False
541
+ _bus_checked_at = now
542
+ return False
543
+
544
+ binary = _resolve_binary()
545
+ if binary is None:
546
+ _bus_available = False
547
+ _bus_checked_at = now
548
+ return False
549
+
550
+ _bus_available = True
551
+ _bus_checked_at = now
552
+ return True
553
+
554
+
555
+ def _invalidate_cache() -> None:
556
+ """Reset availability cache on failure."""
557
+ global _bus_available, _bus_checked_at
558
+ _bus_available = None
559
+ _bus_checked_at = 0.0
560
+
561
+
562
+ def _build_cmd(*args: str) -> List[str]:
563
+ """Build command list using cached binary path."""
564
+ if _bus_binary == "npx":
565
+ return ["npx", "wicked-bus", *args]
566
+ elif _bus_binary:
567
+ return [_bus_binary, *args]
568
+ return ["npx", "wicked-bus", *args]
569
+
570
+
571
+ def _sanitize_payload(payload: Dict[str, Any], event_type: str = "") -> Dict[str, Any]:
572
+ """Remove denied fields from payload. Never send content to the bus.
573
+
574
+ Per-event allow overrides (see _PAYLOAD_ALLOW_OVERRIDES) let specific event
575
+ types pass named denied fields through — these are explicit, audited carve-outs
576
+ for events whose contract requires a denied key (e.g. wicked.fact.extracted
577
+ must ship content to the brain auto-memorize subscriber).
578
+ """
579
+ allow = _PAYLOAD_ALLOW_OVERRIDES.get(event_type, frozenset())
580
+ return {k: v for k, v in payload.items() if k not in _PAYLOAD_DENY_LIST or k in allow}
581
+
582
+
583
+ # ---------------------------------------------------------------------------
584
+ # Emit — fire-and-forget
585
+ # ---------------------------------------------------------------------------
586
+
587
+ def emit_event(
588
+ event_type: str,
589
+ payload: Dict[str, Any],
590
+ chain_id: Optional[str] = None,
591
+ metadata: Optional[Dict[str, Any]] = None,
592
+ ) -> None:
593
+ """Emit an event to wicked-bus. Fire-and-forget — returns immediately.
594
+
595
+ Args:
596
+ event_type: Must be a key in BUS_EVENT_MAP.
597
+ payload: Event data. Denied fields are stripped automatically.
598
+ chain_id: Crew causality chain ID. Added to metadata.
599
+ metadata: Additional operational context (not indexed, not filtered).
600
+ """
601
+ if not _check_available():
602
+ return
603
+
604
+ event_def = BUS_EVENT_MAP.get(event_type)
605
+ if event_def is None:
606
+ return # Unknown event type — fail silently
607
+
608
+ # Count attempts only AFTER availability + event-map validation pass.
609
+ # Bus-absent no-ops (early returns above) must NOT inflate the denominator.
610
+ global _EMIT_ATTEMPTED, _EMIT_SUCCEEDED
611
+ with _emit_counter_lock:
612
+ _EMIT_ATTEMPTED += 1
613
+
614
+ safe_payload = _sanitize_payload(payload, event_type)
615
+
616
+ cmd = _build_cmd(
617
+ "emit",
618
+ "--type", event_type,
619
+ "--domain", event_def["domain"],
620
+ "--subdomain", event_def["subdomain"],
621
+ "--payload", json.dumps(safe_payload, default=str),
622
+ "--json",
623
+ )
624
+
625
+ # Merge chain_id into metadata
626
+ meta = dict(metadata or {})
627
+ if chain_id:
628
+ meta["chain_id"] = chain_id
629
+
630
+ if meta:
631
+ cmd.extend(["--metadata", json.dumps(meta, default=str)])
632
+
633
+ def _fire() -> None:
634
+ global _EMIT_SUCCEEDED
635
+ try:
636
+ result = subprocess.run(
637
+ cmd,
638
+ timeout=_EMIT_TIMEOUT_SECONDS,
639
+ capture_output=True,
640
+ )
641
+ if result.returncode == 0:
642
+ with _emit_counter_lock:
643
+ _EMIT_SUCCEEDED += 1
644
+ except Exception:
645
+ _invalidate_cache()
646
+
647
+ threading.Thread(target=_fire, daemon=True).start()
648
+
649
+
650
+ def bus_emit_stats() -> Dict[str, Any]:
651
+ """Return current emit health counters as a plain dict.
652
+
653
+ Pure reader — no logging, no side effects. Callers (e.g.
654
+ wicked-garden:platform:plugin-health) are responsible for alerting
655
+ when ratio falls below the threshold defined in gate-policy.json
656
+ bus_health.emit_success_threshold.
657
+
658
+ Returns:
659
+ {
660
+ "attempted": int, -- emits past availability + BUS_EVENT_MAP check
661
+ "succeeded": int, -- returncode-0 subprocess completions
662
+ "ratio": float, -- succeeded / attempted; 0.0 when attempted == 0
663
+ }
664
+ """
665
+ with _emit_counter_lock:
666
+ attempted = _EMIT_ATTEMPTED
667
+ succeeded = _EMIT_SUCCEEDED
668
+ ratio = succeeded / attempted if attempted > 0 else 0.0
669
+ return {"attempted": attempted, "succeeded": succeeded, "ratio": ratio}
670
+
671
+
672
+ def _bus_reset_stats() -> None:
673
+ """Reset emit health counters to zero.
674
+
675
+ TEST-TEARDOWN HELPER ONLY — not for production use. Wired into
676
+ tests/conftest.py autouse fixture so counter isolation is automatic
677
+ across the suite. Resets both counters atomically under the lock.
678
+ """
679
+ global _EMIT_ATTEMPTED, _EMIT_SUCCEEDED
680
+ with _emit_counter_lock:
681
+ _EMIT_ATTEMPTED = 0
682
+ _EMIT_SUCCEEDED = 0
683
+
684
+
685
+ # ---------------------------------------------------------------------------
686
+ # Tail — read-only recent-event view (no cursor, no subscriber side effects)
687
+ # ---------------------------------------------------------------------------
688
+
689
+
690
+ def _resolve_bus_db_path() -> Optional[str]:
691
+ """Return the wicked-bus event-store DB path via ``status --json``.
692
+
693
+ Uses the same binary resolver as every other call in this module so the
694
+ path stays correct regardless of how wicked-bus was installed. Returns
695
+ None when the bus is unavailable or the status payload lacks ``db_path``.
696
+ Never raises.
697
+ """
698
+ if not _check_available():
699
+ return None
700
+ try:
701
+ result = subprocess.run(
702
+ _build_cmd("status", "--json"),
703
+ capture_output=True, text=True,
704
+ timeout=_EMIT_TIMEOUT_SECONDS,
705
+ )
706
+ if result.returncode != 0:
707
+ return None
708
+ data = json.loads(result.stdout)
709
+ except Exception:
710
+ _invalidate_cache()
711
+ return None
712
+ db_path = data.get("db_path")
713
+ return db_path if isinstance(db_path, str) and db_path else None
714
+
715
+
716
+ def tail_events(limit: int = 10) -> List[Dict[str, Any]]:
717
+ """Return the most recent ``limit`` bus events, newest first. Read-only.
718
+
719
+ This is a *display* helper (e.g. ``/wicked-garden:smaht:state``). It does
720
+ NOT register a subscriber, advance a cursor, or mutate the bus in any way:
721
+ the wicked-bus ``replay`` command resets a cursor rather than returning
722
+ rows, and ``list`` returns subscribers, so neither yields event rows
723
+ synchronously. Instead we resolve the event-store path via ``status``
724
+ (through the shared binary resolver) and read the ``events`` table in
725
+ read-only mode.
726
+
727
+ Each returned dict carries the canonical bus columns plus a ``data`` alias
728
+ for the parsed ``payload`` (consumers index ``event.get("data", {})``).
729
+
730
+ Fail-open: any error — bus absent, unreadable DB, malformed rows — returns
731
+ ``[]``. Never raises.
732
+
733
+ Args:
734
+ limit: Max number of recent events to return (clamped to >= 0).
735
+ """
736
+ if limit <= 0:
737
+ return []
738
+
739
+ db_path = _resolve_bus_db_path()
740
+ if not db_path or not os.path.exists(db_path):
741
+ return []
742
+
743
+ import sqlite3 # stdlib; imported lazily so the module loads without it
744
+
745
+ events: List[Dict[str, Any]] = []
746
+ conn = None
747
+ try:
748
+ # Read-only URI connection — never creates or writes the DB.
749
+ conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True, timeout=2.0)
750
+ conn.row_factory = sqlite3.Row
751
+ rows = conn.execute(
752
+ "SELECT event_id, event_type, domain, subdomain, payload, "
753
+ "metadata, emitted_at "
754
+ "FROM events ORDER BY event_id DESC LIMIT ?",
755
+ (int(limit),),
756
+ ).fetchall()
757
+ except Exception:
758
+ return [] # fail open — bus DB unreadable or schema mismatch
759
+ finally:
760
+ if conn is not None:
761
+ try:
762
+ conn.close()
763
+ except Exception:
764
+ pass
765
+
766
+ for row in rows:
767
+ try:
768
+ payload_raw = row["payload"]
769
+ payload = json.loads(payload_raw) if payload_raw else {}
770
+ if not isinstance(payload, dict):
771
+ payload = {"value": payload}
772
+ except (json.JSONDecodeError, TypeError):
773
+ payload = {}
774
+ try:
775
+ meta_raw = row["metadata"]
776
+ metadata = json.loads(meta_raw) if meta_raw else {}
777
+ if not isinstance(metadata, dict):
778
+ metadata = {}
779
+ except (json.JSONDecodeError, TypeError):
780
+ metadata = {}
781
+ events.append({
782
+ "event_id": row["event_id"],
783
+ "event_type": row["event_type"],
784
+ "domain": row["domain"],
785
+ "subdomain": row["subdomain"],
786
+ "payload": payload,
787
+ "data": payload, # alias — consumers read event.get("data", {})
788
+ "metadata": metadata,
789
+ "emitted_at": row["emitted_at"],
790
+ })
791
+ return events
792
+
793
+
794
+ # ---------------------------------------------------------------------------
795
+ # Poll — on-invoke consumption
796
+ # ---------------------------------------------------------------------------
797
+
798
+ def _load_cursor() -> Optional[str]:
799
+ """Load cursor ID from persistent storage."""
800
+ global _cursor_id
801
+ if _cursor_id:
802
+ return _cursor_id
803
+ try:
804
+ with open(_CURSOR_FILE, "r") as f:
805
+ data = json.load(f)
806
+ _cursor_id = data.get("cursor_id")
807
+ return _cursor_id
808
+ except (FileNotFoundError, json.JSONDecodeError, OSError):
809
+ return None
810
+
811
+
812
+ def _save_cursor(cursor_id: str) -> None:
813
+ """Persist cursor ID for cross-session continuity."""
814
+ global _cursor_id
815
+ _cursor_id = cursor_id
816
+ try:
817
+ os.makedirs(os.path.dirname(_CURSOR_FILE), exist_ok=True)
818
+ with open(_CURSOR_FILE, "w") as f:
819
+ json.dump({"cursor_id": cursor_id, "updated_at": time.time()}, f)
820
+ except OSError:
821
+ pass # fail open
822
+
823
+
824
+ def _ensure_registered() -> Optional[str]:
825
+ """Register wicked-garden as a bus subscriber if not already registered."""
826
+ cursor = _load_cursor()
827
+ if cursor:
828
+ return cursor
829
+
830
+ if not _check_available():
831
+ return None
832
+
833
+ try:
834
+ result = subprocess.run(
835
+ _build_cmd(
836
+ "register",
837
+ "--plugin", "wicked-garden",
838
+ "--role", "subscriber",
839
+ "--filter", "wicked.*",
840
+ "--json",
841
+ ),
842
+ capture_output=True, text=True,
843
+ timeout=_EMIT_TIMEOUT_SECONDS,
844
+ )
845
+ if result.returncode == 0:
846
+ data = json.loads(result.stdout)
847
+ cursor_id = data.get("cursor_id")
848
+ if cursor_id:
849
+ _save_cursor(cursor_id)
850
+ return cursor_id
851
+ except Exception:
852
+ _invalidate_cache()
853
+
854
+ return None
855
+
856
+
857
+ def poll_pending(
858
+ event_type_prefix: Optional[str] = None,
859
+ limit: int = 50,
860
+ ) -> List[Dict[str, Any]]:
861
+ """Poll for pending events since last ack. Returns list of event dicts.
862
+
863
+ Call at command startup (poll-on-invoke pattern). Returns empty list
864
+ if bus is unavailable or no events pending.
865
+
866
+ Args:
867
+ event_type_prefix: Filter events by type prefix (e.g., "wicked.gate.").
868
+ limit: Max events to return per poll.
869
+ """
870
+ if not _check_available():
871
+ return []
872
+
873
+ cursor = _ensure_registered()
874
+ if not cursor:
875
+ return []
876
+
877
+ try:
878
+ # Read cursor's last_event_id from registration
879
+ result = subprocess.run(
880
+ _build_cmd("list", "--json"),
881
+ capture_output=True, text=True,
882
+ timeout=_EMIT_TIMEOUT_SECONDS,
883
+ )
884
+ if result.returncode != 0:
885
+ return []
886
+
887
+ subs = json.loads(result.stdout)
888
+ last_event_id = 0
889
+ for sub in subs:
890
+ if sub.get("cursor_id") == cursor:
891
+ last_event_id = sub.get("last_event_id", 0)
892
+ break
893
+
894
+ # Replay from last acked position
895
+ replay_result = subprocess.run(
896
+ _build_cmd(
897
+ "replay",
898
+ "--cursor-id", cursor,
899
+ "--from-event-id", str(last_event_id),
900
+ "--json",
901
+ ),
902
+ capture_output=True, text=True,
903
+ timeout=_EMIT_TIMEOUT_SECONDS,
904
+ )
905
+
906
+ if replay_result.returncode != 0:
907
+ return []
908
+
909
+ data = json.loads(replay_result.stdout)
910
+ events = data.get("events", [])
911
+
912
+ # Filter by event_type prefix if specified
913
+ if event_type_prefix and events:
914
+ events = [e for e in events if e.get("event_type", "").startswith(event_type_prefix)]
915
+
916
+ return events[:limit]
917
+
918
+ except Exception:
919
+ _invalidate_cache()
920
+ return []
921
+
922
+
923
+ def ack_events(last_event_id: int) -> bool:
924
+ """Acknowledge events up to last_event_id. Advances the cursor.
925
+
926
+ Call after successfully processing events from poll_pending().
927
+ """
928
+ cursor = _load_cursor()
929
+ if not cursor or not _check_available():
930
+ return False
931
+
932
+ try:
933
+ result = subprocess.run(
934
+ _build_cmd(
935
+ "ack",
936
+ "--cursor-id", cursor,
937
+ "--last-event-id", str(last_event_id),
938
+ "--json",
939
+ ),
940
+ capture_output=True, text=True,
941
+ timeout=_EMIT_TIMEOUT_SECONDS,
942
+ )
943
+ return result.returncode == 0
944
+ except Exception:
945
+ _invalidate_cache()
946
+ return False
947
+
948
+
949
+ # ---------------------------------------------------------------------------
950
+ # Idempotency ledger — keyed on (event_type, chain_id)
951
+ # ---------------------------------------------------------------------------
952
+
953
+ _LEDGER_FILE = os.path.join(
954
+ os.path.expanduser("~"),
955
+ ".something-wicked", "wicked-garden", "local",
956
+ "wicked-garden", "_bus_processed.json",
957
+ )
958
+
959
+
960
+ def _load_ledger() -> Dict[str, float]:
961
+ """Load processed events ledger. Returns {key: timestamp}."""
962
+ try:
963
+ with open(_LEDGER_FILE, "r") as f:
964
+ return json.load(f)
965
+ except (FileNotFoundError, json.JSONDecodeError, OSError):
966
+ return {}
967
+
968
+
969
+ def _save_ledger(ledger: Dict[str, float]) -> None:
970
+ """Persist processed events ledger."""
971
+ try:
972
+ os.makedirs(os.path.dirname(_LEDGER_FILE), exist_ok=True)
973
+ with open(_LEDGER_FILE, "w") as f:
974
+ json.dump(ledger, f)
975
+ except OSError:
976
+ pass # fail open
977
+
978
+
979
+ def is_processed(event_type: str, chain_id: str) -> bool:
980
+ """Check if an event has already been processed."""
981
+ key = f"{event_type}:{chain_id}"
982
+ ledger = _load_ledger()
983
+ return key in ledger
984
+
985
+
986
+ def mark_processed(event_type: str, chain_id: str) -> None:
987
+ """Mark an event as processed in the idempotency ledger."""
988
+ key = f"{event_type}:{chain_id}"
989
+ ledger = _load_ledger()
990
+ ledger[key] = time.time()
991
+
992
+ # Prune entries older than 7 days to prevent unbounded growth
993
+ cutoff = time.time() - (7 * 86400)
994
+ ledger = {k: v for k, v in ledger.items() if v > cutoff}
995
+
996
+ _save_ledger(ledger)
997
+
998
+
999
+ # ---------------------------------------------------------------------------
1000
+ # Consumer registry — static manifest at scripts/_bus_consumers.json.
1001
+ # Enforces the max_consumers budget at load time. Does not reject duplicates
1002
+ # (idempotent), logs an error when the registered count exceeds the budget.
1003
+ # ---------------------------------------------------------------------------
1004
+
1005
+ _CONSUMERS_MANIFEST = os.path.join(
1006
+ os.path.dirname(os.path.abspath(__file__)),
1007
+ "_bus_consumers.json",
1008
+ )
1009
+
1010
+
1011
+ def load_consumer_registry(
1012
+ manifest_path: Optional[str] = None,
1013
+ ) -> Dict[str, Any]:
1014
+ """Load the bus consumer registry and validate it against max_consumers.
1015
+
1016
+ Returns a dict with keys:
1017
+ consumers: list of consumer entries (may be empty on error)
1018
+ max_consumers: int budget (defaults to 8 if absent)
1019
+ registered: int count of consumer entries
1020
+ over_budget: bool — True when registered > max_consumers
1021
+ error: optional str — set when the manifest is missing / malformed
1022
+
1023
+ Fail-open: never raises. A malformed or missing manifest logs an error
1024
+ and returns a registry dict with empty consumers so the bus still boots.
1025
+ Idempotent at load time — duplicate consumer ids are not rejected.
1026
+ """
1027
+ path = manifest_path or _CONSUMERS_MANIFEST
1028
+ result: Dict[str, Any] = {
1029
+ "consumers": [],
1030
+ "max_consumers": 8,
1031
+ "registered": 0,
1032
+ "over_budget": False,
1033
+ "error": None,
1034
+ }
1035
+ try:
1036
+ with open(path, "r") as f:
1037
+ data = json.load(f)
1038
+ except FileNotFoundError:
1039
+ result["error"] = f"consumer manifest not found: {path}"
1040
+ logger.debug(result["error"])
1041
+ return result
1042
+ except (json.JSONDecodeError, OSError) as exc:
1043
+ result["error"] = f"consumer manifest malformed: {exc}"
1044
+ logger.error("bus consumer registry: %s", result["error"])
1045
+ return result
1046
+
1047
+ if not isinstance(data, dict):
1048
+ result["error"] = "consumer manifest root is not an object"
1049
+ logger.error("bus consumer registry: %s", result["error"])
1050
+ return result
1051
+
1052
+ consumers = data.get("consumers", [])
1053
+ if not isinstance(consumers, list):
1054
+ result["error"] = "consumer manifest 'consumers' is not a list"
1055
+ logger.error("bus consumer registry: %s", result["error"])
1056
+ consumers = []
1057
+
1058
+ max_budget = data.get("max_consumers", 8)
1059
+ try:
1060
+ max_budget = int(max_budget)
1061
+ except (TypeError, ValueError):
1062
+ max_budget = 8
1063
+
1064
+ registered = len(consumers)
1065
+ over_budget = registered > max_budget
1066
+
1067
+ result.update({
1068
+ "consumers": consumers,
1069
+ "max_consumers": max_budget,
1070
+ "registered": registered,
1071
+ "over_budget": over_budget,
1072
+ })
1073
+
1074
+ if over_budget:
1075
+ logger.error(
1076
+ "bus consumer registry over budget: %d consumers registered, max %d. "
1077
+ "Review %s before adding more consumers.",
1078
+ registered, max_budget, path,
1079
+ )
1080
+
1081
+ return result