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,429 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Safety module for wicked-patch.
|
|
3
|
+
|
|
4
|
+
Provides safety validations before applying patches:
|
|
5
|
+
1. Git clean working tree check
|
|
6
|
+
2. Transactional patch application with rollback
|
|
7
|
+
3. Symbol graph freshness gate
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
import sqlite3
|
|
15
|
+
import subprocess
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from datetime import datetime, timedelta
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
20
|
+
import logging
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
BACKUP_SUFFIX = ".wicked-backup"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class SafetyCheckResult:
|
|
29
|
+
"""Result of a safety check."""
|
|
30
|
+
passed: bool
|
|
31
|
+
message: str
|
|
32
|
+
details: Optional[Dict[str, Any]] = None
|
|
33
|
+
|
|
34
|
+
def __bool__(self) -> bool:
|
|
35
|
+
return self.passed
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SafetyError(Exception):
|
|
39
|
+
"""Raised when a safety check fails."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class GitSafetyChecker:
|
|
43
|
+
"""Check git repository state for safety."""
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def check_git_clean(path: Path) -> SafetyCheckResult:
|
|
47
|
+
"""
|
|
48
|
+
Check if the git working tree is clean.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
path: Path to check (file or directory)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
SafetyCheckResult with pass/fail and details
|
|
55
|
+
"""
|
|
56
|
+
# Find git root
|
|
57
|
+
git_root = GitSafetyChecker._find_git_root(path)
|
|
58
|
+
if not git_root:
|
|
59
|
+
return SafetyCheckResult(
|
|
60
|
+
passed=False,
|
|
61
|
+
message="Not a git repository. wicked-patch requires git for safety.",
|
|
62
|
+
details={"path": str(path)}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# Check for uncommitted changes
|
|
67
|
+
result = subprocess.run(
|
|
68
|
+
["git", "status", "--porcelain"],
|
|
69
|
+
cwd=git_root,
|
|
70
|
+
capture_output=True,
|
|
71
|
+
text=True,
|
|
72
|
+
timeout=30
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if result.returncode != 0:
|
|
76
|
+
return SafetyCheckResult(
|
|
77
|
+
passed=False,
|
|
78
|
+
message=f"Git status failed: {result.stderr}",
|
|
79
|
+
details={"returncode": result.returncode}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if result.stdout.strip():
|
|
83
|
+
# There are uncommitted changes
|
|
84
|
+
changed_files = result.stdout.strip().split("\n")
|
|
85
|
+
return SafetyCheckResult(
|
|
86
|
+
passed=False,
|
|
87
|
+
message=f"Working tree has {len(changed_files)} uncommitted changes. Commit or stash first.",
|
|
88
|
+
details={
|
|
89
|
+
"changed_files": changed_files[:10], # Limit to first 10
|
|
90
|
+
"total_changes": len(changed_files)
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return SafetyCheckResult(
|
|
95
|
+
passed=True,
|
|
96
|
+
message="Git working tree is clean",
|
|
97
|
+
details={"git_root": str(git_root)}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
except subprocess.TimeoutExpired:
|
|
101
|
+
return SafetyCheckResult(
|
|
102
|
+
passed=False,
|
|
103
|
+
message="Git status timed out",
|
|
104
|
+
)
|
|
105
|
+
except FileNotFoundError:
|
|
106
|
+
return SafetyCheckResult(
|
|
107
|
+
passed=False,
|
|
108
|
+
message="Git not found. Install git to use wicked-patch.",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def _find_git_root(path: Path) -> Optional[Path]:
|
|
113
|
+
"""Find the git repository root."""
|
|
114
|
+
current = path.resolve()
|
|
115
|
+
if current.is_file():
|
|
116
|
+
current = current.parent
|
|
117
|
+
|
|
118
|
+
while current != current.parent:
|
|
119
|
+
if (current / ".git").exists():
|
|
120
|
+
return current
|
|
121
|
+
current = current.parent
|
|
122
|
+
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class FreshnessChecker:
|
|
127
|
+
"""Check symbol graph freshness."""
|
|
128
|
+
|
|
129
|
+
DEFAULT_MAX_AGE_HOURS = 24
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def check_freshness(
|
|
133
|
+
db_path: Path,
|
|
134
|
+
max_age_hours: int = DEFAULT_MAX_AGE_HOURS
|
|
135
|
+
) -> SafetyCheckResult:
|
|
136
|
+
"""
|
|
137
|
+
Check if the symbol graph is fresh enough.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
db_path: Path to the symbol database
|
|
141
|
+
max_age_hours: Maximum age in hours before considered stale
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
SafetyCheckResult with pass/fail and age details
|
|
145
|
+
"""
|
|
146
|
+
if not db_path.exists():
|
|
147
|
+
return SafetyCheckResult(
|
|
148
|
+
passed=False,
|
|
149
|
+
message=f"Symbol database not found: {db_path}",
|
|
150
|
+
details={"db_path": str(db_path)}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
# Try to get index timestamp from database metadata
|
|
155
|
+
conn = sqlite3.connect(str(db_path))
|
|
156
|
+
cursor = conn.cursor()
|
|
157
|
+
|
|
158
|
+
# Check for metadata table
|
|
159
|
+
cursor.execute(
|
|
160
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='metadata'"
|
|
161
|
+
)
|
|
162
|
+
if not cursor.fetchone():
|
|
163
|
+
# No metadata table, fall back to file modification time
|
|
164
|
+
index_time = datetime.fromtimestamp(db_path.stat().st_mtime)
|
|
165
|
+
else:
|
|
166
|
+
# Try to get indexed_at from metadata
|
|
167
|
+
cursor.execute(
|
|
168
|
+
"SELECT value FROM metadata WHERE key = 'indexed_at'"
|
|
169
|
+
)
|
|
170
|
+
row = cursor.fetchone()
|
|
171
|
+
if row:
|
|
172
|
+
index_time = datetime.fromisoformat(row[0])
|
|
173
|
+
else:
|
|
174
|
+
index_time = datetime.fromtimestamp(db_path.stat().st_mtime)
|
|
175
|
+
|
|
176
|
+
conn.close()
|
|
177
|
+
|
|
178
|
+
# Calculate age
|
|
179
|
+
age = datetime.now() - index_time
|
|
180
|
+
age_hours = age.total_seconds() / 3600
|
|
181
|
+
|
|
182
|
+
if age_hours > max_age_hours:
|
|
183
|
+
return SafetyCheckResult(
|
|
184
|
+
passed=False,
|
|
185
|
+
message=f"Symbol graph is stale ({age_hours:.1f}h old). "
|
|
186
|
+
f"Run /wicked-garden:search:index or use --force",
|
|
187
|
+
details={
|
|
188
|
+
"index_time": index_time.isoformat(),
|
|
189
|
+
"age_hours": age_hours,
|
|
190
|
+
"max_age_hours": max_age_hours
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return SafetyCheckResult(
|
|
195
|
+
passed=True,
|
|
196
|
+
message=f"Symbol graph is fresh ({age_hours:.1f}h old)",
|
|
197
|
+
details={
|
|
198
|
+
"index_time": index_time.isoformat(),
|
|
199
|
+
"age_hours": age_hours
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
return SafetyCheckResult(
|
|
205
|
+
passed=False,
|
|
206
|
+
message=f"Failed to check graph freshness: {e}",
|
|
207
|
+
details={"error": str(e)}
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dataclass
|
|
212
|
+
class ApplyResult:
|
|
213
|
+
"""Result of patch application."""
|
|
214
|
+
success: bool
|
|
215
|
+
files_modified: List[str]
|
|
216
|
+
files_failed: List[str]
|
|
217
|
+
error: Optional[str] = None
|
|
218
|
+
rolled_back: bool = False
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class TransactionalApplicator:
|
|
222
|
+
"""
|
|
223
|
+
Apply patches transactionally with rollback on failure.
|
|
224
|
+
|
|
225
|
+
Uses file-level backups to ensure atomicity:
|
|
226
|
+
1. Backup all files before modification
|
|
227
|
+
2. Apply all patches
|
|
228
|
+
3. On failure: restore all backups
|
|
229
|
+
4. On success: delete all backups
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
def __init__(self, patches: List["Patch"]):
|
|
233
|
+
"""
|
|
234
|
+
Initialize with patches to apply.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
patches: List of Patch objects to apply
|
|
238
|
+
"""
|
|
239
|
+
self.patches = patches
|
|
240
|
+
self.backups: Dict[str, Path] = {} # original_path -> backup_path
|
|
241
|
+
|
|
242
|
+
def apply(self, dry_run: bool = False) -> ApplyResult:
|
|
243
|
+
"""
|
|
244
|
+
Apply all patches transactionally.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
dry_run: If True, only validate without applying
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
ApplyResult with success/failure details
|
|
251
|
+
"""
|
|
252
|
+
if dry_run:
|
|
253
|
+
return self._dry_run()
|
|
254
|
+
|
|
255
|
+
files_modified = []
|
|
256
|
+
files_failed = []
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
# Step 1: Create backups
|
|
260
|
+
self._create_backups()
|
|
261
|
+
|
|
262
|
+
# Step 2: Group patches by file
|
|
263
|
+
patches_by_file = self._group_by_file()
|
|
264
|
+
|
|
265
|
+
# Step 3: Apply patches to each file
|
|
266
|
+
for file_path, file_patches in patches_by_file.items():
|
|
267
|
+
try:
|
|
268
|
+
self._apply_file_patches(file_path, file_patches)
|
|
269
|
+
files_modified.append(file_path)
|
|
270
|
+
except Exception as e:
|
|
271
|
+
files_failed.append(file_path)
|
|
272
|
+
raise SafetyError(f"Failed to patch {file_path}: {e}")
|
|
273
|
+
|
|
274
|
+
# Step 4: Success - clean up backups
|
|
275
|
+
self._cleanup_backups()
|
|
276
|
+
|
|
277
|
+
return ApplyResult(
|
|
278
|
+
success=True,
|
|
279
|
+
files_modified=files_modified,
|
|
280
|
+
files_failed=[],
|
|
281
|
+
error=None,
|
|
282
|
+
rolled_back=False
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
except Exception as e:
|
|
286
|
+
# Step 3b: Failure - restore backups
|
|
287
|
+
logger.error(f"Patch application failed: {e}")
|
|
288
|
+
self._restore_backups()
|
|
289
|
+
|
|
290
|
+
return ApplyResult(
|
|
291
|
+
success=False,
|
|
292
|
+
files_modified=[],
|
|
293
|
+
files_failed=files_failed,
|
|
294
|
+
error=str(e),
|
|
295
|
+
rolled_back=True
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
def _dry_run(self) -> ApplyResult:
|
|
299
|
+
"""Validate patches without applying."""
|
|
300
|
+
files = set()
|
|
301
|
+
for patch in self.patches:
|
|
302
|
+
if patch.file_path:
|
|
303
|
+
files.add(patch.file_path)
|
|
304
|
+
if not Path(patch.file_path).exists():
|
|
305
|
+
return ApplyResult(
|
|
306
|
+
success=False,
|
|
307
|
+
files_modified=[],
|
|
308
|
+
files_failed=[patch.file_path],
|
|
309
|
+
error=f"File not found: {patch.file_path}"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
return ApplyResult(
|
|
313
|
+
success=True,
|
|
314
|
+
files_modified=list(files),
|
|
315
|
+
files_failed=[],
|
|
316
|
+
error=None
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def _create_backups(self):
|
|
320
|
+
"""Create backup copies of all files to be modified."""
|
|
321
|
+
files_to_backup = set(p.file_path for p in self.patches if p.file_path)
|
|
322
|
+
|
|
323
|
+
for file_path in files_to_backup:
|
|
324
|
+
path = Path(file_path)
|
|
325
|
+
if path.exists():
|
|
326
|
+
backup_path = path.with_suffix(path.suffix + BACKUP_SUFFIX)
|
|
327
|
+
shutil.copy2(path, backup_path)
|
|
328
|
+
self.backups[file_path] = backup_path
|
|
329
|
+
logger.debug(f"Backed up {file_path} -> {backup_path}")
|
|
330
|
+
|
|
331
|
+
def _restore_backups(self):
|
|
332
|
+
"""Restore all backed up files."""
|
|
333
|
+
for original_path, backup_path in self.backups.items():
|
|
334
|
+
if backup_path.exists():
|
|
335
|
+
shutil.copy2(backup_path, original_path)
|
|
336
|
+
backup_path.unlink()
|
|
337
|
+
logger.info(f"Restored {original_path} from backup")
|
|
338
|
+
|
|
339
|
+
self.backups.clear()
|
|
340
|
+
|
|
341
|
+
def _cleanup_backups(self):
|
|
342
|
+
"""Delete all backup files after successful application."""
|
|
343
|
+
for backup_path in self.backups.values():
|
|
344
|
+
if backup_path.exists():
|
|
345
|
+
backup_path.unlink()
|
|
346
|
+
logger.debug(f"Cleaned up backup {backup_path}")
|
|
347
|
+
|
|
348
|
+
self.backups.clear()
|
|
349
|
+
|
|
350
|
+
def _group_by_file(self) -> Dict[str, List["Patch"]]:
|
|
351
|
+
"""Group patches by file path."""
|
|
352
|
+
by_file: Dict[str, List] = {}
|
|
353
|
+
for patch in self.patches:
|
|
354
|
+
if patch.file_path:
|
|
355
|
+
if patch.file_path not in by_file:
|
|
356
|
+
by_file[patch.file_path] = []
|
|
357
|
+
by_file[patch.file_path].append(patch)
|
|
358
|
+
|
|
359
|
+
# Sort patches within each file by line number (descending)
|
|
360
|
+
# Apply from bottom to top to preserve line numbers
|
|
361
|
+
for patches in by_file.values():
|
|
362
|
+
patches.sort(key=lambda p: p.line_start, reverse=True)
|
|
363
|
+
|
|
364
|
+
return by_file
|
|
365
|
+
|
|
366
|
+
def _apply_file_patches(self, file_path: str, patches: List["Patch"]):
|
|
367
|
+
"""Apply patches to a single file."""
|
|
368
|
+
path = Path(file_path)
|
|
369
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
370
|
+
lines = f.read().split("\n")
|
|
371
|
+
|
|
372
|
+
for patch in patches:
|
|
373
|
+
lines = self._apply_patch(lines, patch)
|
|
374
|
+
|
|
375
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
376
|
+
f.write("\n".join(lines))
|
|
377
|
+
|
|
378
|
+
def _apply_patch(self, lines: List[str], patch: "Patch") -> List[str]:
|
|
379
|
+
"""Apply a single patch to lines."""
|
|
380
|
+
if patch.line_start > patch.line_end:
|
|
381
|
+
# Insertion: insert new content after line_start
|
|
382
|
+
new_lines = patch.new_content.split("\n") if patch.new_content else []
|
|
383
|
+
return lines[:patch.line_start] + new_lines + lines[patch.line_start:]
|
|
384
|
+
elif not patch.new_content.strip():
|
|
385
|
+
# Deletion: remove lines
|
|
386
|
+
return lines[:patch.line_start - 1] + lines[patch.line_end:]
|
|
387
|
+
else:
|
|
388
|
+
# Replacement: replace lines
|
|
389
|
+
new_lines = patch.new_content.split("\n")
|
|
390
|
+
return lines[:patch.line_start - 1] + new_lines + lines[patch.line_end:]
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def run_safety_checks(
|
|
394
|
+
files: List[str],
|
|
395
|
+
db_path: Path,
|
|
396
|
+
force: bool = False,
|
|
397
|
+
skip_git: bool = False,
|
|
398
|
+
max_age_hours: int = 24,
|
|
399
|
+
) -> Tuple[bool, List[SafetyCheckResult]]:
|
|
400
|
+
"""
|
|
401
|
+
Run all safety checks.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
files: List of files that will be modified
|
|
405
|
+
db_path: Path to symbol database
|
|
406
|
+
force: If True, allow stale graph
|
|
407
|
+
skip_git: If True, skip git clean check
|
|
408
|
+
max_age_hours: Maximum graph age
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
Tuple of (all_passed, list of results)
|
|
412
|
+
"""
|
|
413
|
+
results = []
|
|
414
|
+
|
|
415
|
+
# Git clean check
|
|
416
|
+
if not skip_git and files:
|
|
417
|
+
git_result = GitSafetyChecker.check_git_clean(Path(files[0]))
|
|
418
|
+
results.append(git_result)
|
|
419
|
+
if not git_result.passed:
|
|
420
|
+
return False, results
|
|
421
|
+
|
|
422
|
+
# Freshness check — skipped when no db_path (brain-backed mode, no local db)
|
|
423
|
+
if not force and db_path is not None:
|
|
424
|
+
freshness_result = FreshnessChecker.check_freshness(db_path, max_age_hours)
|
|
425
|
+
results.append(freshness_result)
|
|
426
|
+
if not freshness_result.passed:
|
|
427
|
+
return False, results
|
|
428
|
+
|
|
429
|
+
return True, results
|
|
File without changes
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
jam/_bus_consumers.py — Bus event consumers for the jam domain.
|
|
4
|
+
|
|
5
|
+
Synthesis-trigger consumer: tracks per-session Round 1 persona contributions
|
|
6
|
+
and emits `wicked.session.synthesis_ready` when either:
|
|
7
|
+
- contribution count for a session reaches its `expected_persona_count`, OR
|
|
8
|
+
- 120 seconds have elapsed since the last contribution for that session.
|
|
9
|
+
|
|
10
|
+
Poll-on-invoke pattern — callable from jam commands (or any startup hook) to
|
|
11
|
+
drain pending events. Fails open: bus errors never propagate.
|
|
12
|
+
|
|
13
|
+
Round 1 only: contributions with round != 1 are ignored so Round 2 sequential
|
|
14
|
+
persona dialogue is not interfered with.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
import time
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Dict, List, Optional
|
|
26
|
+
|
|
27
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger("wicked-jam.bus-consumers")
|
|
30
|
+
|
|
31
|
+
# Timeout: if no new contribution arrives within this window, mark synthesis ready.
|
|
32
|
+
_SYNTHESIS_TIMEOUT_SECONDS = 120.0
|
|
33
|
+
|
|
34
|
+
# Default expected persona count when the session object lacks one. 4 matches the
|
|
35
|
+
# /wicked-garden:jam:quick default; full brainstorms typically override via the
|
|
36
|
+
# session record.
|
|
37
|
+
_DEFAULT_EXPECTED_PERSONAS = 4
|
|
38
|
+
|
|
39
|
+
# Ledger file tracking per-session contribution state. Separate from the global
|
|
40
|
+
# _bus_processed.json ledger because we track *per-session aggregate*, not
|
|
41
|
+
# per-event idempotency.
|
|
42
|
+
_JAM_LEDGER_FILE = os.path.join(
|
|
43
|
+
os.path.expanduser("~"),
|
|
44
|
+
".something-wicked", "wicked-garden", "local",
|
|
45
|
+
"wicked-jam", "_synthesis_tracker.json",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _load_tracker() -> Dict[str, Dict[str, Any]]:
|
|
50
|
+
"""Load the per-session tracker. Returns {session_id: {...}}."""
|
|
51
|
+
try:
|
|
52
|
+
with open(_JAM_LEDGER_FILE, "r") as f:
|
|
53
|
+
data = json.load(f)
|
|
54
|
+
return data if isinstance(data, dict) else {}
|
|
55
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
56
|
+
return {}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _save_tracker(tracker: Dict[str, Dict[str, Any]]) -> None:
|
|
60
|
+
"""Persist the tracker. Fails open."""
|
|
61
|
+
try:
|
|
62
|
+
os.makedirs(os.path.dirname(_JAM_LEDGER_FILE), exist_ok=True)
|
|
63
|
+
with open(_JAM_LEDGER_FILE, "w") as f:
|
|
64
|
+
json.dump(tracker, f)
|
|
65
|
+
except OSError:
|
|
66
|
+
pass # fail open
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _lookup_expected_persona_count(session_id: str) -> Optional[int]:
|
|
70
|
+
"""Look up expected_persona_count from the jam session record, if present."""
|
|
71
|
+
try:
|
|
72
|
+
from _domain_store import DomainStore
|
|
73
|
+
ds = DomainStore("wicked-jam")
|
|
74
|
+
sessions = ds.list("sessions") or []
|
|
75
|
+
for s in sessions:
|
|
76
|
+
if s.get("id") == session_id or s.get("session_id") == session_id:
|
|
77
|
+
n = s.get("expected_persona_count")
|
|
78
|
+
if isinstance(n, int) and n > 0:
|
|
79
|
+
return n
|
|
80
|
+
return None
|
|
81
|
+
except Exception:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _resolve_expected(session_id: str, tracker_entry: Dict[str, Any]) -> int:
|
|
86
|
+
"""Return expected_persona_count, preferring tracker → store → default."""
|
|
87
|
+
n = tracker_entry.get("expected_persona_count")
|
|
88
|
+
if isinstance(n, int) and n > 0:
|
|
89
|
+
return n
|
|
90
|
+
looked = _lookup_expected_persona_count(session_id)
|
|
91
|
+
if looked:
|
|
92
|
+
return looked
|
|
93
|
+
return _DEFAULT_EXPECTED_PERSONAS
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _emit_synthesis_ready(
|
|
97
|
+
session_id: str,
|
|
98
|
+
received: int,
|
|
99
|
+
expected: int,
|
|
100
|
+
reason: str,
|
|
101
|
+
chain_id: Optional[str],
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Emit wicked.session.synthesis_ready. Fails open."""
|
|
104
|
+
try:
|
|
105
|
+
from _bus import emit_event
|
|
106
|
+
emit_event(
|
|
107
|
+
"wicked.session.synthesis_ready",
|
|
108
|
+
{
|
|
109
|
+
"session_id": session_id,
|
|
110
|
+
"received_count": received,
|
|
111
|
+
"expected_count": expected,
|
|
112
|
+
"reason": reason, # "all_received" | "timeout"
|
|
113
|
+
},
|
|
114
|
+
chain_id=chain_id,
|
|
115
|
+
)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.debug(f"synthesis_ready emit failed (non-blocking): {e}")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def process_pending_events(now: Optional[float] = None) -> List[str]:
|
|
121
|
+
"""Poll wicked.persona.contributed events, track per-session counts, and emit
|
|
122
|
+
wicked.session.synthesis_ready once a session reaches expected count or times out.
|
|
123
|
+
|
|
124
|
+
Returns list of action strings (for logging / tests).
|
|
125
|
+
`now` override enables deterministic testing; real calls pass None.
|
|
126
|
+
"""
|
|
127
|
+
actions: List[str] = []
|
|
128
|
+
now_ts = time.time() if now is None else now
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
from _bus import poll_pending, ack_events
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.debug(f"bus import failed (non-blocking): {e}")
|
|
134
|
+
return actions
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
events = poll_pending(event_type_prefix="wicked.persona.") or []
|
|
138
|
+
|
|
139
|
+
tracker = _load_tracker()
|
|
140
|
+
max_event_id = 0
|
|
141
|
+
changed = False
|
|
142
|
+
|
|
143
|
+
for event in events:
|
|
144
|
+
event_id = event.get("event_id", 0)
|
|
145
|
+
if isinstance(event_id, int) and event_id > max_event_id:
|
|
146
|
+
max_event_id = event_id
|
|
147
|
+
|
|
148
|
+
if event.get("event_type") != "wicked.persona.contributed":
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
payload = event.get("payload") or {}
|
|
152
|
+
session_id = payload.get("session_id")
|
|
153
|
+
if not session_id:
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
# Round 1 only — Round 2 sequential dialogue must not be triggered.
|
|
157
|
+
round_num = payload.get("round", 1)
|
|
158
|
+
try:
|
|
159
|
+
round_num = int(round_num)
|
|
160
|
+
except (TypeError, ValueError):
|
|
161
|
+
round_num = 1
|
|
162
|
+
if round_num != 1:
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
metadata = event.get("metadata") or {}
|
|
166
|
+
chain_id = metadata.get("chain_id")
|
|
167
|
+
|
|
168
|
+
entry = tracker.get(session_id) or {
|
|
169
|
+
"received_count": 0,
|
|
170
|
+
"last_contribution_ts": now_ts,
|
|
171
|
+
"processed_event_ids": [],
|
|
172
|
+
"ready_emitted": False,
|
|
173
|
+
"chain_id": None,
|
|
174
|
+
"expected_persona_count": None,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# Per-event idempotency inside this session's ledger entry
|
|
178
|
+
processed_ids = entry.get("processed_event_ids") or []
|
|
179
|
+
if event_id and event_id in processed_ids:
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
entry["received_count"] = int(entry.get("received_count", 0)) + 1
|
|
183
|
+
entry["last_contribution_ts"] = now_ts
|
|
184
|
+
if event_id:
|
|
185
|
+
processed_ids.append(event_id)
|
|
186
|
+
# Cap ledger size — last 64 ids per session is plenty for Round 1.
|
|
187
|
+
entry["processed_event_ids"] = processed_ids[-64:]
|
|
188
|
+
if chain_id and not entry.get("chain_id"):
|
|
189
|
+
entry["chain_id"] = chain_id
|
|
190
|
+
# Allow first-contribution emitter to seed expected count via payload.
|
|
191
|
+
if not entry.get("expected_persona_count"):
|
|
192
|
+
n = payload.get("expected_persona_count")
|
|
193
|
+
if isinstance(n, int) and n > 0:
|
|
194
|
+
entry["expected_persona_count"] = n
|
|
195
|
+
|
|
196
|
+
tracker[session_id] = entry
|
|
197
|
+
changed = True
|
|
198
|
+
|
|
199
|
+
if not entry.get("ready_emitted"):
|
|
200
|
+
expected = _resolve_expected(session_id, entry)
|
|
201
|
+
if entry["received_count"] >= expected:
|
|
202
|
+
_emit_synthesis_ready(
|
|
203
|
+
session_id,
|
|
204
|
+
received=entry["received_count"],
|
|
205
|
+
expected=expected,
|
|
206
|
+
reason="all_received",
|
|
207
|
+
chain_id=entry.get("chain_id"),
|
|
208
|
+
)
|
|
209
|
+
entry["ready_emitted"] = True
|
|
210
|
+
actions.append(
|
|
211
|
+
f"synthesis_ready emitted for {session_id} "
|
|
212
|
+
f"({entry['received_count']}/{expected}, all_received)"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Timeout sweep — any session with unemitted readiness whose last
|
|
216
|
+
# contribution is older than the timeout gets flushed.
|
|
217
|
+
for session_id, entry in list(tracker.items()):
|
|
218
|
+
if entry.get("ready_emitted"):
|
|
219
|
+
continue
|
|
220
|
+
last = float(entry.get("last_contribution_ts") or 0.0)
|
|
221
|
+
if last <= 0:
|
|
222
|
+
continue
|
|
223
|
+
if (now_ts - last) >= _SYNTHESIS_TIMEOUT_SECONDS:
|
|
224
|
+
received = int(entry.get("received_count", 0))
|
|
225
|
+
if received <= 0:
|
|
226
|
+
continue
|
|
227
|
+
expected = _resolve_expected(session_id, entry)
|
|
228
|
+
_emit_synthesis_ready(
|
|
229
|
+
session_id,
|
|
230
|
+
received=received,
|
|
231
|
+
expected=expected,
|
|
232
|
+
reason="timeout",
|
|
233
|
+
chain_id=entry.get("chain_id"),
|
|
234
|
+
)
|
|
235
|
+
entry["ready_emitted"] = True
|
|
236
|
+
tracker[session_id] = entry
|
|
237
|
+
changed = True
|
|
238
|
+
actions.append(
|
|
239
|
+
f"synthesis_ready emitted for {session_id} "
|
|
240
|
+
f"({received}/{expected}, timeout)"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Prune emitted sessions older than 24h to bound ledger growth.
|
|
244
|
+
cutoff = now_ts - 86400
|
|
245
|
+
pruned = {
|
|
246
|
+
sid: e for sid, e in tracker.items()
|
|
247
|
+
if not (e.get("ready_emitted") and float(e.get("last_contribution_ts") or 0.0) < cutoff)
|
|
248
|
+
}
|
|
249
|
+
if len(pruned) != len(tracker):
|
|
250
|
+
tracker = pruned
|
|
251
|
+
changed = True
|
|
252
|
+
|
|
253
|
+
if changed:
|
|
254
|
+
_save_tracker(tracker)
|
|
255
|
+
|
|
256
|
+
if max_event_id > 0:
|
|
257
|
+
ack_events(max_event_id)
|
|
258
|
+
|
|
259
|
+
except Exception as e:
|
|
260
|
+
logger.debug(f"jam bus consumer error (non-blocking): {e}")
|
|
261
|
+
|
|
262
|
+
return actions
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
if __name__ == "__main__":
|
|
266
|
+
# Manual invocation for testing / debug:
|
|
267
|
+
# python3 scripts/jam/_bus_consumers.py
|
|
268
|
+
logging.basicConfig(level=logging.INFO)
|
|
269
|
+
results = process_pending_events()
|
|
270
|
+
for line in results:
|
|
271
|
+
print(line)
|