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.
- package/.claude-plugin/archetypes.json +166 -0
- package/.claude-plugin/components.json +75 -0
- package/.claude-plugin/marketplace.json +15 -0
- package/.claude-plugin/plugin.json +174 -0
- package/.claude-plugin/specialist.json +48 -0
- package/.claude-plugin/stack-registry.json +96 -0
- package/AGENTS.md +13 -0
- package/CHANGELOG.md +712 -0
- package/CONTRIBUTING.md +99 -0
- package/ETHOS.md +83 -0
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/WICKED_GARDEN_BUS_EVENTS.md +149 -0
- package/agents/agentic/architect.md +449 -0
- package/agents/agentic/performance-analyst.md +702 -0
- package/agents/agentic/safety-reviewer.md +662 -0
- package/agents/crew/implementer.md +163 -0
- package/agents/crew/researcher.md +94 -0
- package/agents/crew/reviewer.md +184 -0
- package/agents/data/data-engineer.md +227 -0
- package/agents/engineering/api-documentarian.md +440 -0
- package/agents/engineering/migration-engineer.md +301 -0
- package/agents/engineering/solution-architect.md +208 -0
- package/agents/jam/brainstorm-facilitator.md +297 -0
- package/agents/jam/council.md +322 -0
- package/agents/jam/quick-facilitator.md +85 -0
- package/agents/persona/persona-agent.md +121 -0
- package/agents/platform/compliance-officer.md +266 -0
- package/agents/platform/privacy-expert.md +496 -0
- package/agents/platform/security-engineer.md +287 -0
- package/agents/product/a11y-expert.md +323 -0
- package/agents/product/requirements-analyst.md +238 -0
- package/agents/product/ui-reviewer.md +180 -0
- package/agents/product/ux-designer.md +199 -0
- package/agents/product/value-strategist.md +269 -0
- package/agents/qe/semantic-reviewer.md +78 -0
- package/commands/agentic/audit.md +24 -0
- package/commands/agentic/design.md +24 -0
- package/commands/agentic/frameworks.md +25 -0
- package/commands/agentic/review.md +27 -0
- package/commands/archetype/build.md +12 -0
- package/commands/archetype/decide.md +12 -0
- package/commands/archetype/explore.md +12 -0
- package/commands/archetype/incident.md +12 -0
- package/commands/archetype/migrate.md +12 -0
- package/commands/archetype/modernize.md +12 -0
- package/commands/archetype/review.md +14 -0
- package/commands/archetype/ship.md +12 -0
- package/commands/archetype/specify.md +12 -0
- package/commands/archetype/triage.md +12 -0
- package/commands/compile.md +37 -0
- package/commands/data/analyze.md +18 -0
- package/commands/data/data.md +21 -0
- package/commands/data/ml.md +18 -0
- package/commands/data/ontology.md +22 -0
- package/commands/data/pipeline.md +17 -0
- package/commands/deliberate.md +181 -0
- package/commands/engineering/add-field.md +77 -0
- package/commands/engineering/apply.md +58 -0
- package/commands/engineering/arch.md +26 -0
- package/commands/engineering/debug.md +27 -0
- package/commands/engineering/docs.md +27 -0
- package/commands/engineering/new-generator.md +257 -0
- package/commands/engineering/patch-plan.md +59 -0
- package/commands/engineering/plan.md +29 -0
- package/commands/engineering/remove.md +48 -0
- package/commands/engineering/rename.md +44 -0
- package/commands/engineering/review.md +32 -0
- package/commands/help.md +128 -0
- package/commands/install.md +118 -0
- package/commands/intent.md +54 -0
- package/commands/jam/brainstorm.md +37 -0
- package/commands/jam/council.md +49 -0
- package/commands/jam/quick.md +21 -0
- package/commands/jam/revisit.md +79 -0
- package/commands/persona/as.md +136 -0
- package/commands/persona/define.md +24 -0
- package/commands/persona/list.md +118 -0
- package/commands/platform/actions.md +20 -0
- package/commands/platform/assert.md +18 -0
- package/commands/platform/audit.md +23 -0
- package/commands/platform/compliance.md +20 -0
- package/commands/platform/gh.md +22 -0
- package/commands/platform/health.md +22 -0
- package/commands/platform/incident.md +23 -0
- package/commands/platform/infra.md +24 -0
- package/commands/platform/peer-health.md +103 -0
- package/commands/platform/plugin-health.md +58 -0
- package/commands/platform/security.md +38 -0
- package/commands/platform/toolchain.md +96 -0
- package/commands/platform/traces.md +20 -0
- package/commands/product/a11y.md +19 -0
- package/commands/product/acceptance.md +19 -0
- package/commands/product/align.md +20 -0
- package/commands/product/analyze.md +23 -0
- package/commands/product/elicit.md +19 -0
- package/commands/product/listen.md +24 -0
- package/commands/product/mockup.md +20 -0
- package/commands/product/screenshot.md +19 -0
- package/commands/product/strategy.md +40 -0
- package/commands/product/synthesize.md +23 -0
- package/commands/product/ux-review.md +42 -0
- package/commands/product/ux.md +20 -0
- package/commands/product/visual-direction.md +34 -0
- package/commands/prove.md +47 -0
- package/commands/report-issue.md +243 -0
- package/commands/reset.md +144 -0
- package/commands/search/blast-radius.md +58 -0
- package/commands/search/hotspots.md +55 -0
- package/commands/search/index.md +38 -0
- package/commands/search/lineage.md +47 -0
- package/commands/search/service-map.md +79 -0
- package/commands/setup.md +363 -0
- package/commands/smaht/briefing.md +200 -0
- package/commands/smaht/events-import.md +95 -0
- package/commands/smaht/state.md +19 -0
- package/commands/where-am-i.md +58 -0
- package/docs/README.md +32 -0
- package/docs/adr/0001-code-relationship-graph-engine.md +116 -0
- package/docs/adr/0002-major-cleanup-keep-cut.md +91 -0
- package/docs/adr/0003-peers-gate-required-layers-optin.md +41 -0
- package/docs/adr/0004-code-graph-moves-to-wicked-brain.md +47 -0
- package/docs/brain-chunk-format.md +135 -0
- package/docs/compiler.md +95 -0
- package/docs/daemon-api.md +70 -0
- package/docs/domains.md +197 -0
- package/docs/getting-started.md +208 -0
- package/docs/plans/2026-06-08-wicked-loom-conduct.md +1337 -0
- package/docs/plans/2026-06-08-wicked-loom-contract.md +638 -0
- package/docs/plans/2026-06-08-wicked-loom-cutover.md +1300 -0
- package/docs/plans/2026-06-08-wicked-loom-expand-compose.md +843 -0
- package/docs/plans/2026-06-08-wicked-loom-flow-cutover.md +403 -0
- package/docs/plans/2026-06-10-codegraph-to-brain-phase1a-graph-core.md +838 -0
- package/docs/plans/2026-06-10-codegraph-to-brain-phase1b-injected-edges.md +44 -0
- package/docs/required-peers.md +60 -0
- package/docs/specs/2026-06-08-wicked-loom-extraction.md +225 -0
- package/docs/specs/2026-06-10-codegraph-to-brain-migration.md +168 -0
- package/docs/v11/archetypes.md +231 -0
- package/hooks/hooks.json +275 -0
- package/hooks/scripts/bootstrap.py +1797 -0
- package/hooks/scripts/invoke.py +62 -0
- package/hooks/scripts/notification.py +166 -0
- package/hooks/scripts/permission_request.py +223 -0
- package/hooks/scripts/post_tool.py +1374 -0
- package/hooks/scripts/pre_compact.py +272 -0
- package/hooks/scripts/pre_tool.py +1278 -0
- package/hooks/scripts/prompt_submit.py +1109 -0
- package/hooks/scripts/session_end.py +95 -0
- package/hooks/scripts/stop.py +624 -0
- package/hooks/scripts/subagent_lifecycle.py +464 -0
- package/hooks/scripts/subscribers/on_gate_decided.py +84 -0
- package/hooks/scripts/task_completed.py +322 -0
- package/hooks/scripts/worktree_guard.py +148 -0
- package/hooks/subscriptions/on_gate_decided.json +9 -0
- package/install.mjs +128 -0
- package/package.json +41 -0
- package/pyproject.toml +59 -0
- package/schemas/README.md +32 -0
- package/schemas/assert_contracts.json +37 -0
- package/schemas/health_probe.json +46 -0
- package/schemas/plugin_status.json +37 -0
- package/scripts/_adapters/__init__.py +166 -0
- package/scripts/_adapters/jira_adapter.py +18 -0
- package/scripts/_adapters/linear_adapter.py +18 -0
- package/scripts/_adapters/miro_adapter.py +18 -0
- package/scripts/_adapters/notion_adapter.py +18 -0
- package/scripts/_agents.py +357 -0
- package/scripts/_brain_ingest/__init__.py +2 -0
- package/scripts/_brain_ingest/session_fact_extractor.py +341 -0
- package/scripts/_brain_port.py +163 -0
- package/scripts/_bus.py +1081 -0
- package/scripts/_bus_catalog_gen.py +93 -0
- package/scripts/_bus_consumers.json +46 -0
- package/scripts/_bus_emit.py +43 -0
- package/scripts/_capability_registry.py +334 -0
- package/scripts/_capability_resolver.py +322 -0
- package/scripts/_domain_store.py +559 -0
- package/scripts/_event_log_reader.py +154 -0
- package/scripts/_event_schema.py +305 -0
- package/scripts/_event_store.py +397 -0
- package/scripts/_heavy_cadence.py +324 -0
- package/scripts/_integration_resolver.py +321 -0
- package/scripts/_logger.py +151 -0
- package/scripts/_loom.py +482 -0
- package/scripts/_paths.py +133 -0
- package/scripts/_python.sh +22 -0
- package/scripts/_run.py +137 -0
- package/scripts/_session.py +502 -0
- package/scripts/_validate_registry.py +1042 -0
- package/scripts/_wicked_testing_probe.py +277 -0
- package/scripts/agentic/analyze_agents.py +391 -0
- package/scripts/agentic/detect_framework.py +420 -0
- package/scripts/agentic/issue_taxonomy.py +357 -0
- package/scripts/agentic/pattern_scorer.py +434 -0
- package/scripts/classify/persist.py +169 -0
- package/scripts/compiler/compile.py +552 -0
- package/scripts/compiler/phase0/VERDICT.md +60 -0
- package/scripts/compiler/phase0/detect.py +292 -0
- package/scripts/compiler/phase0/emit.py +228 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.bindings.json +30 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.lint-output.txt +17 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/.wg/evidence/failing-tests.txt +4 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/.wg/evidence/honest-tests.txt +2 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/fab-fail.build.md +8 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/fab-missing.build.md +8 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/honest.build.md +8 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/wg_check_claims.py +174 -0
- package/scripts/compiler/phase0/evidence/command_iq.bindings.json +45 -0
- package/scripts/compiler/phase0/evidence/command_iq.lint-output.txt +17 -0
- package/scripts/compiler/phase0/evidence/command_iq.stage/.projects/evidence-domain/phase-1-clarify/evidence/failing-tests.txt +4 -0
- package/scripts/compiler/phase0/evidence/command_iq.stage/.projects/evidence-domain/phase-1-clarify/evidence/honest-tests.txt +3 -0
- package/scripts/compiler/phase0/evidence/command_iq.stage/fab-fail.build.md +8 -0
- package/scripts/compiler/phase0/evidence/command_iq.stage/fab-missing.build.md +8 -0
- package/scripts/compiler/phase0/evidence/command_iq.stage/honest.build.md +8 -0
- package/scripts/compiler/phase0/evidence/command_iq.stage/wg_check_claims.py +174 -0
- package/scripts/compiler/phase0/evidence/memos.bindings.json +28 -0
- package/scripts/compiler/phase0/evidence/memos.lint-output.txt +17 -0
- package/scripts/compiler/phase0/evidence/memos.stage/.wg/evidence/failing-tests.txt +4 -0
- package/scripts/compiler/phase0/evidence/memos.stage/.wg/evidence/honest-tests.txt +2 -0
- package/scripts/compiler/phase0/evidence/memos.stage/fab-fail.build.md +8 -0
- package/scripts/compiler/phase0/evidence/memos.stage/fab-missing.build.md +8 -0
- package/scripts/compiler/phase0/evidence/memos.stage/honest.build.md +8 -0
- package/scripts/compiler/phase0/evidence/memos.stage/wg_check_claims.py +174 -0
- package/scripts/compiler/phase0/evidence/verdict.json +104 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.bindings.json +29 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.lint-output.txt +17 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.stage/coverage/failing-tests.txt +4 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.stage/coverage/honest-tests.txt +3 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.stage/fab-fail.build.md +8 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.stage/fab-missing.build.md +8 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.stage/honest.build.md +8 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.stage/wg_check_claims.py +174 -0
- package/scripts/compiler/phase0/evidence/wire-vault-transcript.txt +60 -0
- package/scripts/compiler/phase0/run_probe.py +234 -0
- package/scripts/compiler/phase0/wire_vault.py +310 -0
- package/scripts/crew/__init__.py +0 -0
- package/scripts/crew/_task_audit_writer.py +243 -0
- package/scripts/crew/_task_reader.py +389 -0
- package/scripts/crew/archetypes_v11.py +348 -0
- package/scripts/crew/detectors/__init__.py +12 -0
- package/scripts/crew/flow_compiler.py +121 -0
- package/scripts/crew/phase_manager.py +703 -0
- package/scripts/crew/scope_delta.py +264 -0
- package/scripts/crew/specialist_resolver.py +218 -0
- package/scripts/crew/stack_registry.py +189 -0
- package/scripts/data/data_profiler.py +271 -0
- package/scripts/data/ontology_recommender.py +231 -0
- package/scripts/data/schema_validator.py +267 -0
- package/scripts/delivery/drift.py +739 -0
- package/scripts/delivery/telemetry.py +614 -0
- package/scripts/engineering/patch/codegraph_db.py +125 -0
- package/scripts/engineering/patch/generators/__init__.py +56 -0
- package/scripts/engineering/patch/generators/base.py +373 -0
- package/scripts/engineering/patch/generators/csharp_generator.py +494 -0
- package/scripts/engineering/patch/generators/go_generator.py +430 -0
- package/scripts/engineering/patch/generators/java_generator.py +928 -0
- package/scripts/engineering/patch/generators/jsp_generator.py +454 -0
- package/scripts/engineering/patch/generators/kotlin_generator.py +561 -0
- package/scripts/engineering/patch/generators/perl_generator.py +510 -0
- package/scripts/engineering/patch/generators/php_generator.py +644 -0
- package/scripts/engineering/patch/generators/propagation_engine.py +929 -0
- package/scripts/engineering/patch/generators/python_generator.py +487 -0
- package/scripts/engineering/patch/generators/ruby_generator.py +463 -0
- package/scripts/engineering/patch/generators/rust_generator.py +400 -0
- package/scripts/engineering/patch/generators/sql_generator.py +594 -0
- package/scripts/engineering/patch/generators/typescript_generator.py +439 -0
- package/scripts/engineering/patch/patch.py +800 -0
- package/scripts/engineering/patch/safety.py +429 -0
- package/scripts/jam/__init__.py +0 -0
- package/scripts/jam/_bus_consumers.py +271 -0
- package/scripts/jam/agentic_cli_registry.py +703 -0
- package/scripts/jam/consensus.py +737 -0
- package/scripts/jam/detect_clis.py +409 -0
- package/scripts/jam/jam.py +283 -0
- package/scripts/jam/save_transcript.py +171 -0
- package/scripts/loom/__init__.py +22 -0
- package/scripts/loom/cli.py +170 -0
- package/scripts/loom/compose.py +180 -0
- package/scripts/loom/gate.py +120 -0
- package/scripts/loom/manifest.py +140 -0
- package/scripts/loom/resolve.py +59 -0
- package/scripts/persona/__init__.py +0 -0
- package/scripts/persona/registry.py +688 -0
- package/scripts/platform/guard_pipeline.py +1214 -0
- package/scripts/platform/guard_profiles.py +216 -0
- package/scripts/platform/observability/assert_contracts.py +434 -0
- package/scripts/platform/observability/health_probe.py +915 -0
- package/scripts/platform/observability/ops_log_viewer.py +209 -0
- package/scripts/platform/prereq_doctor.py +567 -0
- package/scripts/qe/conditions_manifest.py +260 -0
- package/scripts/qe/content_sanitizer.py +160 -0
- package/scripts/qe/coverage_tracker.py +535 -0
- package/scripts/qe/discover_scenarios.py +243 -0
- package/scripts/qe/evidence_tracker.py +232 -0
- package/scripts/qe/prove.py +196 -0
- package/scripts/qe/registry_lookup.py +80 -0
- package/scripts/qe/registry_store.py +176 -0
- package/scripts/qe/semantic_review.py +1005 -0
- package/scripts/qe/vault_gate.py +382 -0
- package/scripts/qe/verdict_audit.py +175 -0
- package/scripts/qe/verdict_schema.py +335 -0
- package/scripts/reset.py +220 -0
- package/scripts/resolve_path.py +22 -0
- package/scripts/sentinel/invariants.py +402 -0
- package/scripts/sentinel/pre_push.py +73 -0
- package/scripts/setup/__init__.py +1 -0
- package/scripts/setup/detect_state.py +114 -0
- package/scripts/setup/migrate_legacy.py +67 -0
- package/scripts/setup/migrate_v6_projects.py +232 -0
- package/scripts/setup/onboarding.py +114 -0
- package/scripts/smaht/_bus_consumers.py +98 -0
- package/scripts/smaht/adapters/__init__.py +83 -0
- package/scripts/smaht/adapters/brain_adapter.py +254 -0
- package/scripts/smaht/adapters/context7_adapter.py +438 -0
- package/scripts/smaht/adapters/delegation_adapter.py +104 -0
- package/scripts/smaht/adapters/domain_adapter.py +206 -0
- package/scripts/smaht/adapters/events_adapter.py +231 -0
- package/scripts/smaht/adapters/tools_adapter.py +49 -0
- package/scripts/smaht/cheatsheet_store.py +236 -0
- package/scripts/smaht/context_package.py +386 -0
- package/scripts/smaht/knowledge_graph.py +395 -0
- package/scripts/smaht/propose_skills.py +856 -0
- package/scripts/smaht/test_cheatsheet_store.py +428 -0
- package/scripts/statusline.py +151 -0
- package/scripts/where_am_i.py +236 -0
- package/skills/agentic/agentic-patterns/SKILL.md +91 -0
- package/skills/agentic/agentic-patterns/refs/anti-patterns-design.md +283 -0
- package/skills/agentic/agentic-patterns/refs/anti-patterns-operational.md +216 -0
- package/skills/agentic/agentic-patterns/refs/pattern-catalog.md +329 -0
- package/skills/agentic/context-engineering/SKILL.md +142 -0
- package/skills/agentic/context-engineering/refs/caching-and-optimization.md +251 -0
- package/skills/agentic/context-engineering/refs/compression-techniques.md +171 -0
- package/skills/agentic/context-engineering/refs/cost-calculation-budget.md +250 -0
- package/skills/agentic/context-engineering/refs/cost-optimization-reporting.md +300 -0
- package/skills/agentic/context-engineering/refs/selective-loading.md +179 -0
- package/skills/agentic/frameworks/SKILL.md +191 -0
- package/skills/agentic/frameworks/refs/framework-profiles-1.md +209 -0
- package/skills/agentic/frameworks/refs/framework-profiles-2.md +214 -0
- package/skills/agentic/frameworks/refs/migration-patterns-paths.md +267 -0
- package/skills/agentic/frameworks/refs/migration-patterns-testing.md +208 -0
- package/skills/agentic/refs/audit.md +134 -0
- package/skills/agentic/refs/design.md +137 -0
- package/skills/agentic/refs/frameworks.md +102 -0
- package/skills/agentic/refs/review.md +160 -0
- package/skills/agentic/review-methodology/SKILL.md +186 -0
- package/skills/agentic/review-methodology/refs/deliverable-templates.md +246 -0
- package/skills/agentic/review-methodology/refs/issue-taxonomy-quality-testing.md +277 -0
- package/skills/agentic/review-methodology/refs/issue-taxonomy-reliability-safety.md +253 -0
- package/skills/agentic/trust-and-safety/SKILL.md +177 -0
- package/skills/agentic/trust-and-safety/refs/guardrails-actions.md +246 -0
- package/skills/agentic/trust-and-safety/refs/guardrails-input-output.md +238 -0
- package/skills/agentic/trust-and-safety/refs/guardrails-resources.md +209 -0
- package/skills/agentic/trust-and-safety/refs/safety-checklist-advanced.md +209 -0
- package/skills/agentic/trust-and-safety/refs/safety-checklist-core.md +215 -0
- package/skills/archetype/SKILL.md +104 -0
- package/skills/archetype/refs/build.md +142 -0
- package/skills/archetype/refs/decide.md +117 -0
- package/skills/archetype/refs/explore.md +66 -0
- package/skills/archetype/refs/incident.md +149 -0
- package/skills/archetype/refs/migrate.md +170 -0
- package/skills/archetype/refs/modernize.md +187 -0
- package/skills/archetype/refs/review.md +145 -0
- package/skills/archetype/refs/ship.md +117 -0
- package/skills/archetype/refs/specify.md +108 -0
- package/skills/archetype/refs/triage.md +62 -0
- package/skills/classify/SKILL.md +157 -0
- package/skills/data/analysis/SKILL.md +125 -0
- package/skills/data/analysis/refs/templates.md +170 -0
- package/skills/data/data/SKILL.md +118 -0
- package/skills/data/data/refs/examples.md +235 -0
- package/skills/data/ml/SKILL.md +125 -0
- package/skills/data/ml/refs/templates.md +172 -0
- package/skills/data/pipeline/SKILL.md +136 -0
- package/skills/data/pipeline/refs/templates.md +175 -0
- package/skills/data/refs/analyze.md +123 -0
- package/skills/data/refs/data.md +95 -0
- package/skills/data/refs/ml.md +118 -0
- package/skills/data/refs/pipeline.md +92 -0
- package/skills/deliberate/SKILL.md +125 -0
- package/skills/deliberate/refs/architecture-lens.md +37 -0
- package/skills/deliberate/refs/assumptions-lens.md +43 -0
- package/skills/deliberate/refs/code-lens.md +38 -0
- package/skills/deliberate/refs/content-lens.md +37 -0
- package/skills/deliberate/refs/data-lens.md +43 -0
- package/skills/deliberate/refs/design-lens.md +37 -0
- package/skills/deliberate/refs/opportunity-patterns.md +107 -0
- package/skills/deliberate/refs/ops-lens.md +43 -0
- package/skills/deliberate/refs/product-lens.md +46 -0
- package/skills/deliberate/refs/rethink-framework.md +77 -0
- package/skills/deliberate/refs/security-lens.md +47 -0
- package/skills/deliberate/refs/testing-lens.md +45 -0
- package/skills/deliberate/refs/timing-lens.md +42 -0
- package/skills/engineering/architecture/SKILL.md +177 -0
- package/skills/engineering/architecture/refs/adr-template.md +268 -0
- package/skills/engineering/architecture/refs/architecture-template-deployment.md +176 -0
- package/skills/engineering/architecture/refs/architecture-template-design.md +300 -0
- package/skills/engineering/architecture/refs/examples-ecommerce-analytics.md +299 -0
- package/skills/engineering/architecture/refs/examples-saas-trading.md +291 -0
- package/skills/engineering/architecture/refs/patterns-advanced.md +238 -0
- package/skills/engineering/architecture/refs/patterns-structural.md +303 -0
- package/skills/engineering/backend/SKILL.md +102 -0
- package/skills/engineering/backend/refs/checklists.md +146 -0
- package/skills/engineering/backend/refs/patterns.md +181 -0
- package/skills/engineering/debugging/SKILL.md +99 -0
- package/skills/engineering/debugging/refs/patterns.md +248 -0
- package/skills/engineering/debugging/refs/process.md +203 -0
- package/skills/engineering/docs/SKILL.md +97 -0
- package/skills/engineering/engineering/SKILL.md +88 -0
- package/skills/engineering/engineering/refs/checklists.md +91 -0
- package/skills/engineering/engineering/refs/templates.md +71 -0
- package/skills/engineering/frontend/SKILL.md +92 -0
- package/skills/engineering/frontend/refs/checklists.md +96 -0
- package/skills/engineering/frontend/refs/patterns.md +167 -0
- package/skills/engineering/integration/SKILL.md +183 -0
- package/skills/engineering/integration/refs/error-handling-best-practices.md +139 -0
- package/skills/engineering/integration/refs/error-handling-error-classes-typescript.md +212 -0
- package/skills/engineering/integration/refs/error-handling-error-response-format.md +301 -0
- package/skills/engineering/integration/refs/error-handling-retry-strategies.md +190 -0
- package/skills/engineering/integration/refs/event-schemas-best-practices.md +225 -0
- package/skills/engineering/integration/refs/event-schemas-event-naming-conventions.md +303 -0
- package/skills/engineering/integration/refs/event-schemas-event-structure.md +288 -0
- package/skills/engineering/integration/refs/graphql-best-practices.md +138 -0
- package/skills/engineering/integration/refs/graphql-error-handling.md +198 -0
- package/skills/engineering/integration/refs/graphql-graphql-basics.md +197 -0
- package/skills/engineering/integration/refs/graphql-query-examples.md +226 -0
- package/skills/engineering/integration/refs/graphql-schema-design-patterns.md +280 -0
- package/skills/engineering/integration/refs/grpc-grpc-overview.md +246 -0
- package/skills/engineering/integration/refs/grpc-implementation-examples.md +269 -0
- package/skills/engineering/integration/refs/grpc-metadata-headers.md +304 -0
- package/skills/engineering/integration/refs/grpc-testing.md +49 -0
- package/skills/engineering/integration/refs/openapi-template.yaml +682 -0
- package/skills/engineering/integration/refs/resilience-best-practices.md +109 -0
- package/skills/engineering/integration/refs/resilience-circuit-breaker-pattern.md +302 -0
- package/skills/engineering/integration/refs/resilience-rate-limiting.md +245 -0
- package/skills/engineering/integration/refs/resilience-timeout-pattern.md +212 -0
- package/skills/engineering/integration/refs/rest-api-fundamentals.md +299 -0
- package/skills/engineering/integration/refs/rest-api-responses-security.md +302 -0
- package/skills/engineering/integration/refs/versioning-breaking-changes.md +237 -0
- package/skills/engineering/integration/refs/versioning-migration-strategies.md +257 -0
- package/skills/engineering/integration/refs/versioning-versioning-strategies.md +296 -0
- package/skills/engineering/large-scale-migration/SKILL.md +65 -0
- package/skills/engineering/patch/SKILL.md +184 -0
- package/skills/engineering/refs/arch.md +126 -0
- package/skills/engineering/refs/debug.md +142 -0
- package/skills/engineering/refs/docs.md +190 -0
- package/skills/engineering/refs/plan.md +109 -0
- package/skills/engineering/refs/review.md +138 -0
- package/skills/engineering/system-design/SKILL.md +154 -0
- package/skills/engineering/system-design/refs/anti-patterns-big-ball-of-mud.md +254 -0
- package/skills/engineering/system-design/refs/anti-patterns-circular-dependencies.md +241 -0
- package/skills/engineering/system-design/refs/anti-patterns-feature-envy.md +168 -0
- package/skills/engineering/system-design/refs/anti-patterns-god-object.md +247 -0
- package/skills/engineering/system-design/refs/component-template-lifecycle.md +210 -0
- package/skills/engineering/system-design/refs/component-template-maintenance.md +219 -0
- package/skills/engineering/system-design/refs/component-template-structure.md +276 -0
- package/skills/engineering/system-design/refs/dependencies-best-practices.md +260 -0
- package/skills/engineering/system-design/refs/dependencies-dependency-principles.md +295 -0
- package/skills/engineering/system-design/refs/dependencies-dependency-tracking.md +261 -0
- package/skills/engineering/system-design/refs/interface-template-lifecycle.md +211 -0
- package/skills/engineering/system-design/refs/interface-template-maintenance.md +212 -0
- package/skills/engineering/system-design/refs/interface-template-structure.md +269 -0
- package/skills/engineering/system-design/refs/patterns-dependency-injection.md +151 -0
- package/skills/engineering/system-design/refs/patterns-facade-pattern.md +257 -0
- package/skills/engineering/system-design/refs/patterns-layered-pattern.md +204 -0
- package/skills/engineering/system-design/refs/patterns-plugin-architecture.md +229 -0
- package/skills/engineering/unit-test-quality/SKILL.md +194 -0
- package/skills/ground/SKILL.md +130 -0
- package/skills/integration-discovery/SKILL.md +168 -0
- package/skills/integration-discovery/refs/capabilities.md +282 -0
- package/skills/integration-discovery/refs/cli-detection.md +295 -0
- package/skills/integration-discovery/refs/discovery-sources.md +233 -0
- package/skills/integration-discovery/refs/task-patterns.md +206 -0
- package/skills/jam/SKILL.md +45 -0
- package/skills/jam/refs/facilitation-patterns.md +125 -0
- package/skills/jam/refs/quick.md +64 -0
- package/skills/jam/refs/synthesis-patterns.md +114 -0
- package/skills/multi-model/SKILL.md +168 -0
- package/skills/multi-model/refs/aichat.md +119 -0
- package/skills/multi-model/refs/aider.md +100 -0
- package/skills/multi-model/refs/auditability.md +238 -0
- package/skills/multi-model/refs/codex.md +169 -0
- package/skills/multi-model/refs/context.md +306 -0
- package/skills/multi-model/refs/copilot.md +161 -0
- package/skills/multi-model/refs/examples.md +224 -0
- package/skills/multi-model/refs/gemini.md +185 -0
- package/skills/multi-model/refs/goose.md +112 -0
- package/skills/multi-model/refs/llm.md +124 -0
- package/skills/multi-model/refs/opencode.md +196 -0
- package/skills/multi-model/refs/orchestration.md +188 -0
- package/skills/multi-model/refs/pi.md +208 -0
- package/skills/persona/SKILL.md +144 -0
- package/skills/persona/refs/define.md +133 -0
- package/skills/platform/actions/refs/actions.md +114 -0
- package/skills/platform/audit/SKILL.md +174 -0
- package/skills/platform/audit/refs/audit.md +145 -0
- package/skills/platform/audit/refs/checklists-evidence-operations.md +144 -0
- package/skills/platform/audit/refs/checklists-gdpr-pci-evidence.md +213 -0
- package/skills/platform/audit/refs/checklists-soc2-hipaa.md +120 -0
- package/skills/platform/audit/refs/frameworks-gdpr-pci.md +225 -0
- package/skills/platform/audit/refs/frameworks-soc2-hipaa.md +228 -0
- package/skills/platform/compliance/SKILL.md +169 -0
- package/skills/platform/compliance/refs/checklists.md +303 -0
- package/skills/platform/compliance/refs/compliance.md +117 -0
- package/skills/platform/compliance/refs/frameworks.md +282 -0
- package/skills/platform/errors/SKILL.md +183 -0
- package/skills/platform/errors/refs/patterns.md +271 -0
- package/skills/platform/errors/refs/severity.md +264 -0
- package/skills/platform/gate-benchmark-rebaseline/SKILL.md +108 -0
- package/skills/platform/gh/refs/gh.md +105 -0
- package/skills/platform/gh-cli/SKILL.md +120 -0
- package/skills/platform/gh-cli/refs/automation.md +63 -0
- package/skills/platform/gh-cli/refs/gh-ops.md +103 -0
- package/skills/platform/gh-cli/refs/patterns.md +132 -0
- package/skills/platform/gh-cli/scripts/gh_ops.py +454 -0
- package/skills/platform/github-actions/SKILL.md +159 -0
- package/skills/platform/github-actions/refs/security.md +150 -0
- package/skills/platform/github-actions/refs/templates.md +230 -0
- package/skills/platform/github-actions/refs/troubleshooting.md +174 -0
- package/skills/platform/github-actions/scripts/workflow_gen.py +215 -0
- package/skills/platform/gitlab-ci/SKILL.md +140 -0
- package/skills/platform/gitlab-ci/refs/templates.md +223 -0
- package/skills/platform/gitlab-ci/refs/troubleshooting.md +213 -0
- package/skills/platform/glab-cli/SKILL.md +133 -0
- package/skills/platform/glab-cli/refs/ci-debugging.md +95 -0
- package/skills/platform/glab-cli/refs/glab-ops.md +78 -0
- package/skills/platform/glab-cli/refs/patterns.md +131 -0
- package/skills/platform/glab-cli/scripts/glab_ops.py +313 -0
- package/skills/platform/health/SKILL.md +177 -0
- package/skills/platform/health/refs/health.md +72 -0
- package/skills/platform/health/refs/sources.md +302 -0
- package/skills/platform/incident/refs/incident.md +136 -0
- package/skills/platform/infra/refs/infra.md +104 -0
- package/skills/platform/observability/SKILL.md +128 -0
- package/skills/platform/observability/refs/toolchain-discovery.md +243 -0
- package/skills/platform/prereq-doctor/SKILL.md +57 -0
- package/skills/platform/prereq-doctor/refs/tool-registry.md +58 -0
- package/skills/platform/traces/refs/traces.md +101 -0
- package/skills/product/acceptance-criteria/SKILL.md +193 -0
- package/skills/product/acceptance-criteria/refs/ac-anti-patterns.md +291 -0
- package/skills/product/acceptance-criteria/refs/ac-examples.md +277 -0
- package/skills/product/accessibility/SKILL.md +197 -0
- package/skills/product/accessibility/refs/aria-patterns-basics.md +250 -0
- package/skills/product/accessibility/refs/aria-patterns-dynamic.md +187 -0
- package/skills/product/accessibility/refs/aria-patterns-interactive.md +246 -0
- package/skills/product/accessibility/refs/audit-report-findings.md +248 -0
- package/skills/product/accessibility/refs/audit-report-results.md +276 -0
- package/skills/product/accessibility/refs/common-violations-html-css.md +270 -0
- package/skills/product/accessibility/refs/common-violations-interactive.md +303 -0
- package/skills/product/accessibility/refs/keyboard-testing-basics.md +222 -0
- package/skills/product/accessibility/refs/keyboard-testing-patterns.md +173 -0
- package/skills/product/accessibility/refs/screen-reader-testing-advanced.md +294 -0
- package/skills/product/accessibility/refs/screen-reader-testing-commands.md +255 -0
- package/skills/product/accessibility/refs/wcag-checklist.md +262 -0
- package/skills/product/analyze/SKILL.md +164 -0
- package/skills/product/analyze/refs/algorithms.md +238 -0
- package/skills/product/analyze/refs/sentiment-patterns.md +180 -0
- package/skills/product/imagery/SKILL.md +97 -0
- package/skills/product/imagery/alter/SKILL.md +105 -0
- package/skills/product/imagery/alter/refs/editing-reference.md +146 -0
- package/skills/product/imagery/create/SKILL.md +109 -0
- package/skills/product/imagery/create/refs/prompt-engineering.md +112 -0
- package/skills/product/imagery/create/refs/provider-reference.md +219 -0
- package/skills/product/imagery/review/SKILL.md +101 -0
- package/skills/product/imagery/review/refs/analysis-lenses.md +136 -0
- package/skills/product/imagery/review/refs/quality-gates.md +153 -0
- package/skills/product/imagery/scripts/analyze.sh +23 -0
- package/skills/product/imagery/scripts/edit.sh +14 -0
- package/skills/product/imagery/scripts/generate.sh +13 -0
- package/skills/product/imagery/scripts/provider.py +787 -0
- package/skills/product/listen/SKILL.md +175 -0
- package/skills/product/listen/refs/channels.md +220 -0
- package/skills/product/mockup/SKILL.md +106 -0
- package/skills/product/mockup/refs/templates.md +95 -0
- package/skills/product/refs/a11y.md +109 -0
- package/skills/product/refs/acceptance.md +79 -0
- package/skills/product/refs/align.md +67 -0
- package/skills/product/refs/analyze.md +77 -0
- package/skills/product/refs/elicit.md +67 -0
- package/skills/product/refs/listen.md +58 -0
- package/skills/product/refs/mockup.md +77 -0
- package/skills/product/refs/screenshot.md +59 -0
- package/skills/product/refs/strategy.md +77 -0
- package/skills/product/refs/synthesize.md +54 -0
- package/skills/product/refs/ux-review.md +68 -0
- package/skills/product/refs/ux.md +101 -0
- package/skills/product/requirements-analysis/SKILL.md +136 -0
- package/skills/product/requirements-analysis/refs/requirements-example-auth.md +264 -0
- package/skills/product/requirements-analysis/refs/requirements-example-export-integration.md +88 -0
- package/skills/product/requirements-analysis/refs/requirements-output-format-template.md +247 -0
- package/skills/product/requirements-analysis/refs/user-story-guide.md +296 -0
- package/skills/product/requirements-graph/SKILL.md +155 -0
- package/skills/product/requirements-graph/refs/examples.md +223 -0
- package/skills/product/requirements-graph/refs/schema.md +162 -0
- package/skills/product/requirements-migrate/SKILL.md +90 -0
- package/skills/product/requirements-navigate/SKILL.md +141 -0
- package/skills/product/requirements-navigate/refs/meta-templates.md +141 -0
- package/skills/product/screenshot/SKILL.md +147 -0
- package/skills/product/strategy/SKILL.md +198 -0
- package/skills/product/strategy/refs/frameworks.md +295 -0
- package/skills/product/strategy/refs/templates-competitive-market.md +299 -0
- package/skills/product/strategy/refs/templates-roi-value.md +207 -0
- package/skills/product/synthesize/SKILL.md +116 -0
- package/skills/product/synthesize/refs/journey-mapping.md +212 -0
- package/skills/product/synthesize/refs/prioritization.md +193 -0
- package/skills/product/ux-review/SKILL.md +164 -0
- package/skills/product/visual-direction/SKILL.md +137 -0
- package/skills/product/visual-review/SKILL.md +131 -0
- package/skills/runtime-exec/SKILL.md +138 -0
- package/skills/search/codebase-narrator/SKILL.md +131 -0
- package/skills/search/codebase-narrator/refs/output-template.md +100 -0
- package/skills/smaht/SKILL.md +72 -0
- package/skills/smaht/discovery/SKILL.md +65 -0
- package/skills/smaht/intent/SKILL.md +89 -0
- package/skills/smaht/propose-skills/SKILL.md +142 -0
- package/skills/smaht/refs/state.md +76 -0
- package/skills/swarm/SKILL.md +132 -0
- package/skills/swarm/refs/fan-out.md +104 -0
- package/skills/swarm/refs/independent-verification.md +86 -0
- package/skills/swarm/refs/receipts-and-evidence.md +69 -0
- package/skills/swarm/refs/recon-synthesis.md +87 -0
- package/skills/swarm/refs/ship-discipline.md +90 -0
- package/skills/wickedizer/SKILL.md +147 -0
- package/skills/wickedizer/refs/artifact-types.md +238 -0
- package/skills/wickedizer/refs/complexity.md +278 -0
- package/skills/wickedizer/refs/de-ai-pass.md +140 -0
- package/skills/wickedizer/refs/failure-modes.md +249 -0
- package/skills/wickedizer/refs/patterns.md +282 -0
- package/skills/wickedizer/refs/review.md +221 -0
- package/skills/wickedizer/refs/structure.md +282 -0
- package/skills/wickedizer/refs/voice-profile.md +125 -0
- package/skills/workflow/SKILL.md +132 -0
- package/skills/workflow/refs/archetype-adjustments.md +205 -0
- package/skills/workflow/refs/evidence.md +58 -0
- package/skills/workflow/refs/integration.md +130 -0
- package/skills/workflow/refs/risk-dimension-signals.md +260 -0
- package/skills/workflow/refs/scoring-rubric.md +49 -0
- package/skills/workflow/refs/signal-keywords.md +176 -0
- package/skills/workflow/refs/specialist-routing-rules.md +159 -0
- package/skills/worktrees/SKILL.md +87 -0
- package/skills/worktrees/refs/recipes.md +96 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""scope_delta.py — Compute scope-delta metrics for the clarify HITL gate.
|
|
3
|
+
|
|
4
|
+
Issue #847: the facilitator's clarify HITL gate previously fired only on
|
|
5
|
+
intent ambiguity (the six triggers in ``skills/propose-process/refs/
|
|
6
|
+
ambiguity.md``). It missed the failure where the rubric silently
|
|
7
|
+
expanded a 24-issue wave plan to absorb a 1M-insertion sibling project
|
|
8
|
+
because no trigger looked at scope-size deltas.
|
|
9
|
+
|
|
10
|
+
This helper computes the four scope-delta metrics that drive the
|
|
11
|
+
seventh trigger added in #847:
|
|
12
|
+
|
|
13
|
+
- ``new_items``: items in the proposed plan not present in the
|
|
14
|
+
baseline plan (matched by id).
|
|
15
|
+
- ``oversize_factor``: ratio of the largest new item's size to the
|
|
16
|
+
median size of items already in the baseline plan. Trips the trigger
|
|
17
|
+
at >= 3.0.
|
|
18
|
+
- ``total_size_ratio``: total proposed-plan size / baseline-plan size.
|
|
19
|
+
Trips the trigger at >= 2.0.
|
|
20
|
+
- ``epic_or_project_added``: True iff any new item carries a label
|
|
21
|
+
matching the epic/project regex.
|
|
22
|
+
|
|
23
|
+
Stdlib-only; designed for the propose-process facilitator agent or
|
|
24
|
+
the slim caller to consume programmatically. Returns a dict; the
|
|
25
|
+
facilitator agent should surface the trigger as a clarifying question
|
|
26
|
+
when any threshold is crossed (per ambiguity.md trigger #7).
|
|
27
|
+
|
|
28
|
+
The "size" of an item is whatever the caller measures consistently —
|
|
29
|
+
LOC, file count, sub-task count, or a fused score. The helper does not
|
|
30
|
+
prescribe a unit; it just compares apples to apples.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import argparse
|
|
36
|
+
import json
|
|
37
|
+
import re
|
|
38
|
+
import sys
|
|
39
|
+
from typing import Any, Dict, Iterable, List, Optional
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Issue #847: items labeled with these patterns are project-sized by
|
|
43
|
+
# convention and must not be silently absorbed into a wave plan.
|
|
44
|
+
EPIC_LABEL_PATTERNS = (
|
|
45
|
+
re.compile(r"^epic$", re.IGNORECASE),
|
|
46
|
+
re.compile(r"^project$", re.IGNORECASE),
|
|
47
|
+
re.compile(r"^large$", re.IGNORECASE),
|
|
48
|
+
re.compile(r"^xlarge$", re.IGNORECASE),
|
|
49
|
+
re.compile(r"^xxl$", re.IGNORECASE),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Threshold defaults — surfaced as constants so callers can override
|
|
53
|
+
# but the trigger doctrine in ambiguity.md stays in sync.
|
|
54
|
+
DEFAULT_OVERSIZE_FACTOR_TRIP = 3.0
|
|
55
|
+
DEFAULT_TOTAL_SIZE_RATIO_TRIP = 2.0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _median(values: List[float]) -> float:
|
|
59
|
+
if not values:
|
|
60
|
+
return 0.0
|
|
61
|
+
sorted_values = sorted(values)
|
|
62
|
+
mid = len(sorted_values) // 2
|
|
63
|
+
if len(sorted_values) % 2 == 1:
|
|
64
|
+
return float(sorted_values[mid])
|
|
65
|
+
return (sorted_values[mid - 1] + sorted_values[mid]) / 2.0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _has_epic_label(labels: Optional[Iterable[str]]) -> bool:
|
|
69
|
+
if not labels:
|
|
70
|
+
return False
|
|
71
|
+
for label in labels:
|
|
72
|
+
if not isinstance(label, str):
|
|
73
|
+
continue
|
|
74
|
+
for pattern in EPIC_LABEL_PATTERNS:
|
|
75
|
+
if pattern.match(label.strip()):
|
|
76
|
+
return True
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _normalize_item(raw: Any) -> Dict[str, Any]:
|
|
81
|
+
"""Coerce a raw item entry to the canonical shape.
|
|
82
|
+
|
|
83
|
+
Accepts either a dict ({"id", "size", "labels"}) or a bare string
|
|
84
|
+
(treated as id with size=1, no labels). Anything else is dropped.
|
|
85
|
+
"""
|
|
86
|
+
if isinstance(raw, dict):
|
|
87
|
+
item_id = raw.get("id") or raw.get("name") or raw.get("title")
|
|
88
|
+
size = raw.get("size", 1)
|
|
89
|
+
labels = raw.get("labels", [])
|
|
90
|
+
if item_id is None:
|
|
91
|
+
return {}
|
|
92
|
+
try:
|
|
93
|
+
size = float(size)
|
|
94
|
+
except (TypeError, ValueError):
|
|
95
|
+
size = 1.0
|
|
96
|
+
# Labels normalization (Gemini PR #860 review): a bare string must
|
|
97
|
+
# become a single-element list, not be decomposed by list("epic")
|
|
98
|
+
# into ['e','p','i','c']. A non-iterable type (int, None, etc.)
|
|
99
|
+
# falls back to []. Only str/list/tuple yield meaningful labels.
|
|
100
|
+
if isinstance(labels, str):
|
|
101
|
+
labels_list = [labels]
|
|
102
|
+
elif isinstance(labels, (list, tuple)):
|
|
103
|
+
labels_list = list(labels)
|
|
104
|
+
else:
|
|
105
|
+
labels_list = []
|
|
106
|
+
return {"id": str(item_id), "size": size, "labels": labels_list}
|
|
107
|
+
if isinstance(raw, str):
|
|
108
|
+
return {"id": raw, "size": 1.0, "labels": []}
|
|
109
|
+
return {}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def compute_scope_delta(
|
|
113
|
+
baseline: List[Any],
|
|
114
|
+
proposed: List[Any],
|
|
115
|
+
*,
|
|
116
|
+
oversize_factor_trip: float = DEFAULT_OVERSIZE_FACTOR_TRIP,
|
|
117
|
+
total_size_ratio_trip: float = DEFAULT_TOTAL_SIZE_RATIO_TRIP,
|
|
118
|
+
) -> Dict[str, Any]:
|
|
119
|
+
"""Compute scope-delta metrics for the clarify HITL gate (#847).
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
baseline: List of items the user explicitly named in the
|
|
123
|
+
original ask. Each entry is a dict
|
|
124
|
+
``{"id": str, "size": float, "labels": [str]}`` or a bare
|
|
125
|
+
string id.
|
|
126
|
+
proposed: List of items in the proposed plan, same shape.
|
|
127
|
+
oversize_factor_trip: Threshold at or above which the
|
|
128
|
+
oversize_factor trips the trigger (default 3.0).
|
|
129
|
+
total_size_ratio_trip: Threshold at or above which the
|
|
130
|
+
total_size_ratio trips the trigger (default 2.0).
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Dict with:
|
|
134
|
+
- ``new_items``: list of {id, size, labels} for items in
|
|
135
|
+
proposed but not baseline.
|
|
136
|
+
- ``baseline_median_size`` / ``baseline_total_size``.
|
|
137
|
+
- ``proposed_total_size``.
|
|
138
|
+
- ``oversize_factor``: max(new_item.size) / baseline_median_size,
|
|
139
|
+
or None when baseline_median_size is 0.
|
|
140
|
+
- ``total_size_ratio``: proposed_total / baseline_total, or
|
|
141
|
+
None when baseline_total is 0.
|
|
142
|
+
- ``epic_or_project_added``: True iff any new item has an
|
|
143
|
+
epic-class label.
|
|
144
|
+
- ``triggers``: ordered list of human-readable strings naming
|
|
145
|
+
which thresholds tripped. Empty list = no trigger fired.
|
|
146
|
+
|
|
147
|
+
The trigger fires whenever ``triggers`` is non-empty.
|
|
148
|
+
"""
|
|
149
|
+
base = [it for it in (_normalize_item(x) for x in baseline) if it]
|
|
150
|
+
prop = [it for it in (_normalize_item(x) for x in proposed) if it]
|
|
151
|
+
|
|
152
|
+
base_ids = {it["id"] for it in base}
|
|
153
|
+
new_items = [it for it in prop if it["id"] not in base_ids]
|
|
154
|
+
|
|
155
|
+
baseline_sizes = [it["size"] for it in base]
|
|
156
|
+
baseline_median = _median(baseline_sizes)
|
|
157
|
+
baseline_total = sum(baseline_sizes)
|
|
158
|
+
proposed_total = sum(it["size"] for it in prop)
|
|
159
|
+
|
|
160
|
+
if new_items and baseline_median > 0:
|
|
161
|
+
max_new_size = max(it["size"] for it in new_items)
|
|
162
|
+
oversize_factor = max_new_size / baseline_median
|
|
163
|
+
else:
|
|
164
|
+
oversize_factor = None
|
|
165
|
+
|
|
166
|
+
if baseline_total > 0:
|
|
167
|
+
total_size_ratio = proposed_total / baseline_total
|
|
168
|
+
else:
|
|
169
|
+
total_size_ratio = None
|
|
170
|
+
|
|
171
|
+
epic_added = any(_has_epic_label(it.get("labels")) for it in new_items)
|
|
172
|
+
|
|
173
|
+
triggers: List[str] = []
|
|
174
|
+
if oversize_factor is not None and oversize_factor >= oversize_factor_trip:
|
|
175
|
+
triggers.append(
|
|
176
|
+
f"oversize-factor: largest new item is {oversize_factor:.1f}x "
|
|
177
|
+
f"the baseline median (trip threshold: {oversize_factor_trip}x)"
|
|
178
|
+
)
|
|
179
|
+
if total_size_ratio is not None and total_size_ratio >= total_size_ratio_trip:
|
|
180
|
+
triggers.append(
|
|
181
|
+
f"total-size-ratio: proposed plan is {total_size_ratio:.1f}x "
|
|
182
|
+
f"the baseline total size (trip threshold: {total_size_ratio_trip}x)"
|
|
183
|
+
)
|
|
184
|
+
if epic_added:
|
|
185
|
+
epic_ids = [
|
|
186
|
+
it["id"] for it in new_items if _has_epic_label(it.get("labels"))
|
|
187
|
+
]
|
|
188
|
+
triggers.append(
|
|
189
|
+
f"epic-or-project-label: new item(s) {epic_ids} carry an "
|
|
190
|
+
"epic/project-class label and should be planned separately"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
"new_items": new_items,
|
|
195
|
+
"baseline_median_size": baseline_median,
|
|
196
|
+
"baseline_total_size": baseline_total,
|
|
197
|
+
"proposed_total_size": proposed_total,
|
|
198
|
+
"oversize_factor": oversize_factor,
|
|
199
|
+
"total_size_ratio": total_size_ratio,
|
|
200
|
+
"epic_or_project_added": epic_added,
|
|
201
|
+
"triggers": triggers,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _cli() -> None:
|
|
206
|
+
parser = argparse.ArgumentParser(
|
|
207
|
+
description=(
|
|
208
|
+
"Compute scope-delta metrics for the clarify HITL gate "
|
|
209
|
+
"(Issue #847). Pass --baseline and --proposed as JSON file "
|
|
210
|
+
"paths or '-' to read from stdin."
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
parser.add_argument("--baseline", required=True,
|
|
214
|
+
help="Path to JSON file with baseline items, or '-' for stdin.")
|
|
215
|
+
parser.add_argument("--proposed", required=True,
|
|
216
|
+
help="Path to JSON file with proposed items, or '-' for stdin (mutually exclusive with --baseline -).")
|
|
217
|
+
parser.add_argument("--oversize-factor-trip", type=float,
|
|
218
|
+
default=DEFAULT_OVERSIZE_FACTOR_TRIP,
|
|
219
|
+
help=f"Override oversize-factor trip (default {DEFAULT_OVERSIZE_FACTOR_TRIP}).")
|
|
220
|
+
parser.add_argument("--total-size-ratio-trip", type=float,
|
|
221
|
+
default=DEFAULT_TOTAL_SIZE_RATIO_TRIP,
|
|
222
|
+
help=f"Override total-size-ratio trip (default {DEFAULT_TOTAL_SIZE_RATIO_TRIP}).")
|
|
223
|
+
parser.add_argument("--exit-nonzero-on-trip", action="store_true",
|
|
224
|
+
help="Exit 1 if any trigger fires.")
|
|
225
|
+
args = parser.parse_args()
|
|
226
|
+
|
|
227
|
+
def _load(path: str) -> List[Any]:
|
|
228
|
+
if path == "-":
|
|
229
|
+
return json.loads(sys.stdin.read())
|
|
230
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
231
|
+
return json.load(fh)
|
|
232
|
+
|
|
233
|
+
# Copilot PR #860 review: stdin can only be consumed once. Reject the
|
|
234
|
+
# ambiguous '--baseline - --proposed -' case explicitly so a second
|
|
235
|
+
# empty read doesn't surface as a confusing JSONDecodeError.
|
|
236
|
+
if args.baseline == "-" and args.proposed == "-":
|
|
237
|
+
print(
|
|
238
|
+
"Error: --baseline and --proposed cannot BOTH be '-'. "
|
|
239
|
+
"stdin can only be consumed once. Pass at most one of them as "
|
|
240
|
+
"'-' and the other as a file path.",
|
|
241
|
+
file=sys.stderr,
|
|
242
|
+
)
|
|
243
|
+
sys.exit(2)
|
|
244
|
+
|
|
245
|
+
baseline = _load(args.baseline)
|
|
246
|
+
proposed = _load(args.proposed)
|
|
247
|
+
if not isinstance(baseline, list) or not isinstance(proposed, list):
|
|
248
|
+
print("Error: baseline and proposed must each be a JSON array.",
|
|
249
|
+
file=sys.stderr)
|
|
250
|
+
sys.exit(2)
|
|
251
|
+
|
|
252
|
+
result = compute_scope_delta(
|
|
253
|
+
baseline, proposed,
|
|
254
|
+
oversize_factor_trip=args.oversize_factor_trip,
|
|
255
|
+
total_size_ratio_trip=args.total_size_ratio_trip,
|
|
256
|
+
)
|
|
257
|
+
json.dump(result, sys.stdout, indent=2, default=str)
|
|
258
|
+
sys.stdout.write("\n")
|
|
259
|
+
if args.exit_nonzero_on_trip and result["triggers"]:
|
|
260
|
+
sys.exit(1)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
if __name__ == "__main__":
|
|
264
|
+
_cli()
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Specialist name resolver for the wicked-garden plugin (Issue #573).
|
|
3
|
+
|
|
4
|
+
Three naming systems used to collide for the same concept:
|
|
5
|
+
|
|
6
|
+
1. ``.claude-plugin/specialist.json`` lists the **domains**
|
|
7
|
+
(``engineering``, ``platform``, ``product``, ``qe``, ``data``,
|
|
8
|
+
``jam``, ``agentic``).
|
|
9
|
+
2. The facilitator rubric (``skills/propose-process/SKILL.md`` Step 4)
|
|
10
|
+
picks bare **role** names like ``requirements-analyst`` or
|
|
11
|
+
``solution-architect``.
|
|
12
|
+
3. Actual subagents are keyed ``wicked-garden:{domain}:{role}`` — for
|
|
13
|
+
example ``wicked-garden:product:requirements-analyst``.
|
|
14
|
+
|
|
15
|
+
This module is the single source of truth that bridges the three. It
|
|
16
|
+
walks ``agents/**/*.md``, parses the YAML-like frontmatter by simple
|
|
17
|
+
line scanning (stdlib only — hooks can import it), and returns lookup
|
|
18
|
+
maps keyed by both bare role and full subagent_type.
|
|
19
|
+
|
|
20
|
+
Public API:
|
|
21
|
+
build_resolver(plugin_root: Path) -> dict
|
|
22
|
+
resolve_role(role_name: str, resolver: dict) -> tuple[str, str] | tuple[None, None]
|
|
23
|
+
|
|
24
|
+
The builder is cached per ``plugin_root`` string so repeated calls from
|
|
25
|
+
hooks or scripts do not re-walk the agent tree.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import functools
|
|
31
|
+
import json
|
|
32
|
+
import re
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Optional, Tuple
|
|
35
|
+
|
|
36
|
+
# The canonical subagent_type prefix used throughout the plugin. Callers
|
|
37
|
+
# may pass either a bare role (``requirements-analyst``) or the full
|
|
38
|
+
# prefixed form — the resolver handles both.
|
|
39
|
+
_SUBAGENT_PREFIX = "wicked-garden:"
|
|
40
|
+
|
|
41
|
+
# Frontmatter fences: a leading ``---`` starts the YAML-like block and a
|
|
42
|
+
# trailing ``---`` closes it. We stop at the second fence so nothing in
|
|
43
|
+
# the agent body can accidentally masquerade as frontmatter.
|
|
44
|
+
_FRONTMATTER_FENCE = "---"
|
|
45
|
+
|
|
46
|
+
# Only these two keys are consumed. We deliberately do not pull in a YAML
|
|
47
|
+
# library — hooks are stdlib-only, and the frontmatter in this repo is
|
|
48
|
+
# always a flat, top-level ``key: value`` pattern for these two fields.
|
|
49
|
+
_RE_NAME = re.compile(r"^name:\s*(.+?)\s*$")
|
|
50
|
+
_RE_SUBAGENT_TYPE = re.compile(r"^subagent_type:\s*(.+?)\s*$")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _parse_agent_frontmatter(path: Path) -> dict:
|
|
54
|
+
"""Return ``{"name": ..., "subagent_type": ...}`` for an agent file.
|
|
55
|
+
|
|
56
|
+
Missing keys are omitted from the returned dict (callers treat that
|
|
57
|
+
as "skip this file"). Unreadable files return ``{}``.
|
|
58
|
+
|
|
59
|
+
This is NOT a general YAML parser. It scans line-by-line between the
|
|
60
|
+
first two ``---`` fences, matching only the two keys we care about.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
text = path.read_text(encoding="utf-8")
|
|
64
|
+
except OSError:
|
|
65
|
+
return {}
|
|
66
|
+
|
|
67
|
+
lines = text.splitlines()
|
|
68
|
+
if not lines or lines[0].strip() != _FRONTMATTER_FENCE:
|
|
69
|
+
return {}
|
|
70
|
+
|
|
71
|
+
result: dict = {}
|
|
72
|
+
for line in lines[1:]:
|
|
73
|
+
stripped = line.strip()
|
|
74
|
+
if stripped == _FRONTMATTER_FENCE:
|
|
75
|
+
break
|
|
76
|
+
name_match = _RE_NAME.match(line)
|
|
77
|
+
if name_match and "name" not in result:
|
|
78
|
+
result["name"] = name_match.group(1).strip()
|
|
79
|
+
continue
|
|
80
|
+
subagent_match = _RE_SUBAGENT_TYPE.match(line)
|
|
81
|
+
if subagent_match and "subagent_type" not in result:
|
|
82
|
+
result["subagent_type"] = subagent_match.group(1).strip()
|
|
83
|
+
continue
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _load_domains(plugin_root: Path) -> list:
|
|
88
|
+
"""Return the list of specialist-domain names from specialist.json.
|
|
89
|
+
|
|
90
|
+
Returns an empty list on any error so the resolver stays usable even
|
|
91
|
+
if the manifest is briefly malformed during development.
|
|
92
|
+
"""
|
|
93
|
+
manifest = plugin_root / ".claude-plugin" / "specialist.json"
|
|
94
|
+
try:
|
|
95
|
+
data = json.loads(manifest.read_text(encoding="utf-8"))
|
|
96
|
+
except (OSError, json.JSONDecodeError):
|
|
97
|
+
return []
|
|
98
|
+
names = []
|
|
99
|
+
for entry in data.get("specialists", []):
|
|
100
|
+
if isinstance(entry, dict) and entry.get("name"):
|
|
101
|
+
names.append(entry["name"])
|
|
102
|
+
return names
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@functools.lru_cache(maxsize=32)
|
|
106
|
+
def _build_resolver_cached(plugin_root_str: str) -> dict:
|
|
107
|
+
"""Cached worker — keyed on the stringified plugin root."""
|
|
108
|
+
plugin_root = Path(plugin_root_str)
|
|
109
|
+
agents_dir = plugin_root / "agents"
|
|
110
|
+
|
|
111
|
+
role_to_subagent: dict = {}
|
|
112
|
+
role_to_domain: dict = {}
|
|
113
|
+
subagent_to_role: dict = {}
|
|
114
|
+
warnings: list = []
|
|
115
|
+
|
|
116
|
+
if agents_dir.is_dir():
|
|
117
|
+
# Sort for deterministic collision resolution: earlier (alphabetical
|
|
118
|
+
# by path) wins, later entries become warnings. ``rglob`` order is
|
|
119
|
+
# filesystem-dependent on some platforms, so we sort explicitly.
|
|
120
|
+
for agent_path in sorted(agents_dir.rglob("*.md")):
|
|
121
|
+
meta = _parse_agent_frontmatter(agent_path)
|
|
122
|
+
name = meta.get("name")
|
|
123
|
+
subagent_type = meta.get("subagent_type")
|
|
124
|
+
if not name or not subagent_type:
|
|
125
|
+
continue
|
|
126
|
+
# The domain is the middle segment of wicked-garden:{domain}:{role}
|
|
127
|
+
# — fall back to the parent directory name if the subagent_type
|
|
128
|
+
# is malformed (keeps lookup working even for drifted files).
|
|
129
|
+
if subagent_type.startswith(_SUBAGENT_PREFIX):
|
|
130
|
+
tail = subagent_type[len(_SUBAGENT_PREFIX):]
|
|
131
|
+
parts = tail.split(":", 1)
|
|
132
|
+
domain = parts[0] if len(parts) == 2 else agent_path.parent.name
|
|
133
|
+
else:
|
|
134
|
+
domain = agent_path.parent.name
|
|
135
|
+
|
|
136
|
+
if name in role_to_subagent:
|
|
137
|
+
existing = role_to_subagent[name]
|
|
138
|
+
if existing != subagent_type:
|
|
139
|
+
warnings.append(
|
|
140
|
+
f"role collision: '{name}' maps to both "
|
|
141
|
+
f"'{existing}' and '{subagent_type}' — keeping first"
|
|
142
|
+
)
|
|
143
|
+
# Do not overwrite — first match wins.
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
role_to_subagent[name] = subagent_type
|
|
147
|
+
role_to_domain[name] = domain
|
|
148
|
+
subagent_to_role[subagent_type] = name
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
"role_to_subagent": role_to_subagent,
|
|
152
|
+
"role_to_domain": role_to_domain,
|
|
153
|
+
"subagent_to_role": subagent_to_role,
|
|
154
|
+
"domains": _load_domains(plugin_root),
|
|
155
|
+
"warnings": warnings,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def build_resolver(plugin_root: Path) -> dict:
|
|
160
|
+
"""Build the resolver by walking ``agents/**/*.md`` under ``plugin_root``.
|
|
161
|
+
|
|
162
|
+
Returns a dict with:
|
|
163
|
+
|
|
164
|
+
* ``role_to_subagent`` — bare role -> full ``wicked-garden:{domain}:{role}``
|
|
165
|
+
* ``role_to_domain`` — bare role -> domain (e.g. ``product``)
|
|
166
|
+
* ``subagent_to_role`` — full subagent_type -> bare role (reverse map)
|
|
167
|
+
* ``domains`` — list of specialist-domain names from ``specialist.json``
|
|
168
|
+
* ``warnings`` — list of human-readable strings for collisions
|
|
169
|
+
|
|
170
|
+
The result is cached per plugin_root string, so subsequent calls from
|
|
171
|
+
the same process are O(1).
|
|
172
|
+
"""
|
|
173
|
+
return _build_resolver_cached(str(plugin_root))
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def resolve_role(
|
|
177
|
+
role_name: str, resolver: dict
|
|
178
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
|
179
|
+
"""Return ``(domain, subagent_type)`` for a role, or ``(None, None)``.
|
|
180
|
+
|
|
181
|
+
Accepts either a bare role (``requirements-analyst``) or an already-
|
|
182
|
+
qualified subagent_type (``wicked-garden:product:requirements-analyst``)
|
|
183
|
+
— idempotent for the latter.
|
|
184
|
+
|
|
185
|
+
Unknown roles return ``(None, None)`` without raising. Callers that
|
|
186
|
+
want close-match suggestions should drive ``difflib.get_close_matches``
|
|
187
|
+
off ``resolver["role_to_subagent"].keys()``.
|
|
188
|
+
"""
|
|
189
|
+
if not role_name or not isinstance(role_name, str):
|
|
190
|
+
return None, None
|
|
191
|
+
role_name = role_name.strip()
|
|
192
|
+
if not role_name:
|
|
193
|
+
return None, None
|
|
194
|
+
|
|
195
|
+
# Full subagent_type path: look up the bare role via the reverse map
|
|
196
|
+
# and then use the forward maps for the domain. This keeps the return
|
|
197
|
+
# shape consistent regardless of which form the caller passed in.
|
|
198
|
+
if role_name.startswith(_SUBAGENT_PREFIX):
|
|
199
|
+
bare = resolver.get("subagent_to_role", {}).get(role_name)
|
|
200
|
+
if bare is None:
|
|
201
|
+
return None, None
|
|
202
|
+
domain = resolver.get("role_to_domain", {}).get(bare)
|
|
203
|
+
if domain is None:
|
|
204
|
+
return None, None
|
|
205
|
+
return domain, role_name
|
|
206
|
+
|
|
207
|
+
subagent_type = resolver.get("role_to_subagent", {}).get(role_name)
|
|
208
|
+
if subagent_type is None:
|
|
209
|
+
return None, None
|
|
210
|
+
domain = resolver.get("role_to_domain", {}).get(role_name)
|
|
211
|
+
if domain is None:
|
|
212
|
+
return None, None
|
|
213
|
+
return domain, subagent_type
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def clear_cache() -> None:
|
|
217
|
+
"""Drop the module-level cache. Useful for tests that mutate agents/."""
|
|
218
|
+
_build_resolver_cached.cache_clear()
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""stack_registry.py — legacy-stack-class -> dispatch reader for `modernize`.
|
|
3
|
+
|
|
4
|
+
The §B11/G2 seam: `.claude-plugin/stack-registry.json` is the machine-readable
|
|
5
|
+
dispatch truth (stack -> {blueprint, fixes, transform, validate, status}); this
|
|
6
|
+
module is the thin reader the `modernize` archetype's `discover` phase consults.
|
|
7
|
+
|
|
8
|
+
The load-bearing honesty contract (garden ETHOS — "never invent a pass"):
|
|
9
|
+
on an UNKNOWN stack, or a stack whose ``status`` is ``planned``/``none``, the
|
|
10
|
+
reader does NOT fabricate a transform. It returns a **capability-gap task** dict
|
|
11
|
+
(for the caller to hand to ``TaskCreate``) and fires a ``wicked.modernize.stack_gap``
|
|
12
|
+
bus event (fire-and-forget, fail-open). Only ``status: wired`` returns a runnable
|
|
13
|
+
dispatch. This mirrors the loom gate's fail-closed posture in dispatch data.
|
|
14
|
+
|
|
15
|
+
Pure resolution; the only I/O is reading the catalog + a fire-and-forget bus emit.
|
|
16
|
+
|
|
17
|
+
Usage (library):
|
|
18
|
+
from stack_registry import resolve_dispatch
|
|
19
|
+
r = resolve_dispatch("node-legacy-to-modern")
|
|
20
|
+
# r["status"] == "wired" -> r["dispatch"] = {blueprint, transform, validate}
|
|
21
|
+
# else -> r["gap_task"] = {title, body, ...}, dispatch is None
|
|
22
|
+
|
|
23
|
+
Usage (CLI):
|
|
24
|
+
sh "${CLAUDE_PLUGIN_ROOT}/scripts/_python.sh" \\
|
|
25
|
+
"${CLAUDE_PLUGIN_ROOT}/scripts/crew/stack_registry.py" \\
|
|
26
|
+
resolve --stack node-legacy-to-modern
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import json
|
|
33
|
+
import sys
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Any, Dict, List, Optional
|
|
36
|
+
|
|
37
|
+
_THIS_DIR = Path(__file__).resolve().parent
|
|
38
|
+
_PLUGIN_ROOT = _THIS_DIR.parents[1]
|
|
39
|
+
_REGISTRY_PATH = _PLUGIN_ROOT / ".claude-plugin" / "stack-registry.json"
|
|
40
|
+
|
|
41
|
+
# The ONLY status that returns a runnable dispatch. Everything else —
|
|
42
|
+
# `planned`, `none`, a typo, or any novel/unknown status — fails closed to a
|
|
43
|
+
# capability-gap (the load-bearing honesty contract: dispatch is wired-only).
|
|
44
|
+
_WIRED = "wired"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def load_registry(path: Optional[Path] = None) -> Dict[str, Any]:
|
|
48
|
+
"""Return the stack-registry catalog dict. Raises FileNotFoundError when
|
|
49
|
+
the registry isn't present — callers must not silently fall back."""
|
|
50
|
+
return json.loads((path or _REGISTRY_PATH).read_text(encoding="utf-8"))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _gap_task(stack_id: str, label: str, status: str, reason: str) -> Dict[str, Any]:
|
|
54
|
+
"""Build a capability-gap task dict for the caller to hand to TaskCreate.
|
|
55
|
+
This is what we emit INSTEAD of a fabricated migration."""
|
|
56
|
+
return {
|
|
57
|
+
"title": f"modernize: capability gap for stack '{stack_id}' (status={status})",
|
|
58
|
+
"body": (
|
|
59
|
+
f"{reason}\n\n"
|
|
60
|
+
f"The `modernize` archetype refuses to fabricate a transform for a "
|
|
61
|
+
f"stack it cannot actually handle. Wire a transform + validate for "
|
|
62
|
+
f"'{label}' in .claude-plugin/stack-registry.json (flip status to "
|
|
63
|
+
f"'wired'), or route this work to a stack that is already wired."
|
|
64
|
+
),
|
|
65
|
+
"stack": stack_id,
|
|
66
|
+
"status": status,
|
|
67
|
+
"kind": "capability-gap",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def resolve_dispatch(
|
|
72
|
+
stack_id: str,
|
|
73
|
+
*,
|
|
74
|
+
registry: Optional[Dict[str, Any]] = None,
|
|
75
|
+
emit: bool = True,
|
|
76
|
+
) -> Dict[str, Any]:
|
|
77
|
+
"""Resolve a legacy stack class to a dispatch decision.
|
|
78
|
+
|
|
79
|
+
Returns a dict ``{stack, status, dispatch, gap_task}`` where exactly one of
|
|
80
|
+
``dispatch`` / ``gap_task`` is non-None:
|
|
81
|
+
- status == "wired" -> dispatch = {blueprint, fixes, transform, validate}
|
|
82
|
+
- anything else (unknown /
|
|
83
|
+
planned / none / a typo /
|
|
84
|
+
any novel status) -> gap_task = {...}, dispatch = None, and a
|
|
85
|
+
``wicked.modernize.stack_gap`` event is emitted (when ``emit``).
|
|
86
|
+
|
|
87
|
+
Dispatch is strictly **wired-only**: a stack whose status is anything other
|
|
88
|
+
than ``wired`` — including an unrecognised/misspelled status — fails closed
|
|
89
|
+
to a capability-gap rather than fabricating a migration.
|
|
90
|
+
"""
|
|
91
|
+
reg = registry if registry is not None else load_registry()
|
|
92
|
+
stacks = reg.get("stacks", {})
|
|
93
|
+
entry = stacks.get(stack_id)
|
|
94
|
+
|
|
95
|
+
if entry is None:
|
|
96
|
+
gap = _gap_task(stack_id, stack_id, "unknown",
|
|
97
|
+
f"Stack '{stack_id}' is not in the registry.")
|
|
98
|
+
_emit_gap(gap, emit)
|
|
99
|
+
return {"stack": stack_id, "status": "unknown", "dispatch": None, "gap_task": gap}
|
|
100
|
+
|
|
101
|
+
status = entry.get("status") or "none"
|
|
102
|
+
if status != _WIRED:
|
|
103
|
+
# Fail closed: planned/none AND any unknown/typo'd status land here.
|
|
104
|
+
gap = _gap_task(stack_id, entry.get("label", stack_id), status,
|
|
105
|
+
f"Stack '{stack_id}' is registered as status={status} "
|
|
106
|
+
f"(only status='{_WIRED}' returns a runnable dispatch).")
|
|
107
|
+
_emit_gap(gap, emit)
|
|
108
|
+
return {"stack": stack_id, "status": status, "dispatch": None, "gap_task": gap}
|
|
109
|
+
|
|
110
|
+
# status == "wired": return the runnable dispatch. `or default` (not
|
|
111
|
+
# .get(k, default)) so an explicit JSON null coalesces to the empty
|
|
112
|
+
# container instead of crashing list(None) / passing None downstream.
|
|
113
|
+
return {
|
|
114
|
+
"stack": stack_id,
|
|
115
|
+
"status": status,
|
|
116
|
+
"dispatch": {
|
|
117
|
+
"blueprint": entry.get("blueprint"),
|
|
118
|
+
"fixes": list(entry.get("fixes") or []),
|
|
119
|
+
"transform": entry.get("transform") or {},
|
|
120
|
+
"validate": entry.get("validate") or {},
|
|
121
|
+
},
|
|
122
|
+
"gap_task": None,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _emit_gap(gap_task: Dict[str, Any], emit: bool) -> None:
|
|
127
|
+
"""Fire the stack-gap bus event. Fire-and-forget, fail-open — bus absent
|
|
128
|
+
must never block the dispatch decision (the gap_task is the source of truth)."""
|
|
129
|
+
if not emit:
|
|
130
|
+
return
|
|
131
|
+
try:
|
|
132
|
+
scripts_dir = str(_THIS_DIR.parent) # scripts/ for _bus
|
|
133
|
+
if scripts_dir not in sys.path: # guard: don't accumulate dups
|
|
134
|
+
sys.path.insert(0, scripts_dir)
|
|
135
|
+
from _bus import emit_event # type: ignore
|
|
136
|
+
emit_event(
|
|
137
|
+
"wicked.modernize.stack_gap",
|
|
138
|
+
{"stack": gap_task["stack"], "status": gap_task["status"],
|
|
139
|
+
"title": gap_task["title"]},
|
|
140
|
+
chain_id=f"modernize.{gap_task['stack']}.{gap_task['status']}.gap",
|
|
141
|
+
)
|
|
142
|
+
except Exception:
|
|
143
|
+
pass # bus is optional infrastructure
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def list_stacks(registry: Optional[Dict[str, Any]] = None) -> List[Dict[str, str]]:
|
|
147
|
+
"""Return [{id, label, status}, ...] for roadmap display."""
|
|
148
|
+
reg = registry if registry is not None else load_registry()
|
|
149
|
+
return [
|
|
150
|
+
{"id": sid, "label": s.get("label", sid), "status": s.get("status", "none")}
|
|
151
|
+
for sid, s in reg.get("stacks", {}).items()
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
# CLI
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
def _cli() -> None:
|
|
160
|
+
parser = argparse.ArgumentParser(
|
|
161
|
+
description="modernize stack-registry dispatch reader (fail-closed)."
|
|
162
|
+
)
|
|
163
|
+
sub = parser.add_subparsers(dest="action", required=True)
|
|
164
|
+
|
|
165
|
+
resolve = sub.add_parser("resolve", help="Resolve a stack to a dispatch decision.")
|
|
166
|
+
resolve.add_argument("--stack", required=True)
|
|
167
|
+
resolve.add_argument("--no-emit", action="store_true",
|
|
168
|
+
help="Do not emit the stack-gap bus event (testing/dry-run).")
|
|
169
|
+
|
|
170
|
+
sub.add_parser("list", help="List registered stacks + statuses (roadmap).")
|
|
171
|
+
|
|
172
|
+
args = parser.parse_args()
|
|
173
|
+
|
|
174
|
+
if args.action == "resolve":
|
|
175
|
+
result = resolve_dispatch(args.stack, emit=not args.no_emit)
|
|
176
|
+
json.dump(result, sys.stdout, indent=2, default=str)
|
|
177
|
+
sys.stdout.write("\n")
|
|
178
|
+
# Exit 0 when wired (dispatch ready); exit 3 on a gap (fail-closed,
|
|
179
|
+
# matching prove.py's "backend down / no-pass" convention).
|
|
180
|
+
sys.exit(0 if result["dispatch"] is not None else 3)
|
|
181
|
+
|
|
182
|
+
if args.action == "list":
|
|
183
|
+
json.dump(list_stacks(), sys.stdout, indent=2, default=str)
|
|
184
|
+
sys.stdout.write("\n")
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
if __name__ == "__main__":
|
|
189
|
+
_cli()
|