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,151 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
_logger.py — Structured operational logging for wicked-garden hook scripts.
|
|
4
|
+
|
|
5
|
+
Writes one JSONL line per call to $TMPDIR/wicked-ops-{session_id}.jsonl.
|
|
6
|
+
Fail-silent: never raises, never crashes the caller.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from _logger import log
|
|
10
|
+
|
|
11
|
+
log("bootstrap", "normal", "onboarding.status", ok=True, detail={"has_memories": True})
|
|
12
|
+
log("smaht", "verbose", "prompt.routed", detail={"path": "fast", "turn": 1})
|
|
13
|
+
log("post_tool", "debug", "hook.end", ms=1.23)
|
|
14
|
+
|
|
15
|
+
Level hierarchy: normal (0) < verbose (1) < debug (2)
|
|
16
|
+
Effective level is resolved from: WICKED_LOG_LEVEL env var > SessionState.log_level > "normal"
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Level constants
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
_LEVELS = {"normal": 0, "verbose": 1, "debug": 2}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# Internal helpers
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _get_session_id() -> str:
|
|
37
|
+
"""Read and sanitize CLAUDE_SESSION_ID for use in filenames."""
|
|
38
|
+
import re
|
|
39
|
+
raw = os.environ.get("CLAUDE_SESSION_ID", "unknown")
|
|
40
|
+
safe = re.sub(r"[^a-zA-Z0-9\-_]", "_", raw)
|
|
41
|
+
return safe or "unknown"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _log_file(session_id: str) -> Path:
|
|
45
|
+
"""Return path to the ops JSONL log file for the given session."""
|
|
46
|
+
tmpdir = os.environ.get("TMPDIR") or __import__("tempfile").gettempdir()
|
|
47
|
+
return Path(tmpdir) / f"wicked-ops-{session_id}.jsonl"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _resolve_level() -> str:
|
|
51
|
+
"""Determine the effective log level.
|
|
52
|
+
|
|
53
|
+
Priority order:
|
|
54
|
+
1. WICKED_LOG_LEVEL environment variable
|
|
55
|
+
2. SessionState.log_level (read directly from raw JSON — no import)
|
|
56
|
+
3. Default: "normal"
|
|
57
|
+
"""
|
|
58
|
+
# 1. Environment variable (highest priority)
|
|
59
|
+
env = os.environ.get("WICKED_LOG_LEVEL", "").strip().lower()
|
|
60
|
+
if env in _LEVELS:
|
|
61
|
+
return env
|
|
62
|
+
|
|
63
|
+
# 2. SessionState JSON file (avoid circular import: read raw JSON directly)
|
|
64
|
+
try:
|
|
65
|
+
import json as _json
|
|
66
|
+
_sid = _get_session_id()
|
|
67
|
+
_tmpdir = os.environ.get("TMPDIR") or __import__("tempfile").gettempdir()
|
|
68
|
+
_state_path = Path(_tmpdir) / f"wicked-garden-session-{_sid}.json"
|
|
69
|
+
if _state_path.exists():
|
|
70
|
+
_data = _json.loads(_state_path.read_text(encoding="utf-8"))
|
|
71
|
+
_level = _data.get("log_level", "").strip().lower()
|
|
72
|
+
if _level in _LEVELS:
|
|
73
|
+
return _level
|
|
74
|
+
except Exception:
|
|
75
|
+
pass # Fail open — use default
|
|
76
|
+
|
|
77
|
+
# 3. Default
|
|
78
|
+
return "normal"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
# Public API
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def log(
|
|
87
|
+
domain: str,
|
|
88
|
+
level: str,
|
|
89
|
+
event: str,
|
|
90
|
+
ok: bool = True,
|
|
91
|
+
ms: float | None = None,
|
|
92
|
+
detail: dict | None = None,
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Write a structured log entry to the session ops JSONL file.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
domain: Source domain, e.g. "bootstrap", "smaht", "crew"
|
|
98
|
+
level: Log level — "normal", "verbose", or "debug"
|
|
99
|
+
event: Dot-separated event name, e.g. "onboarding.status"
|
|
100
|
+
ok: Outcome indicator (default True)
|
|
101
|
+
ms: Elapsed milliseconds, rounded to 2 decimal places (optional)
|
|
102
|
+
detail: Arbitrary JSON-safe payload dict (optional)
|
|
103
|
+
|
|
104
|
+
This function never raises. All exceptions are silently swallowed.
|
|
105
|
+
"""
|
|
106
|
+
try:
|
|
107
|
+
# Resolve effective level and filter
|
|
108
|
+
effective_level = _resolve_level()
|
|
109
|
+
event_rank = _LEVELS.get(level, 2) # Unknown levels treated as debug (most permissive)
|
|
110
|
+
effective_rank = _LEVELS.get(effective_level, 0)
|
|
111
|
+
|
|
112
|
+
if event_rank > effective_rank:
|
|
113
|
+
return # Silently discard — event is more verbose than the threshold
|
|
114
|
+
|
|
115
|
+
# Build the log entry
|
|
116
|
+
session_id = _get_session_id()
|
|
117
|
+
ts = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
118
|
+
ms_val = round(ms, 2) if ms is not None else None
|
|
119
|
+
|
|
120
|
+
# Serialize detail safely
|
|
121
|
+
detail_val: dict | None
|
|
122
|
+
if detail is None:
|
|
123
|
+
detail_val = None
|
|
124
|
+
else:
|
|
125
|
+
try:
|
|
126
|
+
json.dumps(detail) # Probe serializability
|
|
127
|
+
detail_val = detail
|
|
128
|
+
except (TypeError, ValueError):
|
|
129
|
+
detail_val = {"_raw": str(detail)}
|
|
130
|
+
|
|
131
|
+
entry = {
|
|
132
|
+
"ts": ts,
|
|
133
|
+
"session": session_id,
|
|
134
|
+
"domain": domain,
|
|
135
|
+
"level": level,
|
|
136
|
+
"event": event,
|
|
137
|
+
"ok": ok,
|
|
138
|
+
"ms": ms_val,
|
|
139
|
+
"detail": detail_val,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Write one JSONL line — append mode, no fsync
|
|
143
|
+
log_path = _log_file(session_id)
|
|
144
|
+
try:
|
|
145
|
+
with log_path.open("a", encoding="utf-8") as fh:
|
|
146
|
+
fh.write(json.dumps(entry) + "\n")
|
|
147
|
+
except OSError:
|
|
148
|
+
pass # I/O errors are silently swallowed
|
|
149
|
+
|
|
150
|
+
except Exception:
|
|
151
|
+
pass # Outer catch — logger must never crash the caller
|
package/scripts/_loom.py
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""_loom.py — wicked-loom peer-resolution runtime (absorbed, Phase B).
|
|
3
|
+
|
|
4
|
+
After the wicked-* ecosystem rationalization (ECOSYSTEM-RATIONALIZATION.md
|
|
5
|
+
§5a Phase B), wicked-loom's peer-resolution capabilities are absorbed into
|
|
6
|
+
wicked-garden as ``scripts/loom/``. The standalone ``wicked-loom`` npm
|
|
7
|
+
package is no longer required — this module dispatches to the internal Python
|
|
8
|
+
modules in-process (no subprocess for loom itself).
|
|
9
|
+
|
|
10
|
+
The external interface is UNCHANGED — all callers (vault_gate.py,
|
|
11
|
+
phase_manager.py, bootstrap.py) continue to call resolve_loom(),
|
|
12
|
+
run_json(), use_loom(), loom_available() exactly as before.
|
|
13
|
+
|
|
14
|
+
RETIRED (not callable through this shim):
|
|
15
|
+
loom flow run / status / resume — replaced by wicked-crew.
|
|
16
|
+
|
|
17
|
+
ABSORBED (available in-process):
|
|
18
|
+
loom resolve <peer> -> scripts/loom/resolve.py
|
|
19
|
+
loom doctor [--strict] -> scripts/loom/compose.py
|
|
20
|
+
loom compose install [--peer X] -> scripts/loom/compose.py
|
|
21
|
+
loom gate <produces> [--scope S]-> scripts/loom/gate.py
|
|
22
|
+
|
|
23
|
+
Resolution notes
|
|
24
|
+
----------------
|
|
25
|
+
The ``WICKED_LOOM_BIN`` env var is still respected as the highest-priority
|
|
26
|
+
override (env override beats internal), so debugging with a custom binary
|
|
27
|
+
still works. ``WICKED_LOOM_CUTOVER=off`` is the kill-switch — it disables
|
|
28
|
+
loom entirely (gates fail closed, peer resolution unavailable), identical to
|
|
29
|
+
the pre-absorption kill-switch. The internal path is selected by default
|
|
30
|
+
when the ``scripts/loom/`` package is importable (always true in a garden
|
|
31
|
+
install); the external subprocess fallback is used only when the internal
|
|
32
|
+
package is absent or ``_run`` is injected by tests.
|
|
33
|
+
|
|
34
|
+
Stdlib-only (plus the absorbed scripts/loom/ package). Cross-platform
|
|
35
|
+
(argv lists, shutil.which, no shell).
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import json
|
|
41
|
+
import os
|
|
42
|
+
import shutil
|
|
43
|
+
import subprocess # noqa: S404 — argv lists only, shell=False
|
|
44
|
+
import sys
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
47
|
+
|
|
48
|
+
_CONFIG_PATH = Path.home() / ".something-wicked" / "wicked-garden" / "config.json"
|
|
49
|
+
_DEFAULT_TIMEOUT = 120
|
|
50
|
+
_CUTOVER_MODES = ("auto", "on", "off")
|
|
51
|
+
_PACKAGE = "wicked-loom"
|
|
52
|
+
_ENV_BIN = "WICKED_LOOM_BIN"
|
|
53
|
+
_ENV_FLAG = "WICKED_LOOM_CUTOVER"
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Internal module import (absorbed loom package)
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# Add scripts/ to sys.path so ``import loom`` finds scripts/loom/ regardless
|
|
59
|
+
# of which directory the caller is running from.
|
|
60
|
+
_SCRIPTS_DIR = str(Path(__file__).resolve().parent)
|
|
61
|
+
if _SCRIPTS_DIR not in sys.path:
|
|
62
|
+
sys.path.insert(0, _SCRIPTS_DIR)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
from loom import resolve as _loom_resolve_mod
|
|
66
|
+
from loom import compose as _loom_compose_mod
|
|
67
|
+
from loom import gate as _loom_gate_mod
|
|
68
|
+
from loom import manifest as _loom_manifest_mod
|
|
69
|
+
_HAVE_INTERNAL = True
|
|
70
|
+
except ImportError: # pragma: no cover — only absent if scripts/loom/ is missing
|
|
71
|
+
_HAVE_INTERNAL = False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# Resolution ladder (for the external CLI fallback)
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
def _argv_for(target: str) -> List[str]:
|
|
79
|
+
"""A .mjs/.js path is a script -> invoke via node; else run directly."""
|
|
80
|
+
if target.endswith((".mjs", ".js")):
|
|
81
|
+
return ["node", target]
|
|
82
|
+
return [target]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _read_config_preference(key: str) -> Optional[str]:
|
|
86
|
+
"""Read ``tool_preferences.{key}`` from config.json; None on any error."""
|
|
87
|
+
try:
|
|
88
|
+
if not _CONFIG_PATH.exists():
|
|
89
|
+
return None
|
|
90
|
+
data = json.loads(_CONFIG_PATH.read_text(encoding="utf-8"))
|
|
91
|
+
prefs = data.get("tool_preferences")
|
|
92
|
+
if isinstance(prefs, dict):
|
|
93
|
+
value = prefs.get(key)
|
|
94
|
+
if isinstance(value, str) and value.strip():
|
|
95
|
+
return value.strip()
|
|
96
|
+
return None
|
|
97
|
+
except (json.JSONDecodeError, OSError):
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def resolve_loom(*, allow_npx: bool = True,
|
|
102
|
+
project_dir: Optional[Path] = None) -> Optional[List[str]]:
|
|
103
|
+
"""Return the argv prefix that invokes wicked-loom, or None.
|
|
104
|
+
|
|
105
|
+
After absorption the primary path is the internal Python module
|
|
106
|
+
(``_HAVE_INTERNAL``), not a subprocess. ``resolve_loom()`` now reflects
|
|
107
|
+
that: it returns a sentinel ``["_internal"]`` when the absorbed package is
|
|
108
|
+
importable, and falls back to the external CLI ladder only when it is not.
|
|
109
|
+
|
|
110
|
+
The ``WICKED_LOOM_BIN`` env override still takes highest priority — an
|
|
111
|
+
explicit override or kill-switch beats everything including the internal
|
|
112
|
+
module. This preserves the debugging escape hatch and the empty-string
|
|
113
|
+
kill-switch semantics.
|
|
114
|
+
|
|
115
|
+
Ladder (first hit wins):
|
|
116
|
+
1. WICKED_LOOM_BIN env. Set-but-empty is the kill-switch -> None.
|
|
117
|
+
2. Internal absorbed module (scripts/loom/) -> ["_internal"] sentinel.
|
|
118
|
+
3. tool_preferences.wicked-loom in config.json.
|
|
119
|
+
4. wicked-loom on PATH.
|
|
120
|
+
5. project-local node_modules/.bin/wicked-loom.
|
|
121
|
+
6. npx --yes wicked-loom (only when allow_npx).
|
|
122
|
+
"""
|
|
123
|
+
# Step 1: explicit env override / kill-switch (highest priority)
|
|
124
|
+
if _ENV_BIN in os.environ:
|
|
125
|
+
env = os.environ[_ENV_BIN].strip()
|
|
126
|
+
return _argv_for(env) if env else None # empty == kill-switch
|
|
127
|
+
|
|
128
|
+
# Step 2: absorbed internal module (always available in a garden install)
|
|
129
|
+
if _HAVE_INTERNAL:
|
|
130
|
+
return ["_internal"]
|
|
131
|
+
|
|
132
|
+
# Steps 3-6: external CLI fallback
|
|
133
|
+
pref = _read_config_preference(_PACKAGE)
|
|
134
|
+
if pref:
|
|
135
|
+
return _argv_for(pref)
|
|
136
|
+
|
|
137
|
+
found = shutil.which(_PACKAGE)
|
|
138
|
+
if found:
|
|
139
|
+
return [found]
|
|
140
|
+
|
|
141
|
+
base = Path(project_dir) if project_dir else Path.cwd()
|
|
142
|
+
local = base / "node_modules" / ".bin" / _PACKAGE
|
|
143
|
+
if local.exists():
|
|
144
|
+
return [str(local)]
|
|
145
|
+
|
|
146
|
+
if allow_npx and shutil.which("npx"):
|
|
147
|
+
return ["npx", "--yes", _PACKAGE]
|
|
148
|
+
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def loom_available(project_dir: Optional[Path] = None) -> bool:
|
|
153
|
+
"""True iff loom is concretely available — internal module or external install.
|
|
154
|
+
|
|
155
|
+
After absorption this returns True whenever the internal module is
|
|
156
|
+
importable (i.e., always in a properly installed wicked-garden). The
|
|
157
|
+
``allow_npx=False`` semantics from before absorption: the npx last-resort
|
|
158
|
+
is excluded. The internal module is a concrete install by definition.
|
|
159
|
+
"""
|
|
160
|
+
if _HAVE_INTERNAL:
|
|
161
|
+
return True
|
|
162
|
+
# External concrete probe (no npx)
|
|
163
|
+
return resolve_loom(allow_npx=False, project_dir=project_dir) is not None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
# Internal dispatch (in-process, no subprocess for loom itself)
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
def _fmt(exit_code: int, payload: dict) -> Dict[str, Any]:
|
|
171
|
+
"""Wrap an internal result in the standard run_json return shape."""
|
|
172
|
+
raw = json.dumps(payload)
|
|
173
|
+
return {"exit_code": exit_code, "json": payload, "stdout": raw,
|
|
174
|
+
"stderr": "", "error": None}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _internal_resolve(args: List[str],
|
|
178
|
+
project_dir: Optional[Path] = None) -> Dict[str, Any]:
|
|
179
|
+
"""``loom resolve <peer>`` — in-process via loom.resolve."""
|
|
180
|
+
if not args:
|
|
181
|
+
payload = {"error": "usage: loom resolve <peer>"}
|
|
182
|
+
return {"exit_code": 2, "json": payload, "stdout": json.dumps(payload),
|
|
183
|
+
"stderr": "", "error": None}
|
|
184
|
+
peer_name = args[0]
|
|
185
|
+
cmd = _loom_resolve_mod.resolve(peer_name)
|
|
186
|
+
payload = {"peer": peer_name, "command": cmd}
|
|
187
|
+
return _fmt(0 if cmd is not None else 1, payload)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _internal_doctor(args: List[str]) -> Dict[str, Any]:
|
|
191
|
+
"""``loom doctor [--strict]`` — in-process via loom.compose."""
|
|
192
|
+
strict = "--strict" in args
|
|
193
|
+
rows = _loom_compose_mod.check_all()
|
|
194
|
+
all_reachable = all(r.get("status") == "ok" for r in rows)
|
|
195
|
+
not_capable = [r.get("peer") for r in rows if not r.get("capability_ok")]
|
|
196
|
+
all_capable = not not_capable
|
|
197
|
+
payload = {
|
|
198
|
+
"peers": rows,
|
|
199
|
+
"all_reachable": all_reachable,
|
|
200
|
+
"all_capable": all_capable,
|
|
201
|
+
"not_capable": not_capable,
|
|
202
|
+
"strict": strict,
|
|
203
|
+
}
|
|
204
|
+
ok = all_reachable and (all_capable if strict else True)
|
|
205
|
+
return _fmt(0 if ok else 1, payload)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _internal_compose(args: List[str]) -> Dict[str, Any]:
|
|
209
|
+
"""``loom compose install [--peer X]`` — in-process via loom.compose."""
|
|
210
|
+
if not args or args[0] != "install":
|
|
211
|
+
payload = {"error": "usage: loom compose install [--peer <name>]"}
|
|
212
|
+
return {"exit_code": 2, "json": payload, "stdout": json.dumps(payload),
|
|
213
|
+
"stderr": "", "error": None}
|
|
214
|
+
target: Optional[str] = None
|
|
215
|
+
if "--peer" in args:
|
|
216
|
+
i = args.index("--peer")
|
|
217
|
+
if i + 1 < len(args):
|
|
218
|
+
target = args[i + 1]
|
|
219
|
+
names = [target] if target else list(_loom_manifest_mod.PEERS)
|
|
220
|
+
results = [_loom_compose_mod.install_peer(n) for n in names]
|
|
221
|
+
payload = {"results": results}
|
|
222
|
+
all_ok = all(r.get("status") == "installed" for r in results)
|
|
223
|
+
return _fmt(0 if all_ok else 1, payload)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _internal_gate(args: List[str],
|
|
227
|
+
project_dir: Optional[Path] = None) -> Dict[str, Any]:
|
|
228
|
+
"""``loom gate <produces> [--scope S] [--with-attestations]`` — in-process.
|
|
229
|
+
|
|
230
|
+
CWD preservation: the external loom subprocess path ran loom in
|
|
231
|
+
``project_dir``, so vault (spawned by loom) also ran in that directory.
|
|
232
|
+
The internal path skips the loom subprocess, so we must forward
|
|
233
|
+
``project_dir`` as the vault subprocess's CWD explicitly. Without this,
|
|
234
|
+
vault runs in the parent process CWD and cannot find the project's
|
|
235
|
+
``.wicked/`` store — "no contract declared" (the #891 cwd regression,
|
|
236
|
+
re-introduced at the in-process boundary).
|
|
237
|
+
"""
|
|
238
|
+
# Replicate cli.py's _positionals / _opt without importing cli (avoid
|
|
239
|
+
# circular dep; the cli module imports from loom.* not from _loom).
|
|
240
|
+
_value_opts = ("--scope", "--verifier-spec")
|
|
241
|
+
|
|
242
|
+
def _positionals(a: list) -> list:
|
|
243
|
+
out, i, n = [], 0, len(a)
|
|
244
|
+
while i < n:
|
|
245
|
+
tok = a[i]
|
|
246
|
+
if tok in _value_opts:
|
|
247
|
+
i += 2
|
|
248
|
+
continue
|
|
249
|
+
if tok.startswith("--"):
|
|
250
|
+
i += 1
|
|
251
|
+
continue
|
|
252
|
+
out.append(tok)
|
|
253
|
+
i += 1
|
|
254
|
+
return out
|
|
255
|
+
|
|
256
|
+
def _opt(a: list, name: str) -> Optional[str]:
|
|
257
|
+
if name in a:
|
|
258
|
+
idx = a.index(name)
|
|
259
|
+
if idx + 1 < len(a):
|
|
260
|
+
return a[idx + 1]
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
scope = _opt(args, "--scope") or "default"
|
|
264
|
+
verifier_spec = _opt(args, "--verifier-spec")
|
|
265
|
+
positional = _positionals(args)
|
|
266
|
+
produces = positional[0] if positional else None
|
|
267
|
+
|
|
268
|
+
if produces is None:
|
|
269
|
+
payload = {"error": "usage: loom gate <produces> [--scope S] "
|
|
270
|
+
"[--verifier-spec PATH] [--with-attestations]"}
|
|
271
|
+
return {"exit_code": 2, "json": payload, "stdout": json.dumps(payload),
|
|
272
|
+
"stderr": "", "error": None}
|
|
273
|
+
|
|
274
|
+
# Build a runner that honours project_dir as the vault subprocess CWD.
|
|
275
|
+
# gate.run_gate shells wicked-vault (still external); only the loom layer
|
|
276
|
+
# moved in-process. When project_dir is provided we inject a custom runner
|
|
277
|
+
# so vault runs in the project directory — mirroring how the old external
|
|
278
|
+
# loom subprocess ran in project_dir and its own vault call inherited it.
|
|
279
|
+
if project_dir is not None:
|
|
280
|
+
_cwd = str(project_dir)
|
|
281
|
+
|
|
282
|
+
def _project_run(cmd: list, timeout: int = 30): # type: ignore[override]
|
|
283
|
+
from loom.compose import RunResult
|
|
284
|
+
p = subprocess.run( # noqa: S603
|
|
285
|
+
cmd, capture_output=True, text=True,
|
|
286
|
+
timeout=timeout, cwd=_cwd,
|
|
287
|
+
)
|
|
288
|
+
return RunResult(returncode=p.returncode,
|
|
289
|
+
stdout=p.stdout, stderr=p.stderr)
|
|
290
|
+
|
|
291
|
+
run_kwarg: Dict[str, Any] = {"run": _project_run}
|
|
292
|
+
else:
|
|
293
|
+
run_kwarg = {}
|
|
294
|
+
|
|
295
|
+
verdict = _loom_gate_mod.run_gate(
|
|
296
|
+
produces, scope=scope, verifier_spec=verifier_spec,
|
|
297
|
+
with_attestations="--with-attestations" in args,
|
|
298
|
+
**run_kwarg,
|
|
299
|
+
)
|
|
300
|
+
payload = {"gate": verdict}
|
|
301
|
+
return _fmt(0 if verdict.get("satisfied") else 1, payload)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _dispatch_internal(args: List[str],
|
|
305
|
+
project_dir: Optional[Path] = None) -> Dict[str, Any]:
|
|
306
|
+
"""Dispatch a loom command to the absorbed in-process modules.
|
|
307
|
+
|
|
308
|
+
Returns the same {exit_code, json, stdout, stderr, error} shape as the
|
|
309
|
+
external subprocess path so all callers are unchanged.
|
|
310
|
+
"""
|
|
311
|
+
if not args:
|
|
312
|
+
payload = {"commands": ["resolve", "doctor", "compose", "gate"]}
|
|
313
|
+
return _fmt(0, payload)
|
|
314
|
+
cmd = args[0]
|
|
315
|
+
rest = args[1:]
|
|
316
|
+
if cmd == "resolve":
|
|
317
|
+
return _internal_resolve(rest, project_dir=project_dir)
|
|
318
|
+
if cmd == "doctor":
|
|
319
|
+
return _internal_doctor(rest)
|
|
320
|
+
if cmd == "compose":
|
|
321
|
+
return _internal_compose(rest)
|
|
322
|
+
if cmd == "gate":
|
|
323
|
+
return _internal_gate(rest, project_dir=project_dir)
|
|
324
|
+
if cmd == "flow":
|
|
325
|
+
payload = {
|
|
326
|
+
"error": "loom flow is retired",
|
|
327
|
+
"detail": (
|
|
328
|
+
"loom flow run/status/resume was the loom conduct surface, "
|
|
329
|
+
"replaced by wicked-crew + wicked-orchestration "
|
|
330
|
+
"(ECOSYSTEM-RATIONALIZATION.md §5a Phase B)."
|
|
331
|
+
),
|
|
332
|
+
}
|
|
333
|
+
return {"exit_code": 2, "json": payload, "stdout": json.dumps(payload),
|
|
334
|
+
"stderr": "", "error": "loom flow is retired"}
|
|
335
|
+
payload = {"error": f"unknown command: {cmd}",
|
|
336
|
+
"commands": ["resolve", "doctor", "compose", "gate"]}
|
|
337
|
+
return {"exit_code": 2, "json": payload, "stdout": json.dumps(payload),
|
|
338
|
+
"stderr": "", "error": f"unknown loom command: {cmd}"}
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# ---------------------------------------------------------------------------
|
|
342
|
+
# External subprocess runner (fallback / test injection)
|
|
343
|
+
# ---------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
def _default_run(prefix: List[str], args: List[str], timeout: int,
|
|
346
|
+
cwd: Optional[str] = None) -> Dict[str, Any]:
|
|
347
|
+
try:
|
|
348
|
+
proc = subprocess.run( # noqa: S603 — argv list, shell=False
|
|
349
|
+
prefix + args, capture_output=True, text=True, timeout=timeout,
|
|
350
|
+
cwd=cwd,
|
|
351
|
+
)
|
|
352
|
+
return {"exit_code": proc.returncode, "stdout": proc.stdout,
|
|
353
|
+
"stderr": proc.stderr, "error": None}
|
|
354
|
+
except FileNotFoundError:
|
|
355
|
+
return {"exit_code": None, "stdout": "", "stderr": "",
|
|
356
|
+
"error": "loom executable not found"}
|
|
357
|
+
except subprocess.TimeoutExpired:
|
|
358
|
+
return {"exit_code": None, "stdout": "", "stderr": "",
|
|
359
|
+
"error": f"loom call exceeded {timeout}s"}
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
Runner = Callable[..., Dict[str, Any]]
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def run_json(args: List[str], *, timeout: int = _DEFAULT_TIMEOUT,
|
|
366
|
+
project_dir: Optional[Path] = None,
|
|
367
|
+
_run: Optional[Runner] = None) -> Dict[str, Any]:
|
|
368
|
+
"""Run ``loom <args>``; return {exit_code, json, stdout, stderr, error}.
|
|
369
|
+
|
|
370
|
+
Never raises (R4). json is the parsed result or None (unresolvable,
|
|
371
|
+
not-found, timeout, non-JSON, or retired command).
|
|
372
|
+
|
|
373
|
+
Dispatch order:
|
|
374
|
+
1. WICKED_LOOM_BIN env override (highest priority — beats even internal):
|
|
375
|
+
- Empty string -> kill-switch; return "not resolvable" immediately.
|
|
376
|
+
- Non-empty str -> use the specified external binary (debugging escape hatch).
|
|
377
|
+
2. Internal absorbed modules (in-process) — when _HAVE_INTERNAL is True
|
|
378
|
+
and no _run override is injected AND no WICKED_LOOM_BIN is set.
|
|
379
|
+
This is the post-absorption default for normal operation.
|
|
380
|
+
3. External subprocess — when _run is injected (tests), when the
|
|
381
|
+
internal package is absent, or when WICKED_LOOM_BIN is explicitly set.
|
|
382
|
+
|
|
383
|
+
``_run`` is the injected subprocess runner (tests pass a fake). When
|
|
384
|
+
None AND _HAVE_INTERNAL (and WICKED_LOOM_BIN is unset), the internal
|
|
385
|
+
path is used (no subprocess for loom). When None AND not _HAVE_INTERNAL,
|
|
386
|
+
resolves _default_run at call time — so monkeypatching
|
|
387
|
+
``_loom._default_run`` (the documented test seam) takes effect; binding
|
|
388
|
+
it as a default-arg value would freeze it at def-time.
|
|
389
|
+
"""
|
|
390
|
+
# Step 1: explicit WICKED_LOOM_BIN env override (beats internal module).
|
|
391
|
+
# Non-empty -> debugging escape hatch: use that binary, skip internal.
|
|
392
|
+
# Empty -> deliberate kill-switch: no loom at all.
|
|
393
|
+
_env_override = os.environ.get(_ENV_BIN)
|
|
394
|
+
if _env_override is not None:
|
|
395
|
+
if not _env_override.strip():
|
|
396
|
+
return {"exit_code": None, "json": None, "stdout": "", "stderr": "",
|
|
397
|
+
"error": "wicked-loom not resolvable"}
|
|
398
|
+
# Non-empty override: fall through to external subprocess below.
|
|
399
|
+
elif _HAVE_INTERNAL and _run is None:
|
|
400
|
+
# Step 2: internal path — env override unset, internal modules available,
|
|
401
|
+
# no test injection.
|
|
402
|
+
return _dispatch_internal(args, project_dir=project_dir)
|
|
403
|
+
|
|
404
|
+
# External subprocess path (env override / test injection / no internal).
|
|
405
|
+
prefix = resolve_loom(project_dir=project_dir)
|
|
406
|
+
if prefix is None or prefix == ["_internal"]:
|
|
407
|
+
# "_internal" sentinel: internal was selected but _run was injected
|
|
408
|
+
# for testing; fall back to "not resolvable" in that edge case so
|
|
409
|
+
# tests that inject _run and expect the external path still work.
|
|
410
|
+
# (Normally _run injection only happens in tests that also mock
|
|
411
|
+
# resolve_loom, so this path is rarely hit in practice.)
|
|
412
|
+
if prefix == ["_internal"] and _run is not None:
|
|
413
|
+
# Test is injecting _run to exercise the subprocess path; honour it.
|
|
414
|
+
pass # fall through to the _run invocation below with a dummy prefix
|
|
415
|
+
else:
|
|
416
|
+
return {"exit_code": None, "json": None, "stdout": "", "stderr": "",
|
|
417
|
+
"error": "wicked-loom not resolvable"}
|
|
418
|
+
|
|
419
|
+
if _run is not None:
|
|
420
|
+
# Test seam: injected runner, use whatever prefix resolve_loom returned
|
|
421
|
+
# (or a mock prefix from patch.object).
|
|
422
|
+
effective_prefix = prefix if prefix and prefix != ["_internal"] else [_PACKAGE]
|
|
423
|
+
run_result = _run(effective_prefix, args, timeout)
|
|
424
|
+
else:
|
|
425
|
+
cwd = str(project_dir) if project_dir is not None else None
|
|
426
|
+
run_result = _default_run(prefix, args, timeout, cwd=cwd)
|
|
427
|
+
|
|
428
|
+
if run_result["error"] is not None:
|
|
429
|
+
return {"exit_code": None, "json": None, "stdout": "", "stderr": "",
|
|
430
|
+
"error": run_result["error"]}
|
|
431
|
+
try:
|
|
432
|
+
parsed = json.loads(run_result["stdout"]) if (run_result["stdout"] or "").strip() else {}
|
|
433
|
+
except json.JSONDecodeError:
|
|
434
|
+
return {"exit_code": run_result["exit_code"], "json": None,
|
|
435
|
+
"stdout": run_result["stdout"], "stderr": run_result["stderr"],
|
|
436
|
+
"error": "loom returned non-JSON output"}
|
|
437
|
+
return {"exit_code": run_result["exit_code"], "json": parsed,
|
|
438
|
+
"stdout": run_result["stdout"], "stderr": run_result["stderr"],
|
|
439
|
+
"error": None}
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
# ---------------------------------------------------------------------------
|
|
443
|
+
# Feature flag
|
|
444
|
+
# ---------------------------------------------------------------------------
|
|
445
|
+
|
|
446
|
+
def cutover_mode() -> str:
|
|
447
|
+
"""The WICKED_LOOM_CUTOVER flag, normalized. Unknown -> 'auto'.
|
|
448
|
+
|
|
449
|
+
After absorption: 'auto' always uses loom (internal module is always
|
|
450
|
+
available). 'off' remains the kill-switch — disables loom entirely and
|
|
451
|
+
causes gates to fail closed. 'on' is unchanged.
|
|
452
|
+
"""
|
|
453
|
+
raw = os.environ.get(_ENV_FLAG, "").strip().lower()
|
|
454
|
+
return raw if raw in _CUTOVER_MODES else "auto"
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def use_loom(*, project_dir: Optional[Path] = None) -> bool:
|
|
458
|
+
"""Should this call go through loom?
|
|
459
|
+
|
|
460
|
+
off -> never (emergency kill-switch; gate fails closed).
|
|
461
|
+
on -> always.
|
|
462
|
+
auto -> True when internal module is available (always in garden install),
|
|
463
|
+
OR when the external CLI resolves (backward compat for environments
|
|
464
|
+
without the absorbed package).
|
|
465
|
+
|
|
466
|
+
After absorption: auto is effectively always True in a garden install
|
|
467
|
+
because _HAVE_INTERNAL is True. The kill-switch (off) is preserved for
|
|
468
|
+
emergency disable.
|
|
469
|
+
"""
|
|
470
|
+
mode = cutover_mode()
|
|
471
|
+
if mode == "off":
|
|
472
|
+
return False
|
|
473
|
+
if mode == "on":
|
|
474
|
+
return True
|
|
475
|
+
# auto: internal (absorbed) takes priority over external CLI check
|
|
476
|
+
if _HAVE_INTERNAL:
|
|
477
|
+
return True
|
|
478
|
+
return resolve_loom(project_dir=project_dir) is not None
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
__all__ = ["resolve_loom", "loom_available", "run_json",
|
|
482
|
+
"cutover_mode", "use_loom", "_HAVE_INTERNAL"]
|