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,915 @@
1
+ """
2
+ health_probe.py — CI-runnable health checker for wicked-garden plugins.
3
+
4
+ Validates all installed plugins against structural contracts:
5
+ 1. Hook event names (valid set; TaskCompleted flagged as WARNING)
6
+ 2. Script paths referenced in hooks.json commands (must exist)
7
+ 3. Cross-plugin subagent_type refs in commands/*.md and agents/*.md
8
+ 4. Ghost specialist checks (role, personas, enhances fields)
9
+ 5. plugin.json required fields (name, version, description)
10
+ 6. Bus consumer budget (scripts/_bus_consumers.json vs max_consumers)
11
+
12
+ Exit codes:
13
+ 0 healthy — no violations
14
+ 1 degraded — warnings only
15
+ 2 unhealthy — one or more errors
16
+
17
+ Output is persisted via DomainStore("wicked-observability").
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import argparse
23
+ import datetime
24
+ import json
25
+ import re
26
+ import sys
27
+ from pathlib import Path
28
+ from typing import Any
29
+
30
+ # Resolve _domain_store from the scripts/ directory.
31
+ # This file lives at scripts/platform/observability/, so scripts/ is parents[2].
32
+ sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
33
+ from _domain_store import DomainStore
34
+
35
+ _sm = DomainStore("wicked-observability")
36
+
37
+ # Repo root (the wicked-garden plugin itself). This file lives at
38
+ # scripts/platform/observability/, so the repo root is parents[3].
39
+ PLUGIN_ROOT = Path(__file__).resolve().parents[3]
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Constants
43
+ # ---------------------------------------------------------------------------
44
+
45
+ VALID_HOOK_EVENTS: frozenset[str] = frozenset(
46
+ [
47
+ "SessionStart",
48
+ "UserPromptSubmit",
49
+ "PreToolUse",
50
+ "PostToolUse",
51
+ "PostToolUseFailure",
52
+ "SubagentStart",
53
+ "SubagentStop",
54
+ "Stop",
55
+ "PreCompact",
56
+ "Notification",
57
+ "PermissionRequest",
58
+ "TeammateIdle",
59
+ "SessionEnd",
60
+ ]
61
+ )
62
+
63
+ # TaskCompleted is documented in some places but silently never fires.
64
+ SILENT_EVENTS: frozenset[str] = frozenset(["TaskCompleted"])
65
+
66
+ REQUIRED_PLUGIN_FIELDS: tuple[str, ...] = ("name", "version", "description")
67
+
68
+ REQUIRED_SPECIALIST_FIELDS: tuple[str, ...] = ("specialist", "enhances")
69
+ # Multi-specialist format: top-level "specialists" array, each entry has these
70
+ REQUIRED_MULTI_SPECIALIST_ENTRY_FIELDS: tuple[str, ...] = ("name", "role", "enhances")
71
+
72
+ # Pattern for subagent_type references to wicked-* plugins
73
+ # Matches: subagent_type="wicked-foo:agent-name"
74
+ # Does NOT match template placeholders like wicked-{specialist}:{agent}
75
+ SUBAGENT_REF_RE = re.compile(
76
+ r'subagent_type\s*=\s*["\']wicked-([a-z][a-z0-9-]*):([a-z][a-z0-9-]*)["\']'
77
+ )
78
+
79
+ # Pattern for extracting a script path from a hook command string.
80
+ # Handles: python3 "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/foo.py" --arg
81
+ # and: cd "${CLAUDE_PLUGIN_ROOT}" && uv run python scripts/foo.py
82
+ SCRIPT_PATH_RE = re.compile(
83
+ r'\$\{CLAUDE_PLUGIN_ROOT\}[/\\]([^\s"\']+\.py)'
84
+ )
85
+
86
+
87
+ # ---------------------------------------------------------------------------
88
+ # Violation dataclass (plain dict for stdlib compatibility)
89
+ # ---------------------------------------------------------------------------
90
+
91
+ def make_violation(
92
+ plugin: str,
93
+ vtype: str,
94
+ severity: str,
95
+ message: str,
96
+ file: str | None = None,
97
+ line: int | None = None,
98
+ ) -> dict[str, Any]:
99
+ return {
100
+ "plugin": plugin,
101
+ "type": vtype,
102
+ "severity": severity,
103
+ "message": message,
104
+ "file": file,
105
+ "line": line,
106
+ }
107
+
108
+
109
+ # ---------------------------------------------------------------------------
110
+ # Check 1: Hook event names
111
+ # ---------------------------------------------------------------------------
112
+
113
+ def check_hook_events(
114
+ plugin_name: str,
115
+ hooks_json: Path,
116
+ hooks_data: dict[str, Any],
117
+ ) -> list[dict[str, Any]]:
118
+ violations: list[dict[str, Any]] = []
119
+ hooks_block = hooks_data.get("hooks", {})
120
+
121
+ # Normalise: dict keyed by event name, or list of objects with "event" key
122
+ if isinstance(hooks_block, dict):
123
+ event_names = list(hooks_block.keys())
124
+ elif isinstance(hooks_block, list):
125
+ event_names = [
126
+ entry.get("event", "")
127
+ for entry in hooks_block
128
+ if isinstance(entry, dict) and "event" in entry
129
+ ]
130
+ else:
131
+ return violations
132
+
133
+ rel_file = _rel(hooks_json)
134
+ for event in event_names:
135
+ if not event:
136
+ continue
137
+ if event in VALID_HOOK_EVENTS:
138
+ continue
139
+ if event in SILENT_EVENTS:
140
+ violations.append(
141
+ make_violation(
142
+ plugin=plugin_name,
143
+ vtype="invalid_event",
144
+ severity="warning",
145
+ message=(
146
+ f"Hook event '{event}' is documented but silently never fires "
147
+ "in the Claude Code runtime. Scripts bound to it will not execute."
148
+ ),
149
+ file=rel_file,
150
+ )
151
+ )
152
+ else:
153
+ violations.append(
154
+ make_violation(
155
+ plugin=plugin_name,
156
+ vtype="invalid_event",
157
+ severity="error",
158
+ message=f"Unknown hook event '{event}'. Not in the valid event set.",
159
+ file=rel_file,
160
+ )
161
+ )
162
+
163
+ return violations
164
+
165
+
166
+ # ---------------------------------------------------------------------------
167
+ # Check 2: Script paths referenced in hooks
168
+ # ---------------------------------------------------------------------------
169
+
170
+ def _extract_script_paths(command_str: str) -> list[str]:
171
+ """Return relative paths (after CLAUDE_PLUGIN_ROOT) found in a command string."""
172
+ return SCRIPT_PATH_RE.findall(command_str)
173
+
174
+
175
+ def check_script_paths(
176
+ plugin_name: str,
177
+ plugin_root: Path,
178
+ hooks_json: Path,
179
+ hooks_data: dict[str, Any],
180
+ ) -> list[dict[str, Any]]:
181
+ violations: list[dict[str, Any]] = []
182
+ hooks_block = hooks_data.get("hooks", {})
183
+ rel_file = _rel(hooks_json)
184
+
185
+ # Gather all command strings regardless of format
186
+ command_strings: list[str] = []
187
+
188
+ if isinstance(hooks_block, dict):
189
+ for event_entries in hooks_block.values():
190
+ if not isinstance(event_entries, list):
191
+ continue
192
+ for matcher_block in event_entries:
193
+ if not isinstance(matcher_block, dict):
194
+ continue
195
+ inner_hooks = matcher_block.get("hooks", [])
196
+ for hook in inner_hooks:
197
+ cmd = hook.get("command", "")
198
+ if cmd:
199
+ command_strings.append(cmd)
200
+
201
+ elif isinstance(hooks_block, list):
202
+ for entry in hooks_block:
203
+ if isinstance(entry, dict):
204
+ cmd = entry.get("command", "")
205
+ if cmd:
206
+ command_strings.append(cmd)
207
+
208
+ for cmd in command_strings:
209
+ for rel_script in _extract_script_paths(cmd):
210
+ script_path = plugin_root / rel_script
211
+ if not script_path.exists():
212
+ violations.append(
213
+ make_violation(
214
+ plugin=plugin_name,
215
+ vtype="missing_script",
216
+ severity="error",
217
+ message=(
218
+ f"Script referenced in hook command does not exist: "
219
+ f"{rel_script}"
220
+ ),
221
+ file=rel_file,
222
+ )
223
+ )
224
+
225
+ return violations
226
+
227
+
228
+ # ---------------------------------------------------------------------------
229
+ # Check 3: Cross-plugin subagent_type references
230
+ # ---------------------------------------------------------------------------
231
+
232
+ def check_cross_plugin_refs(
233
+ plugin_name: str,
234
+ plugin_root: Path,
235
+ plugins_dir: Path,
236
+ ) -> list[dict[str, Any]]:
237
+ violations: list[dict[str, Any]] = []
238
+
239
+ md_files: list[Path] = []
240
+ for subdir in ("commands", "agents"):
241
+ target = plugin_root / subdir
242
+ if target.is_dir():
243
+ md_files.extend(target.glob("*.md"))
244
+
245
+ for md_file in md_files:
246
+ try:
247
+ content = md_file.read_text(encoding="utf-8", errors="replace")
248
+ except OSError:
249
+ continue
250
+
251
+ for match in SUBAGENT_REF_RE.finditer(content):
252
+ ref_plugin_name = f"wicked-{match.group(1)}"
253
+ ref_agent_name = match.group(2)
254
+
255
+ ref_plugin_dir = plugins_dir / ref_plugin_name
256
+ if not ref_plugin_dir.is_dir():
257
+ violations.append(
258
+ make_violation(
259
+ plugin=plugin_name,
260
+ vtype="ghost_reference",
261
+ severity="error",
262
+ message=(
263
+ f"References subagent_type=\"{ref_plugin_name}:{ref_agent_name}\" "
264
+ f"but plugin directory '{ref_plugin_name}' does not exist."
265
+ ),
266
+ file=_rel_from(md_file, plugin_root.parent.parent),
267
+ )
268
+ )
269
+ continue
270
+
271
+ # Plugin exists — check the agent file
272
+ agent_file = ref_plugin_dir / "agents" / f"{ref_agent_name}.md"
273
+ if not agent_file.exists():
274
+ violations.append(
275
+ make_violation(
276
+ plugin=plugin_name,
277
+ vtype="ghost_reference",
278
+ severity="error",
279
+ message=(
280
+ f"References subagent_type=\"{ref_plugin_name}:{ref_agent_name}\" "
281
+ f"but agent file 'agents/{ref_agent_name}.md' not found in {ref_plugin_name}."
282
+ ),
283
+ file=_rel_from(md_file, plugin_root.parent.parent),
284
+ )
285
+ )
286
+
287
+ return violations
288
+
289
+
290
+ # ---------------------------------------------------------------------------
291
+ # Check 4: Ghost specialist validation
292
+ # ---------------------------------------------------------------------------
293
+
294
+ def _check_single_specialist_entry(
295
+ plugin_name: str,
296
+ plugin_root: Path,
297
+ rel_file: str,
298
+ spec_data: dict[str, Any],
299
+ personas: list,
300
+ enhances: list,
301
+ label: str = "",
302
+ ) -> list[dict[str, Any]]:
303
+ """Validate one specialist entry (shared by single and multi formats)."""
304
+ violations: list[dict[str, Any]] = []
305
+ prefix = f"[{label}] " if label else ""
306
+
307
+ # Validate specialist has 'role'
308
+ if not isinstance(spec_data, dict) or not spec_data.get("role"):
309
+ violations.append(
310
+ make_violation(
311
+ plugin=plugin_name,
312
+ vtype="ghost_specialist",
313
+ severity="error",
314
+ message=f"{prefix}specialist block missing or has no 'role'.",
315
+ file=rel_file,
316
+ )
317
+ )
318
+
319
+ # Validate personas agent paths (if present — personas are optional in lean manifests)
320
+ if isinstance(personas, list) and personas:
321
+ for persona in personas:
322
+ if not isinstance(persona, dict):
323
+ continue
324
+ agent_rel = persona.get("agent", "")
325
+ if not agent_rel:
326
+ continue
327
+ agent_path = plugin_root / agent_rel
328
+ if not agent_path.exists():
329
+ violations.append(
330
+ make_violation(
331
+ plugin=plugin_name,
332
+ vtype="ghost_specialist",
333
+ severity="error",
334
+ message=(
335
+ f"{prefix}Persona '{persona.get('name', '?')}' references agent "
336
+ f"'{agent_rel}' which does not exist."
337
+ ),
338
+ file=rel_file,
339
+ )
340
+ )
341
+
342
+ # Validate enhances is a non-empty list
343
+ if not isinstance(enhances, list) or not enhances:
344
+ violations.append(
345
+ make_violation(
346
+ plugin=plugin_name,
347
+ vtype="ghost_specialist",
348
+ severity="error",
349
+ message=f"{prefix}'enhances' must be a non-empty list.",
350
+ file=rel_file,
351
+ )
352
+ )
353
+
354
+ return violations
355
+
356
+
357
+ def check_specialist(
358
+ plugin_name: str,
359
+ plugin_root: Path,
360
+ specialist_json: Path,
361
+ ) -> list[dict[str, Any]]:
362
+ violations: list[dict[str, Any]] = []
363
+ rel_file = _rel_from(specialist_json, plugin_root.parent.parent)
364
+
365
+ try:
366
+ data = json.loads(specialist_json.read_text(encoding="utf-8"))
367
+ except (OSError, json.JSONDecodeError) as exc:
368
+ return [
369
+ make_violation(
370
+ plugin=plugin_name,
371
+ vtype="ghost_specialist",
372
+ severity="error",
373
+ message=f"Could not parse specialist.json: {exc}",
374
+ file=rel_file,
375
+ )
376
+ ]
377
+
378
+ # Detect format: multi-specialist array vs. legacy single-specialist
379
+ if "specialists" in data and isinstance(data["specialists"], list):
380
+ # Multi-specialist format: {"specialists": [{name, role, personas, enhances}, ...]}
381
+ for entry in data["specialists"]:
382
+ if not isinstance(entry, dict):
383
+ violations.append(
384
+ make_violation(
385
+ plugin=plugin_name,
386
+ vtype="ghost_specialist",
387
+ severity="error",
388
+ message="specialist.json 'specialists' array contains non-object entry.",
389
+ file=rel_file,
390
+ )
391
+ )
392
+ continue
393
+ label = entry.get("name", "?")
394
+ for field in REQUIRED_MULTI_SPECIALIST_ENTRY_FIELDS:
395
+ if field not in entry:
396
+ violations.append(
397
+ make_violation(
398
+ plugin=plugin_name,
399
+ vtype="ghost_specialist",
400
+ severity="error",
401
+ message=f"[{label}] missing required field '{field}'.",
402
+ file=rel_file,
403
+ )
404
+ )
405
+ violations.extend(
406
+ _check_single_specialist_entry(
407
+ plugin_name, plugin_root, rel_file,
408
+ spec_data=entry,
409
+ personas=entry.get("personas", []),
410
+ enhances=entry.get("enhances", []),
411
+ label=label,
412
+ )
413
+ )
414
+ else:
415
+ # Legacy single-specialist format:
416
+ # {"specialist": {..., "role": "..."}, "personas": [], "enhances": []}
417
+ # or shorthand string form:
418
+ # {"specialist": "role-name", "personas": [...]}
419
+ for field in REQUIRED_SPECIALIST_FIELDS:
420
+ if field not in data:
421
+ violations.append(
422
+ make_violation(
423
+ plugin=plugin_name,
424
+ vtype="ghost_specialist",
425
+ severity="error",
426
+ message=f"specialist.json missing required field '{field}'.",
427
+ file=rel_file,
428
+ )
429
+ )
430
+ raw_specialist = data.get("specialist", {})
431
+ # Normalise string shorthand ("specialist": "engineering") into a dict
432
+ if isinstance(raw_specialist, str):
433
+ raw_specialist = {"role": raw_specialist}
434
+ violations.extend(
435
+ _check_single_specialist_entry(
436
+ plugin_name, plugin_root, rel_file,
437
+ spec_data=raw_specialist,
438
+ personas=data.get("personas", []),
439
+ enhances=data.get("enhances", []),
440
+ )
441
+ )
442
+
443
+ return violations
444
+
445
+
446
+ # ---------------------------------------------------------------------------
447
+ # Check 6: Bus consumer budget (scripts/_bus_consumers.json)
448
+ # ---------------------------------------------------------------------------
449
+
450
+ def check_bus_consumer_budget(
451
+ plugin_name: str,
452
+ plugin_root: Path,
453
+ ) -> list[dict[str, Any]]:
454
+ """Validate that registered bus consumers stay within the declared budget.
455
+
456
+ Reads plugin_root/scripts/_bus_consumers.json. Flags a warning when
457
+ registered > max_consumers. Silent (no violation) when the manifest is
458
+ absent — plugins without bus consumers are not required to ship one.
459
+ Fail-open: a malformed manifest is reported as a warning, not an error.
460
+ """
461
+ violations: list[dict[str, Any]] = []
462
+ manifest_path = plugin_root / "scripts" / "_bus_consumers.json"
463
+ if not manifest_path.exists():
464
+ return violations
465
+
466
+ rel_file = _rel_from(manifest_path, plugin_root.parent.parent)
467
+
468
+ try:
469
+ data = json.loads(manifest_path.read_text(encoding="utf-8"))
470
+ except (OSError, json.JSONDecodeError) as exc:
471
+ violations.append(
472
+ make_violation(
473
+ plugin=plugin_name,
474
+ vtype="bus_consumer_budget",
475
+ severity="warning",
476
+ message=f"Could not parse _bus_consumers.json: {exc}",
477
+ file=rel_file,
478
+ )
479
+ )
480
+ return violations
481
+
482
+ if not isinstance(data, dict):
483
+ violations.append(
484
+ make_violation(
485
+ plugin=plugin_name,
486
+ vtype="bus_consumer_budget",
487
+ severity="warning",
488
+ message="_bus_consumers.json root is not an object.",
489
+ file=rel_file,
490
+ )
491
+ )
492
+ return violations
493
+
494
+ consumers = data.get("consumers", [])
495
+ if not isinstance(consumers, list):
496
+ violations.append(
497
+ make_violation(
498
+ plugin=plugin_name,
499
+ vtype="bus_consumer_budget",
500
+ severity="warning",
501
+ message="_bus_consumers.json 'consumers' is not a list.",
502
+ file=rel_file,
503
+ )
504
+ )
505
+ return violations
506
+
507
+ max_budget_raw = data.get("max_consumers", 8)
508
+ try:
509
+ max_budget = int(max_budget_raw)
510
+ except (TypeError, ValueError):
511
+ max_budget = 8
512
+
513
+ registered = len(consumers)
514
+ if registered > max_budget:
515
+ violations.append(
516
+ make_violation(
517
+ plugin=plugin_name,
518
+ vtype="bus_consumer_budget",
519
+ severity="warning",
520
+ message=(
521
+ f"Bus consumer budget exceeded: {registered} consumers "
522
+ f"registered, max {max_budget}. Review entries before "
523
+ f"adding more."
524
+ ),
525
+ file=rel_file,
526
+ )
527
+ )
528
+
529
+ return violations
530
+
531
+
532
+ # ---------------------------------------------------------------------------
533
+ # Check 5: plugin.json required fields
534
+ # ---------------------------------------------------------------------------
535
+
536
+ def check_plugin_json(
537
+ plugin_name: str,
538
+ plugin_root: Path,
539
+ ) -> list[dict[str, Any]]:
540
+ violations: list[dict[str, Any]] = []
541
+
542
+ plugin_json_path = plugin_root / ".claude-plugin" / "plugin.json"
543
+ if not plugin_json_path.exists():
544
+ return [
545
+ make_violation(
546
+ plugin=plugin_name,
547
+ vtype="missing_field",
548
+ severity="error",
549
+ message=".claude-plugin/plugin.json not found.",
550
+ file=".claude-plugin/plugin.json",
551
+ )
552
+ ]
553
+
554
+ rel_file = _rel_from(plugin_json_path, plugin_root.parent.parent)
555
+
556
+ try:
557
+ data = json.loads(plugin_json_path.read_text(encoding="utf-8"))
558
+ except (OSError, json.JSONDecodeError) as exc:
559
+ return [
560
+ make_violation(
561
+ plugin=plugin_name,
562
+ vtype="missing_field",
563
+ severity="error",
564
+ message=f"Could not parse plugin.json: {exc}",
565
+ file=rel_file,
566
+ )
567
+ ]
568
+
569
+ for field in REQUIRED_PLUGIN_FIELDS:
570
+ if field not in data or not data[field]:
571
+ violations.append(
572
+ make_violation(
573
+ plugin=plugin_name,
574
+ vtype="missing_field",
575
+ severity="error",
576
+ message=f"plugin.json missing required field '{field}'.",
577
+ file=rel_file,
578
+ )
579
+ )
580
+
581
+ return violations
582
+
583
+
584
+ # ---------------------------------------------------------------------------
585
+ # Per-plugin orchestration
586
+ # ---------------------------------------------------------------------------
587
+
588
+ def probe_plugin(plugin_root: Path, plugins_dir: Path) -> list[dict[str, Any]]:
589
+ plugin_name = plugin_root.name
590
+ violations: list[dict[str, Any]] = []
591
+
592
+ # Check 5 first (plugin.json) — always run regardless of other structure
593
+ violations.extend(check_plugin_json(plugin_name, plugin_root))
594
+
595
+ # Check 1 & 2: hooks
596
+ hooks_json_path = plugin_root / "hooks" / "hooks.json"
597
+ if hooks_json_path.exists():
598
+ try:
599
+ hooks_data = json.loads(hooks_json_path.read_text(encoding="utf-8"))
600
+ except (OSError, json.JSONDecodeError) as exc:
601
+ violations.append(
602
+ make_violation(
603
+ plugin=plugin_name,
604
+ vtype="missing_field",
605
+ severity="error",
606
+ message=f"Could not parse hooks/hooks.json: {exc}",
607
+ file="hooks/hooks.json",
608
+ )
609
+ )
610
+ hooks_data = None
611
+
612
+ if hooks_data is not None:
613
+ violations.extend(
614
+ check_hook_events(plugin_name, hooks_json_path, hooks_data)
615
+ )
616
+ violations.extend(
617
+ check_script_paths(plugin_name, plugin_root, hooks_json_path, hooks_data)
618
+ )
619
+
620
+ # Check 3: cross-plugin refs
621
+ violations.extend(
622
+ check_cross_plugin_refs(plugin_name, plugin_root, plugins_dir)
623
+ )
624
+
625
+ # Check 4: specialist
626
+ specialist_json_path = plugin_root / ".claude-plugin" / "specialist.json"
627
+ if specialist_json_path.exists():
628
+ violations.extend(
629
+ check_specialist(plugin_name, plugin_root, specialist_json_path)
630
+ )
631
+
632
+ # Check 6: bus consumer budget (scripts/_bus_consumers.json)
633
+ violations.extend(
634
+ check_bus_consumer_budget(plugin_name, plugin_root)
635
+ )
636
+
637
+ return violations
638
+
639
+
640
+ # ---------------------------------------------------------------------------
641
+ # Result assembly and persistence
642
+ # ---------------------------------------------------------------------------
643
+
644
+ def _determine_plugin_status(plugin_violations: list[dict[str, Any]]) -> str:
645
+ severities = {v["severity"] for v in plugin_violations}
646
+ if "error" in severities:
647
+ return "unhealthy"
648
+ if "warning" in severities:
649
+ return "degraded"
650
+ return "healthy"
651
+
652
+
653
+ def _determine_overall_status(violations: list[dict[str, Any]]) -> str:
654
+ severities = {v["severity"] for v in violations}
655
+ if "error" in severities:
656
+ return "unhealthy"
657
+ if "warning" in severities:
658
+ return "degraded"
659
+ return "healthy"
660
+
661
+
662
+ def build_report(
663
+ all_violations: list[dict[str, Any]],
664
+ plugins_checked: int,
665
+ per_plugin_violations: dict[str, list[dict[str, Any]]],
666
+ ) -> dict[str, Any]:
667
+ errors = sum(1 for v in all_violations if v["severity"] == "error")
668
+ warnings = sum(1 for v in all_violations if v["severity"] == "warning")
669
+
670
+ healthy = degraded = unhealthy = 0
671
+ for violations in per_plugin_violations.values():
672
+ status = _determine_plugin_status(violations)
673
+ if status == "healthy":
674
+ healthy += 1
675
+ elif status == "degraded":
676
+ degraded += 1
677
+ else:
678
+ unhealthy += 1
679
+
680
+ return {
681
+ "status": _determine_overall_status(all_violations),
682
+ "checked_at": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
683
+ "plugins_checked": plugins_checked,
684
+ "violations": all_violations,
685
+ "summary": {
686
+ "errors": errors,
687
+ "warnings": warnings,
688
+ "plugins_healthy": healthy,
689
+ "plugins_degraded": degraded,
690
+ "plugins_unhealthy": unhealthy,
691
+ },
692
+ }
693
+
694
+
695
+ def persist_report(report: dict[str, Any]) -> str | None:
696
+ """Persist the report via DomainStore. Returns the record ID on success."""
697
+ try:
698
+ # Upsert: try update first, fall back to create
699
+ record = dict(report)
700
+ record["id"] = "latest"
701
+
702
+ existing = _sm.get("health", "latest")
703
+ if existing:
704
+ result = _sm.update("health", "latest", record)
705
+ else:
706
+ result = _sm.create("health", record)
707
+
708
+ return "latest" if result else None
709
+ except Exception as exc:
710
+ print(f"WARNING: Could not persist health report: {exc}", file=sys.stderr)
711
+ return None
712
+
713
+
714
+ # ---------------------------------------------------------------------------
715
+ # Human-readable console output
716
+ # ---------------------------------------------------------------------------
717
+
718
+ def print_report(report: dict[str, Any], record_id: str | None = None) -> None:
719
+ status = report["status"].upper()
720
+ checked = report["plugins_checked"]
721
+ summary = report["summary"]
722
+
723
+ # Status line
724
+ status_icon = {"HEALTHY": "[OK]", "DEGRADED": "[WARN]", "UNHEALTHY": "[FAIL]"}.get(
725
+ status, "[?]"
726
+ )
727
+ print(f"\n{status_icon} Health probe: {status}")
728
+ print(
729
+ f" Plugins checked: {checked} | "
730
+ f"Errors: {summary['errors']} | "
731
+ f"Warnings: {summary['warnings']}"
732
+ )
733
+ print(
734
+ f" Healthy: {summary['plugins_healthy']} | "
735
+ f"Degraded: {summary['plugins_degraded']} | "
736
+ f"Unhealthy: {summary['plugins_unhealthy']}"
737
+ )
738
+
739
+ violations = report["violations"]
740
+ if not violations:
741
+ print("\n All plugins passed health checks.")
742
+ else:
743
+ print(f"\n Violations ({len(violations)}):")
744
+ for v in violations:
745
+ sev = v["severity"].upper()
746
+ loc = f" ({v['file']})" if v.get("file") else ""
747
+ print(f" [{sev}] {v['plugin']}{loc}")
748
+ print(f" {v['message']}")
749
+
750
+ if record_id:
751
+ print(f"\n Report persisted as: wicked-observability/health/{record_id}\n")
752
+ else:
753
+ print("\n Report not persisted.\n")
754
+
755
+
756
+ # ---------------------------------------------------------------------------
757
+ # Path helpers
758
+ # ---------------------------------------------------------------------------
759
+
760
+ def _rel(path: Path) -> str:
761
+ """Return path relative to its plugin root (plugins/wicked-x/ stripped)."""
762
+ parts = path.parts
763
+ try:
764
+ # Find 'plugins' segment and skip two more (plugins, wicked-x)
765
+ idx = next(i for i, p in enumerate(parts) if p == "plugins")
766
+ return str(Path(*parts[idx + 2 :]))
767
+ except StopIteration:
768
+ return str(path)
769
+
770
+
771
+ def _rel_from(path: Path, base: Path) -> str:
772
+ try:
773
+ return str(path.relative_to(base))
774
+ except ValueError:
775
+ return str(path)
776
+
777
+
778
+ # ---------------------------------------------------------------------------
779
+ # Plugins directory discovery
780
+ # ---------------------------------------------------------------------------
781
+
782
+ def find_plugins_dir(start: Path) -> Path | None:
783
+ """Walk up from start to find a 'plugins/' directory in the monorepo root.
784
+
785
+ Returns None when no ``plugins/`` directory exists anywhere up the tree.
786
+ The single-plugin layout (this repo *is* the plugin — no ``plugins/``
787
+ monorepo wrapper) is handled by the caller, which treats PLUGIN_ROOT as
788
+ the lone plugin rather than iterating a non-existent ``plugins/`` dir.
789
+ """
790
+ candidate = start
791
+ for _ in range(10):
792
+ plugins = candidate / "plugins"
793
+ if plugins.is_dir():
794
+ return plugins
795
+ parent = candidate.parent
796
+ if parent == candidate:
797
+ break
798
+ candidate = parent
799
+ return None
800
+
801
+
802
+ # ---------------------------------------------------------------------------
803
+ # Entry point
804
+ # ---------------------------------------------------------------------------
805
+
806
+ def main() -> int:
807
+ # Fast-path: --health-check emits a sample report for contract testing
808
+ if "--health-check" in sys.argv:
809
+ print(json.dumps({
810
+ "status": "healthy",
811
+ "checked_at": datetime.datetime.now(datetime.timezone.utc).isoformat(),
812
+ "plugins_checked": 0,
813
+ "violations": [],
814
+ "summary": {
815
+ "errors": 0, "warnings": 0,
816
+ "plugins_healthy": 0, "plugins_degraded": 0, "plugins_unhealthy": 0,
817
+ },
818
+ }))
819
+ return 0
820
+
821
+ parser = argparse.ArgumentParser(
822
+ description="Validate wicked-garden plugin health."
823
+ )
824
+ parser.add_argument(
825
+ "--plugin",
826
+ metavar="PLUGIN_NAME",
827
+ help="Only probe a single plugin (e.g. wicked-crew).",
828
+ )
829
+ parser.add_argument(
830
+ "--plugins-dir",
831
+ metavar="PATH",
832
+ help="Path to the plugins/ directory. Auto-detected if omitted.",
833
+ )
834
+ parser.add_argument(
835
+ "--json",
836
+ dest="json_only",
837
+ action="store_true",
838
+ help="Print JSON to stdout only, no human-readable output.",
839
+ )
840
+ args = parser.parse_args()
841
+
842
+ # Resolve plugins directory. When --plugins-dir is omitted we walk up
843
+ # looking for a 'plugins/' monorepo wrapper; if none exists this repo IS
844
+ # the single plugin, so we fall back to PLUGIN_ROOT.
845
+ if args.plugins_dir:
846
+ plugins_dir = Path(args.plugins_dir).resolve()
847
+ else:
848
+ plugins_dir = find_plugins_dir(Path.cwd())
849
+
850
+ if plugins_dir is None:
851
+ # Single-plugin layout: the repo root is the lone plugin. Its parent
852
+ # acts as the synthetic "plugins/" dir so --plugin <name> still works.
853
+ single_plugin_root = PLUGIN_ROOT
854
+ plugins_dir = PLUGIN_ROOT.parent
855
+ if args.plugin and args.plugin != single_plugin_root.name:
856
+ print(
857
+ f"ERROR: plugin '{args.plugin}' not found (single-plugin repo "
858
+ f"is '{single_plugin_root.name}')",
859
+ file=sys.stderr,
860
+ )
861
+ return 2
862
+ plugin_roots = [single_plugin_root]
863
+ else:
864
+ if not plugins_dir.is_dir():
865
+ print(f"ERROR: plugins directory not found: {plugins_dir}", file=sys.stderr)
866
+ return 2
867
+
868
+ # Determine which plugins to probe
869
+ if args.plugin:
870
+ plugin_root = plugins_dir / args.plugin
871
+ if not plugin_root.is_dir():
872
+ print(
873
+ f"ERROR: plugin '{args.plugin}' not found in {plugins_dir}",
874
+ file=sys.stderr,
875
+ )
876
+ return 2
877
+ plugin_roots = [plugin_root]
878
+ else:
879
+ plugin_roots = sorted(
880
+ p for p in plugins_dir.iterdir() if p.is_dir() and p.name.startswith("wicked-")
881
+ )
882
+
883
+ # Run probes
884
+ all_violations: list[dict[str, Any]] = []
885
+ per_plugin_violations: dict[str, list[dict[str, Any]]] = {}
886
+
887
+ for plugin_root in plugin_roots:
888
+ plugin_name = plugin_root.name
889
+ violations = probe_plugin(plugin_root, plugins_dir)
890
+ per_plugin_violations[plugin_name] = violations
891
+ all_violations.extend(violations)
892
+
893
+ # Build report
894
+ report = build_report(all_violations, len(plugin_roots), per_plugin_violations)
895
+
896
+ # Persist
897
+ record_id = persist_report(report)
898
+
899
+ # Output
900
+ if args.json_only:
901
+ print(json.dumps(report, indent=2, ensure_ascii=False))
902
+ else:
903
+ print_report(report, record_id)
904
+
905
+ # Exit code
906
+ status = report["status"]
907
+ if status == "healthy":
908
+ return 0
909
+ if status == "degraded":
910
+ return 1
911
+ return 2
912
+
913
+
914
+ if __name__ == "__main__":
915
+ sys.exit(main())