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,739 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ drift.py — quality-telemetry drift detection for wicked-garden.
4
+
5
+ Closes Issue #443: distinguish noise (common-cause) from signal (special-cause)
6
+ on the session-level quality timeline maintained by telemetry.py.
7
+
8
+ Algorithms
9
+ ----------
10
+ 1. Moving average (N=4 baseline window). Flags ``drop_pct`` when the current
11
+ session's metric drops >15% below the baseline mean — the minimal viable
12
+ alarm specified in the issue.
13
+ 2. EWMA slope over the last ``ewma_window`` sessions with alpha=0.3. A
14
+ monotonically negative slope across the smoothed points is reported as a
15
+ trending degradation signal distinct from a single-point drop.
16
+ 3. 3σ Western Electric zone-A rule (baseline mean ± 3 * baseline_stddev).
17
+ Points outside 3σ are tagged ``special-cause``. Points outside 2σ are
18
+ reported as ``warn`` (zone B). Everything else is ``common-cause`` noise.
19
+ 4. Western Electric runs rules (issues #459, #719) beyond zone A:
20
+ - ``2_of_3_zone_b``: 2 of the last 3 points are beyond 2σ on the bad side
21
+ (below-baseline). Fires ``special-cause`` even when the latest point is
22
+ inside zone A.
23
+ - ``4_of_5_zone_c``: 4 of the last 5 points beyond 1σ on the bad side
24
+ (issue #719). Catches sustained drift one zone closer to the mean.
25
+ - ``8_consecutive_one_side``: 8 consecutive points all on the bad side of
26
+ the mean (issue #719). Catches subtle but persistent bias.
27
+ - ``trending_down``: 6 consecutive points each lower than the one before
28
+ (monotonic degradation). Fires ``special-cause``.
29
+ - ``trending_up``: symmetric monotonic climb. Reported but never flips
30
+ ``drift`` — rising metrics on a "higher is better" axis are good news.
31
+ These runs rules are additive — they OR with the zone-A sigma check.
32
+
33
+ Classification output
34
+ ---------------------
35
+ {
36
+ "metric": "gate_pass_rate",
37
+ "latest": 0.55,
38
+ "baseline": {"mean": 0.88, "stddev": 0.06, "window": 4, "samples": [...]},
39
+ "drop_pct": 0.375, # 37.5% below baseline mean
40
+ "ewma_slope": -0.018, # negative slope = worsening
41
+ "zone": "special-cause" | "warn" | "common-cause" | "insufficient",
42
+ "drift": True, # final decision: alarm should fire
43
+ "reasons": ["drop_pct 37.5% >= 15%", "outside 3σ"],
44
+ "session_count": 5,
45
+ }
46
+
47
+ Guardrails
48
+ ----------
49
+ - Stdlib-only. Fail-open. No hard dependency on wicked-bus.
50
+ - Minimum-sample rule: require >= 5 sessions (issue acceptance criterion) before
51
+ classifying anything as drift. Below that, return ``zone: "insufficient"``.
52
+ - "No new gate in response to common-cause variation" — we expose a helper
53
+ ``is_actionable`` that returns True only for special-cause drift, so the
54
+ caller (crew/status) can refuse to escalate on noise.
55
+ """
56
+
57
+ from __future__ import annotations
58
+
59
+ import json
60
+ import math
61
+ import os
62
+ import sys
63
+ from pathlib import Path
64
+ from typing import Any, Dict, List, Optional
65
+
66
+ # ---------------------------------------------------------------------------
67
+ # Path setup — co-located with telemetry.py under scripts/delivery/
68
+ # ---------------------------------------------------------------------------
69
+
70
+ _SCRIPTS_ROOT = Path(__file__).resolve().parents[1]
71
+ if str(_SCRIPTS_ROOT) not in sys.path:
72
+ sys.path.insert(0, str(_SCRIPTS_ROOT))
73
+ if str(Path(__file__).resolve().parent) not in sys.path:
74
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
75
+
76
+ # Defaults — kept as module constants so callers can tune via kwargs.
77
+ DEFAULT_BASELINE_WINDOW = 4 # sessions preceding the latest one
78
+ DEFAULT_EWMA_WINDOW = 6
79
+ DEFAULT_EWMA_ALPHA = 0.3
80
+ DEFAULT_DROP_PCT_THRESHOLD = 0.15 # issue acceptance: 15% below 4-session baseline
81
+ DEFAULT_SPECIAL_CAUSE_SIGMA = 3.0 # Western Electric zone A
82
+ DEFAULT_WARN_SIGMA = 2.0 # zone B
83
+ DEFAULT_ZONE_C_SIGMA = 1.0 # zone C (issue #719)
84
+ MIN_SESSIONS_FOR_DRIFT = 5 # issue #459 acceptance
85
+ # Issue #719 acceptance: at least 8 samples before *any* WE rule may fire.
86
+ WARMUP_MIN_SAMPLES = 8
87
+
88
+ # Western Electric runs-rule thresholds (issues #459, #719).
89
+ # 2-of-3: "2 of the most recent 3 points beyond 2σ on the same side."
90
+ WE_2OF3_WINDOW = 3
91
+ WE_2OF3_COUNT = 2
92
+ # 4-of-5: "4 of the most recent 5 points beyond 1σ on the same side."
93
+ WE_4OF5_WINDOW = 5
94
+ WE_4OF5_COUNT = 4
95
+ # Run-of-8: "8 consecutive points on the same side of the centerline."
96
+ WE_RUN_OF_8_WINDOW = 8
97
+ # Trending: 6 consecutive monotonically increasing / decreasing points.
98
+ WE_TRENDING_WINDOW = 6
99
+
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # Low-level statistics (stdlib only, deterministic)
103
+ # ---------------------------------------------------------------------------
104
+
105
+ def _mean(xs: List[float]) -> float:
106
+ return sum(xs) / len(xs) if xs else 0.0
107
+
108
+
109
+ def _stddev(xs: List[float]) -> float:
110
+ """Population standard deviation. Returns 0 on fewer than 2 points."""
111
+ if len(xs) < 2:
112
+ return 0.0
113
+ mu = _mean(xs)
114
+ return math.sqrt(sum((x - mu) ** 2 for x in xs) / len(xs))
115
+
116
+
117
+ def _ewma(xs: List[float], alpha: float = DEFAULT_EWMA_ALPHA) -> List[float]:
118
+ """Return the EWMA series. Empty input → empty output."""
119
+ out: List[float] = []
120
+ prev: Optional[float] = None
121
+ for x in xs:
122
+ if prev is None:
123
+ prev = x
124
+ else:
125
+ prev = alpha * x + (1 - alpha) * prev
126
+ out.append(prev)
127
+ return out
128
+
129
+
130
+ def _slope(xs: List[float]) -> float:
131
+ """Simple linear regression slope with x = [0, 1, 2, ...]."""
132
+ n = len(xs)
133
+ if n < 2:
134
+ return 0.0
135
+ x = list(range(n))
136
+ mx = _mean([float(i) for i in x])
137
+ my = _mean(xs)
138
+ num = sum((xi - mx) * (yi - my) for xi, yi in zip(x, xs))
139
+ den = sum((xi - mx) ** 2 for xi in x)
140
+ if den == 0:
141
+ return 0.0
142
+ return num / den
143
+
144
+
145
+ # ---------------------------------------------------------------------------
146
+ # Metric extraction from a timeline
147
+ # ---------------------------------------------------------------------------
148
+
149
+ def _pull_metric(records: List[Dict[str, Any]], metric: str) -> List[float]:
150
+ """Extract a numeric metric series from the timeline, skipping missing."""
151
+ out: List[float] = []
152
+ for r in records:
153
+ m = r.get("metrics") or {}
154
+ v = m.get(metric)
155
+ if isinstance(v, (int, float)):
156
+ out.append(float(v))
157
+ return out
158
+
159
+
160
+ # ---------------------------------------------------------------------------
161
+ # Western Electric runs rules (issue #459)
162
+ # ---------------------------------------------------------------------------
163
+
164
+ def _we_2_of_3_zone_b(
165
+ series: List[float],
166
+ mean: float,
167
+ stddev: float,
168
+ *,
169
+ warn_sigma: float = DEFAULT_WARN_SIGMA,
170
+ ) -> bool:
171
+ """WE rule: 2 of the last 3 points beyond 2σ below the baseline mean.
172
+
173
+ Requires at least 3 points and a non-zero baseline stddev. We only flag
174
+ the "bad" (below-baseline) side — metrics like gate_pass_rate are
175
+ higher-is-better. Callers with lower-is-better metrics should invert
176
+ the series before passing it in.
177
+ """
178
+ if stddev <= 0:
179
+ return False
180
+ if len(series) < WE_2OF3_WINDOW:
181
+ return False
182
+ recent = series[-WE_2OF3_WINDOW:]
183
+ # Point counts as "beyond 2σ on the bad side" when it sits at least
184
+ # warn_sigma standard deviations below the baseline mean.
185
+ threshold = mean - warn_sigma * stddev
186
+ breaches = sum(1 for x in recent if x < threshold)
187
+ return breaches >= WE_2OF3_COUNT
188
+
189
+
190
+ def _we_4_of_5_zone_c(
191
+ series: List[float],
192
+ mean: float,
193
+ stddev: float,
194
+ *,
195
+ side: str = "below",
196
+ zone_c_sigma: float = DEFAULT_ZONE_C_SIGMA,
197
+ ) -> bool:
198
+ """WE rule (#719): 4 of the last 5 points beyond 1σ on one side of the baseline.
199
+
200
+ ``side`` mirrors ``_we_8_consecutive_one_side``: "below" (default — the
201
+ bad side for higher-is-better metrics) or "above". Requires non-zero
202
+ baseline stddev and at least ``WE_4OF5_WINDOW`` observations.
203
+ """
204
+ if stddev <= 0:
205
+ return False
206
+ if len(series) < WE_4OF5_WINDOW:
207
+ return False
208
+ recent = series[-WE_4OF5_WINDOW:]
209
+ if side == "above":
210
+ threshold = mean + zone_c_sigma * stddev
211
+ breaches = sum(1 for x in recent if x > threshold)
212
+ else:
213
+ threshold = mean - zone_c_sigma * stddev
214
+ breaches = sum(1 for x in recent if x < threshold)
215
+ return breaches >= WE_4OF5_COUNT
216
+
217
+
218
+ def _we_8_consecutive_one_side(
219
+ series: List[float],
220
+ mean: float,
221
+ *,
222
+ side: str = "below",
223
+ ) -> bool:
224
+ """WE rule (#719): 8 consecutive points on one side of the centerline.
225
+
226
+ ``side="below"`` is the bad side for higher-is-better metrics.
227
+ Equality with the mean does not count — SPC theory requires strict
228
+ one-sided runs to indicate a process shift.
229
+ """
230
+ if len(series) < WE_RUN_OF_8_WINDOW:
231
+ return False
232
+ window = series[-WE_RUN_OF_8_WINDOW:]
233
+ if side == "below":
234
+ return all(x < mean for x in window)
235
+ if side == "above":
236
+ return all(x > mean for x in window)
237
+ return False
238
+
239
+
240
+ def _we_trending(series: List[float], direction: str = "down") -> bool:
241
+ """WE rule: 6 consecutive points moving monotonically.
242
+
243
+ ``direction`` is "down" (each point strictly less than the previous) or
244
+ "up" (strictly greater). Flat runs (equal values) do NOT count — SPC
245
+ theory requires strict monotonicity for a trend signal.
246
+ """
247
+ if len(series) < WE_TRENDING_WINDOW:
248
+ return False
249
+ window = series[-WE_TRENDING_WINDOW:]
250
+ if direction == "down":
251
+ return all(window[i] < window[i - 1] for i in range(1, len(window)))
252
+ if direction == "up":
253
+ return all(window[i] > window[i - 1] for i in range(1, len(window)))
254
+ return False
255
+
256
+
257
+ # ---------------------------------------------------------------------------
258
+ # Public API
259
+ # ---------------------------------------------------------------------------
260
+
261
+ def classify(
262
+ records: List[Dict[str, Any]],
263
+ metric: str = "gate_pass_rate",
264
+ *,
265
+ baseline_window: int = DEFAULT_BASELINE_WINDOW,
266
+ ewma_window: int = DEFAULT_EWMA_WINDOW,
267
+ drop_pct_threshold: float = DEFAULT_DROP_PCT_THRESHOLD,
268
+ special_cause_sigma: float = DEFAULT_SPECIAL_CAUSE_SIGMA,
269
+ warn_sigma: float = DEFAULT_WARN_SIGMA,
270
+ min_sessions: int = MIN_SESSIONS_FOR_DRIFT,
271
+ ) -> Dict[str, Any]:
272
+ """Classify the latest point against its baseline.
273
+
274
+ Returns a dict with fields documented in the module docstring. Always
275
+ returns a dict — never raises. Missing metric → ``zone: insufficient``.
276
+ """
277
+ series = _pull_metric(records, metric)
278
+ session_count = len(series)
279
+
280
+ result: Dict[str, Any] = {
281
+ "metric": metric,
282
+ "session_count": session_count,
283
+ "latest": series[-1] if series else None,
284
+ "baseline": None,
285
+ "drop_pct": None,
286
+ "ewma_slope": None,
287
+ "zone": "insufficient",
288
+ "drift": False,
289
+ "reasons": [],
290
+ }
291
+
292
+ if session_count < min_sessions:
293
+ result["reasons"].append(
294
+ f"need >= {min_sessions} sessions, have {session_count}"
295
+ )
296
+ return result
297
+
298
+ # Baseline: the N sessions immediately preceding the latest.
299
+ baseline = series[-(baseline_window + 1):-1] if baseline_window < session_count else series[:-1]
300
+ if not baseline:
301
+ result["reasons"].append("no baseline samples available")
302
+ return result
303
+
304
+ mu = _mean(baseline)
305
+ sigma = _stddev(baseline)
306
+ latest = series[-1]
307
+ result["baseline"] = {
308
+ "mean": round(mu, 6),
309
+ "stddev": round(sigma, 6),
310
+ "window": len(baseline),
311
+ "samples": [round(x, 6) for x in baseline],
312
+ }
313
+
314
+ # Moving-average deviation (issue acceptance: >15% drop).
315
+ if mu > 0:
316
+ drop_pct = (mu - latest) / mu # positive ⇒ latest is below baseline
317
+ elif mu == 0 and latest < 0:
318
+ drop_pct = 1.0
319
+ else:
320
+ drop_pct = 0.0
321
+ result["drop_pct"] = round(drop_pct, 6)
322
+
323
+ # 3σ classification.
324
+ if sigma > 0:
325
+ z = (mu - latest) / sigma
326
+ else:
327
+ # Flat baseline: any delta is instantly "special-cause" in SPC theory,
328
+ # but we soften: require drop_pct threshold *and* a non-zero delta.
329
+ z = math.inf if latest < mu else 0.0
330
+
331
+ # EWMA slope over the last ewma_window points.
332
+ window_series = series[-ewma_window:] if len(series) >= ewma_window else series
333
+ ewma_series = _ewma(window_series)
334
+ slope = _slope(ewma_series)
335
+ result["ewma_slope"] = round(slope, 6)
336
+
337
+ # Zone classification (zone-A sigma test).
338
+ # Surface structured fields so _classify_rule doesn't have to substring-match
339
+ # the human-readable reasons text (Copilot review on PR #730).
340
+ result["outside_special_cause_sigma"] = z >= special_cause_sigma
341
+ result["special_cause_sigma_threshold"] = special_cause_sigma
342
+ if z >= special_cause_sigma:
343
+ zone = "special-cause"
344
+ result["reasons"].append(f"outside {special_cause_sigma:.0f}σ (z={z:.2f})")
345
+ elif z >= warn_sigma:
346
+ zone = "warn"
347
+ result["reasons"].append(f"outside {warn_sigma:.0f}σ (z={z:.2f})")
348
+ else:
349
+ zone = "common-cause"
350
+
351
+ # Western Electric runs rules (issues #459, #719) — operate on the full
352
+ # observed series using the baseline mean/stddev as the reference frame.
353
+ # Issue #719 acceptance: at least WARMUP_MIN_SAMPLES observations before
354
+ # *any* WE rule may fire, regardless of which rule is configured. Below
355
+ # warmup we still classify zone-A from the latest z-score, but suppress
356
+ # WE flags entirely. ``insufficient_warmup`` lets callers tell "below
357
+ # warmup" apart from "warm but quiet".
358
+ we_rules: List[str] = []
359
+ insufficient_warmup = session_count < WARMUP_MIN_SAMPLES
360
+ result["insufficient_warmup"] = insufficient_warmup
361
+ result["warmup_min_samples"] = WARMUP_MIN_SAMPLES
362
+
363
+ if not insufficient_warmup:
364
+ if _we_2_of_3_zone_b(series, mu, sigma, warn_sigma=warn_sigma):
365
+ we_rules.append("2_of_3_zone_b")
366
+ result["reasons"].append(
367
+ f"WE 2-of-3: 2+ of last {WE_2OF3_WINDOW} points beyond "
368
+ f"{warn_sigma:.0f}σ below baseline"
369
+ )
370
+ if _we_4_of_5_zone_c(series, mu, sigma):
371
+ we_rules.append("4_of_5_zone_c")
372
+ result["reasons"].append(
373
+ f"WE 4-of-5: 4+ of last {WE_4OF5_WINDOW} points beyond "
374
+ f"{DEFAULT_ZONE_C_SIGMA:.0f}σ below baseline"
375
+ )
376
+ if _we_8_consecutive_one_side(series, mu, side="below"):
377
+ we_rules.append("8_consecutive_one_side")
378
+ result["reasons"].append(
379
+ f"WE run-of-8: {WE_RUN_OF_8_WINDOW} consecutive points below baseline mean"
380
+ )
381
+ if _we_trending(series, direction="down"):
382
+ we_rules.append("trending_down")
383
+ result["reasons"].append(
384
+ f"WE trending-down: {WE_TRENDING_WINDOW} consecutive decreasing points"
385
+ )
386
+ if _we_trending(series, direction="up"):
387
+ we_rules.append("trending_up")
388
+ result["reasons"].append(
389
+ f"WE trending-up: {WE_TRENDING_WINDOW} consecutive increasing points"
390
+ )
391
+ else:
392
+ result["reasons"].append(
393
+ f"warmup gate: need {WARMUP_MIN_SAMPLES} samples for WE rules, "
394
+ f"have {session_count}"
395
+ )
396
+ result["we_rules"] = we_rules
397
+
398
+ # Any "bad-side" WE runs rule escalates zone to special-cause.
399
+ # trending_up is informational only (higher-is-better metrics).
400
+ bad_side_rules = {"2_of_3_zone_b", "4_of_5_zone_c", "8_consecutive_one_side", "trending_down"}
401
+ if any(r in bad_side_rules for r in we_rules):
402
+ zone = "special-cause"
403
+
404
+ # Drift alarm: >=15% drop below baseline OR special-cause zone.
405
+ # Issue #719 acceptance: gate all flags behind WARMUP_MIN_SAMPLES so we
406
+ # don't fire on a freshly-onboarded project's first noisy week. The
407
+ # existing ``min_sessions`` (default 5) is the *classification* floor; the
408
+ # warmup gate is the *flag* floor.
409
+ drift = False
410
+ if drop_pct >= drop_pct_threshold:
411
+ result["reasons"].append(
412
+ f"drop_pct {drop_pct * 100:.1f}% >= {drop_pct_threshold * 100:.0f}%"
413
+ )
414
+ if not insufficient_warmup:
415
+ drift = True
416
+ if zone == "special-cause" and not insufficient_warmup:
417
+ drift = True
418
+
419
+ # Trending degradation: EWMA slope meaningfully negative.
420
+ if slope < -0.01 and len(ewma_series) >= 3:
421
+ result["reasons"].append(f"ewma_slope {slope:.4f} trending down")
422
+ # Trending slope alone does not flip drift=True unless combined with
423
+ # a drop or special-cause — slope is advisory context.
424
+
425
+ result["zone"] = zone
426
+ result["drift"] = drift
427
+ return result
428
+
429
+
430
+ def is_actionable(classification: Dict[str, Any]) -> bool:
431
+ """Should the caller escalate (e.g. add a gate, alert a user)?
432
+
433
+ Issue acceptance: "No new process gate added in response to common-cause
434
+ variation." Actionable = special-cause OR drop_pct breach — both
435
+ unambiguous signal, not noise.
436
+ """
437
+ if not classification:
438
+ return False
439
+ if classification.get("zone") == "special-cause":
440
+ return True
441
+ drop = classification.get("drop_pct") or 0
442
+ if drop >= DEFAULT_DROP_PCT_THRESHOLD:
443
+ return True
444
+ return False
445
+
446
+
447
+ def summarize(classification: Dict[str, Any]) -> str:
448
+ """Render a one-line human summary for status output."""
449
+ if not classification:
450
+ return "no telemetry data"
451
+ zone = classification.get("zone", "unknown")
452
+ metric = classification.get("metric", "metric")
453
+ latest = classification.get("latest")
454
+ base = classification.get("baseline") or {}
455
+ mu = base.get("mean")
456
+ drop = classification.get("drop_pct")
457
+ latest_s = "n/a" if latest is None else f"{latest:.3f}"
458
+ mu_s = "n/a" if mu is None else f"{mu:.3f}"
459
+ drop_s = "n/a" if drop is None else f"{drop * 100:+.1f}%"
460
+ if zone == "insufficient":
461
+ return f"{metric}: insufficient data ({classification.get('session_count', 0)} sessions)"
462
+ label = {
463
+ "special-cause": "SIGNAL",
464
+ "warn": "watch",
465
+ "common-cause": "noise",
466
+ }.get(zone, zone)
467
+ return f"{metric}: latest={latest_s} baseline={mu_s} delta={drop_s} [{label}]"
468
+
469
+
470
+ # ---------------------------------------------------------------------------
471
+ # Bus emission (fail-open)
472
+ # ---------------------------------------------------------------------------
473
+
474
+ def emit_drift_event(
475
+ project: str,
476
+ classification: Dict[str, Any],
477
+ ) -> bool:
478
+ """Emit a wicked-bus drift event if the classification is actionable.
479
+
480
+ Returns True if emission was attempted (bus may have been unavailable),
481
+ False if the classification didn't warrant emission.
482
+ Never raises.
483
+ """
484
+ try:
485
+ if not is_actionable(classification):
486
+ return False
487
+ # Import lazily so drift.py remains importable without the bus shim.
488
+ from _bus import emit_event # type: ignore
489
+ payload = {
490
+ "project": project,
491
+ "metric": classification.get("metric"),
492
+ "zone": classification.get("zone"),
493
+ "latest": classification.get("latest"),
494
+ "baseline_mean": (classification.get("baseline") or {}).get("mean"),
495
+ "baseline_stddev": (classification.get("baseline") or {}).get("stddev"),
496
+ "drop_pct": classification.get("drop_pct"),
497
+ "ewma_slope": classification.get("ewma_slope"),
498
+ "reasons": classification.get("reasons"),
499
+ "session_count": classification.get("session_count"),
500
+ }
501
+ emit_event("wicked.quality.drift_detected", payload)
502
+ return True
503
+ except Exception as exc:
504
+ # Fail-open — bus absence must never break telemetry.
505
+ print(f"[wicked-garden] drift emit error: {exc}", file=sys.stderr)
506
+ return False
507
+
508
+
509
+ # ---------------------------------------------------------------------------
510
+ # SPC flag emission via DomainStore (issue #719)
511
+ # ---------------------------------------------------------------------------
512
+
513
+ # Critical = zone-A (3σ) breach or 8-in-a-row run; warn = lesser WE rules
514
+ # or simple drop_pct trigger. Severity drives downstream alert routing.
515
+ _CRITICAL_RULES = {"1_outside_3sigma", "8_consecutive_one_side"}
516
+
517
+
518
+ def _classify_rule(classification: Dict[str, Any]) -> List[str]:
519
+ """Return all rule identifiers that fired for this classification.
520
+
521
+ A single classification can fire multiple rules — emit one flag per rule
522
+ so downstream consumers can deduplicate by (metric, rule) instead of
523
+ forcing the priority decision here.
524
+ """
525
+ rules = list(classification.get("we_rules") or [])
526
+ # Synthesize a rule id for the zone-A z-score breach using the structured
527
+ # field set by classify() — substring-matching the reasons text was brittle
528
+ # against tuned ``special_cause_sigma`` (e.g. "z=4.2" matches "3" by accident).
529
+ if classification.get("outside_special_cause_sigma"):
530
+ if "1_outside_3sigma" not in rules:
531
+ rules.append("1_outside_3sigma")
532
+ drop = classification.get("drop_pct") or 0.0
533
+ if drop >= DEFAULT_DROP_PCT_THRESHOLD and not rules:
534
+ rules.append("drop_pct_threshold")
535
+ # Strip informational-only rules from emission set.
536
+ return [r for r in rules if r != "trending_up"]
537
+
538
+
539
+ def emit_spc_flag(
540
+ project: str,
541
+ classification: Dict[str, Any],
542
+ *,
543
+ metric: Optional[str] = None,
544
+ ) -> List[Dict[str, Any]]:
545
+ """Persist one ``delivery:spc:flag`` record per fired rule.
546
+
547
+ Returns the list of payloads written (empty when nothing fired or warmup
548
+ blocks emission). Never raises — DomainStore failures are logged to stderr.
549
+ """
550
+ if classification.get("insufficient_warmup"):
551
+ return []
552
+ if not classification.get("drift"):
553
+ return []
554
+ rules = _classify_rule(classification)
555
+ if not rules:
556
+ return []
557
+
558
+ metric_name = metric or classification.get("metric") or "unknown"
559
+ base = classification.get("baseline") or {}
560
+ samples = base.get("samples") or []
561
+ sample_window = {
562
+ "start": samples[0] if samples else None,
563
+ "end": classification.get("latest"),
564
+ "n": classification.get("session_count"),
565
+ }
566
+ written: List[Dict[str, Any]] = []
567
+ try:
568
+ from _domain_store import DomainStore # type: ignore
569
+ ds = DomainStore("delivery", hook_mode=True)
570
+ except Exception as exc: # pragma: no cover — DomainStore is plugin core
571
+ print(f"[wicked-garden] spc flag store error: {exc}", file=sys.stderr)
572
+ return []
573
+
574
+ for rule in rules:
575
+ payload = {
576
+ "project": project,
577
+ "metric": metric_name,
578
+ "rule": rule,
579
+ "severity": "critical" if rule in _CRITICAL_RULES else "warn",
580
+ "sample_window": sample_window,
581
+ "current_value": classification.get("latest"),
582
+ "baseline_mean": base.get("mean"),
583
+ "baseline_stddev": base.get("stddev"),
584
+ "drop_pct": classification.get("drop_pct"),
585
+ "reasons": classification.get("reasons"),
586
+ "recorded_at": _utc_iso_now(),
587
+ }
588
+ try:
589
+ rec = ds.create("spc", payload)
590
+ if rec is not None:
591
+ written.append(rec)
592
+ except Exception as exc: # pragma: no cover
593
+ print(f"[wicked-garden] spc flag write error: {exc}", file=sys.stderr)
594
+ return written
595
+
596
+
597
+ def _utc_iso_now() -> str:
598
+ """Local helper — drift.py is otherwise time-free, so import datetime here."""
599
+ from datetime import datetime, timezone
600
+ return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
601
+
602
+
603
+ def list_recent_flags(
604
+ project: str,
605
+ *,
606
+ limit: int = 20,
607
+ ) -> List[Dict[str, Any]]:
608
+ """Return recent SPC flags for a project, newest first. Fail-open."""
609
+ try:
610
+ from _domain_store import DomainStore # type: ignore
611
+ ds = DomainStore("delivery", hook_mode=True)
612
+ flags = ds.list("spc", project=project) or []
613
+ except Exception:
614
+ return []
615
+ flags.sort(key=lambda r: r.get("recorded_at") or "", reverse=True)
616
+ return flags[:limit] if limit and limit > 0 else flags
617
+
618
+
619
+ # ---------------------------------------------------------------------------
620
+ # CLI
621
+ # ---------------------------------------------------------------------------
622
+
623
+ def _cli_classify(args: List[str]) -> int:
624
+ if not args:
625
+ print(json.dumps({"ok": False, "reason": "project required"}))
626
+ return 0
627
+ project = args[0]
628
+ metric = args[1] if len(args) > 1 else "gate_pass_rate"
629
+ try:
630
+ from delivery.telemetry import read_timeline # type: ignore
631
+ except Exception:
632
+ # When invoked via direct path, the delivery package import may not
633
+ # be on sys.path. Fall back to relative import.
634
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
635
+ from telemetry import read_timeline # type: ignore
636
+ records = read_timeline(project)
637
+ cls = classify(records, metric)
638
+ print(json.dumps({
639
+ "ok": True,
640
+ "project": project,
641
+ "metric": metric,
642
+ "classification": cls,
643
+ "summary": summarize(cls),
644
+ "actionable": is_actionable(cls),
645
+ }, indent=2))
646
+ return 0
647
+
648
+
649
+ def _cli_emit(args: List[str]) -> int:
650
+ """Classify + conditionally emit bus event. Used by hooks."""
651
+ if not args:
652
+ print(json.dumps({"ok": False, "reason": "project required"}))
653
+ return 0
654
+ project = args[0]
655
+ metric = args[1] if len(args) > 1 else "gate_pass_rate"
656
+ try:
657
+ from delivery.telemetry import read_timeline # type: ignore
658
+ except Exception:
659
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
660
+ from telemetry import read_timeline # type: ignore
661
+ cls = classify(read_timeline(project), metric)
662
+ emitted = emit_drift_event(project, cls)
663
+ print(json.dumps({
664
+ "ok": True,
665
+ "emitted": emitted,
666
+ "actionable": is_actionable(cls),
667
+ "classification": cls,
668
+ }))
669
+ return 0
670
+
671
+
672
+ def _cli_flag(args: List[str]) -> int:
673
+ """Classify and persist any SPC flags via DomainStore. Used by /delivery:health."""
674
+ if not args:
675
+ print(json.dumps({"ok": False, "reason": "project required"}))
676
+ return 0
677
+ project = args[0]
678
+ metric = args[1] if len(args) > 1 else "gate_pass_rate"
679
+ try:
680
+ from delivery.telemetry import read_timeline # type: ignore
681
+ except Exception:
682
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
683
+ from telemetry import read_timeline # type: ignore
684
+ cls = classify(read_timeline(project), metric)
685
+ flags = emit_spc_flag(project, cls, metric=metric)
686
+ print(json.dumps({
687
+ "ok": True,
688
+ "project": project,
689
+ "metric": metric,
690
+ "classification": cls,
691
+ "flags_written": flags,
692
+ }))
693
+ return 0
694
+
695
+
696
+ def _cli_list_flags(args: List[str]) -> int:
697
+ if not args:
698
+ print(json.dumps({"ok": False, "reason": "project required"}))
699
+ return 0
700
+ project = args[0]
701
+ limit = 20
702
+ if len(args) > 1:
703
+ try:
704
+ limit = int(args[1])
705
+ except ValueError:
706
+ limit = 20
707
+ flags = list_recent_flags(project, limit=limit)
708
+ print(json.dumps({"ok": True, "project": project, "flags": flags}))
709
+ return 0
710
+
711
+
712
+ def main(argv: Optional[List[str]] = None) -> int:
713
+ argv = list(argv if argv is not None else sys.argv[1:])
714
+ if not argv or argv[0] in ("-h", "--help", "help"):
715
+ print(
716
+ "Usage:\n"
717
+ " drift.py classify <project> [metric]\n"
718
+ " drift.py emit <project> [metric]\n"
719
+ " drift.py flag <project> [metric]\n"
720
+ " drift.py list-flags <project> [limit]\n",
721
+ file=sys.stderr,
722
+ )
723
+ return 0
724
+ cmd = argv[0]
725
+ rest = argv[1:]
726
+ if cmd == "classify":
727
+ return _cli_classify(rest)
728
+ if cmd == "emit":
729
+ return _cli_emit(rest)
730
+ if cmd == "flag":
731
+ return _cli_flag(rest)
732
+ if cmd == "list-flags":
733
+ return _cli_list_flags(rest)
734
+ print(json.dumps({"ok": False, "reason": f"unknown command: {cmd}"}))
735
+ return 0
736
+
737
+
738
+ if __name__ == "__main__":
739
+ sys.exit(main())