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,929 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Propagation Engine for symbol graph traversal.
|
|
3
|
+
|
|
4
|
+
Orchestrates code generation across multiple files using the lineage graph.
|
|
5
|
+
When a change is made to a symbol, this engine:
|
|
6
|
+
1. Traces all dependent symbols via lineage paths
|
|
7
|
+
2. Groups affected symbols by file and language
|
|
8
|
+
3. Invokes appropriate generators for each file
|
|
9
|
+
4. Aggregates patches into a unified PatchSet
|
|
10
|
+
|
|
11
|
+
This is the core of the "higher-level" approach to code changes,
|
|
12
|
+
automatically propagating changes across all affected code.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import sqlite3
|
|
19
|
+
from collections import defaultdict
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
23
|
+
import logging
|
|
24
|
+
|
|
25
|
+
from .base import (
|
|
26
|
+
ChangeSpec,
|
|
27
|
+
ChangeType,
|
|
28
|
+
FieldSpec,
|
|
29
|
+
GeneratorRegistry,
|
|
30
|
+
Patch,
|
|
31
|
+
PatchSet,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class AffectedSymbol:
|
|
39
|
+
"""A symbol affected by a change."""
|
|
40
|
+
id: str
|
|
41
|
+
name: str
|
|
42
|
+
type: str
|
|
43
|
+
file_path: str
|
|
44
|
+
line_start: int
|
|
45
|
+
line_end: Optional[int] = None
|
|
46
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
47
|
+
impact_type: str = "direct" # direct, upstream, downstream
|
|
48
|
+
distance: int = 0 # Hops from the source of change
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class PropagationPlan:
|
|
53
|
+
"""
|
|
54
|
+
Plan for propagating a change.
|
|
55
|
+
|
|
56
|
+
Contains all affected symbols organized by their relationship
|
|
57
|
+
to the source of the change.
|
|
58
|
+
"""
|
|
59
|
+
source_symbol: AffectedSymbol
|
|
60
|
+
direct_impacts: List[AffectedSymbol] = field(default_factory=list)
|
|
61
|
+
upstream_impacts: List[AffectedSymbol] = field(default_factory=list)
|
|
62
|
+
downstream_impacts: List[AffectedSymbol] = field(default_factory=list)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def all_affected(self) -> List[AffectedSymbol]:
|
|
66
|
+
return [self.source_symbol] + self.direct_impacts + self.upstream_impacts + self.downstream_impacts
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def files_affected(self) -> Set[str]:
|
|
70
|
+
return {s.file_path for s in self.all_affected if s.file_path}
|
|
71
|
+
|
|
72
|
+
def by_file(self) -> Dict[str, List[AffectedSymbol]]:
|
|
73
|
+
"""Group affected symbols by file."""
|
|
74
|
+
by_file: Dict[str, List[AffectedSymbol]] = defaultdict(list)
|
|
75
|
+
for symbol in self.all_affected:
|
|
76
|
+
if symbol.file_path:
|
|
77
|
+
by_file[symbol.file_path].append(symbol)
|
|
78
|
+
return dict(by_file)
|
|
79
|
+
|
|
80
|
+
def summary(self) -> str:
|
|
81
|
+
"""Human-readable summary."""
|
|
82
|
+
lines = [
|
|
83
|
+
f"Source: {self.source_symbol.name} ({self.source_symbol.type})",
|
|
84
|
+
f"Direct impacts: {len(self.direct_impacts)}",
|
|
85
|
+
f"Upstream impacts: {len(self.upstream_impacts)}",
|
|
86
|
+
f"Downstream impacts: {len(self.downstream_impacts)}",
|
|
87
|
+
f"Total files affected: {len(self.files_affected)}",
|
|
88
|
+
]
|
|
89
|
+
return "\n".join(lines)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class PropagationEngine:
|
|
93
|
+
"""
|
|
94
|
+
Engine for propagating changes across the codebase.
|
|
95
|
+
|
|
96
|
+
Uses the symbol graph to find all affected symbols, then
|
|
97
|
+
coordinates generators to produce patches for each file.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
# Map change types to how they propagate.
|
|
101
|
+
# include_refs: reference types to follow. Empty set means follow all types.
|
|
102
|
+
# Cross-file ref types from additional tables: calls, imports, extends, uses
|
|
103
|
+
PROPAGATION_RULES = {
|
|
104
|
+
ChangeType.ADD_FIELD: {
|
|
105
|
+
"propagate_downstream": True, # Add to UI bindings, tests
|
|
106
|
+
"propagate_upstream": False, # Don't add to callers
|
|
107
|
+
"include_refs": {"binds_to", "maps_to", "uses", "extends", "imports"},
|
|
108
|
+
},
|
|
109
|
+
ChangeType.REMOVE_FIELD: {
|
|
110
|
+
"propagate_downstream": True, # Remove from UI, tests
|
|
111
|
+
"propagate_upstream": True, # Update callers
|
|
112
|
+
"include_refs": {"binds_to", "maps_to", "uses", "calls", "extends", "imports"},
|
|
113
|
+
},
|
|
114
|
+
ChangeType.RENAME_FIELD: {
|
|
115
|
+
"propagate_downstream": True,
|
|
116
|
+
"propagate_upstream": True,
|
|
117
|
+
"include_refs": {"binds_to", "maps_to", "uses", "calls", "extends", "imports"},
|
|
118
|
+
},
|
|
119
|
+
ChangeType.MODIFY_FIELD: {
|
|
120
|
+
"propagate_downstream": True,
|
|
121
|
+
"propagate_upstream": False,
|
|
122
|
+
"include_refs": {"binds_to", "maps_to", "extends"},
|
|
123
|
+
},
|
|
124
|
+
ChangeType.ADD_VALIDATION: {
|
|
125
|
+
"propagate_downstream": True, # Add to UI validation
|
|
126
|
+
"propagate_upstream": False,
|
|
127
|
+
"include_refs": {"binds_to", "extends"},
|
|
128
|
+
},
|
|
129
|
+
ChangeType.ADD_COLUMN: {
|
|
130
|
+
"propagate_downstream": False, # DB is the sink
|
|
131
|
+
"propagate_upstream": True, # Update entity
|
|
132
|
+
"include_refs": {"maps_to", "extends"},
|
|
133
|
+
},
|
|
134
|
+
ChangeType.RENAME_COLUMN: {
|
|
135
|
+
"propagate_downstream": False, # DB is the sink
|
|
136
|
+
"propagate_upstream": True, # Update entity refs
|
|
137
|
+
"include_refs": {"maps_to", "extends"},
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
def __init__(self, db_path: Path):
|
|
142
|
+
"""
|
|
143
|
+
Initialize the engine.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
db_path: Path to the symbol graph SQLite database
|
|
147
|
+
"""
|
|
148
|
+
self.db_path = db_path
|
|
149
|
+
self._conn: Optional[sqlite3.Connection] = None
|
|
150
|
+
|
|
151
|
+
def _get_conn(self) -> sqlite3.Connection:
|
|
152
|
+
"""Get or create database connection."""
|
|
153
|
+
if self._conn is None:
|
|
154
|
+
self._conn = sqlite3.connect(str(self.db_path))
|
|
155
|
+
self._conn.row_factory = sqlite3.Row
|
|
156
|
+
return self._conn
|
|
157
|
+
|
|
158
|
+
def close(self):
|
|
159
|
+
"""Close database connection."""
|
|
160
|
+
if self._conn:
|
|
161
|
+
self._conn.close()
|
|
162
|
+
self._conn = None
|
|
163
|
+
|
|
164
|
+
def plan_propagation(
|
|
165
|
+
self,
|
|
166
|
+
change_spec: ChangeSpec,
|
|
167
|
+
max_depth: int = 5,
|
|
168
|
+
) -> PropagationPlan:
|
|
169
|
+
"""
|
|
170
|
+
Create a plan for propagating a change.
|
|
171
|
+
|
|
172
|
+
Analyzes the symbol graph to find all affected symbols.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
change_spec: The change to propagate
|
|
176
|
+
max_depth: Maximum traversal depth
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
PropagationPlan with all affected symbols
|
|
180
|
+
"""
|
|
181
|
+
conn = self._get_conn()
|
|
182
|
+
cursor = conn.cursor()
|
|
183
|
+
|
|
184
|
+
# Get the source symbol
|
|
185
|
+
source = self._get_symbol(cursor, change_spec.target_symbol_id)
|
|
186
|
+
if not source:
|
|
187
|
+
raise ValueError(f"Symbol not found: {change_spec.target_symbol_id}")
|
|
188
|
+
|
|
189
|
+
source_affected = AffectedSymbol(
|
|
190
|
+
id=source["id"],
|
|
191
|
+
name=source["name"],
|
|
192
|
+
type=source["type"],
|
|
193
|
+
file_path=source["file_path"],
|
|
194
|
+
line_start=source.get("line_start", 0),
|
|
195
|
+
line_end=source.get("line_end"),
|
|
196
|
+
metadata=json.loads(source.get("metadata") or "{}"),
|
|
197
|
+
impact_type="source",
|
|
198
|
+
distance=0,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
plan = PropagationPlan(source_symbol=source_affected)
|
|
202
|
+
|
|
203
|
+
# Get propagation rules
|
|
204
|
+
rules = self.PROPAGATION_RULES.get(change_spec.change_type, {})
|
|
205
|
+
include_refs = rules.get("include_refs", set())
|
|
206
|
+
|
|
207
|
+
# Shared visited set to prevent duplicate symbols across all impact lists
|
|
208
|
+
visited: Set[str] = {source["id"]}
|
|
209
|
+
|
|
210
|
+
# Find direct impacts (symbols that reference or are referenced by source)
|
|
211
|
+
direct_refs = self._get_direct_references(cursor, source["id"])
|
|
212
|
+
# Batch-fetch all referenced symbols to avoid N+1 queries
|
|
213
|
+
filtered_refs = [r for r in direct_refs if r["ref_type"] in include_refs or not include_refs]
|
|
214
|
+
ref_ids = [r["related_id"] for r in filtered_refs]
|
|
215
|
+
symbols_by_id = self._get_symbols_batch(cursor, ref_ids)
|
|
216
|
+
for ref in filtered_refs:
|
|
217
|
+
symbol = symbols_by_id.get(ref["related_id"])
|
|
218
|
+
if symbol and symbol["id"] not in visited:
|
|
219
|
+
visited.add(symbol["id"])
|
|
220
|
+
plan.direct_impacts.append(AffectedSymbol(
|
|
221
|
+
id=symbol["id"],
|
|
222
|
+
name=symbol["name"],
|
|
223
|
+
type=symbol["type"],
|
|
224
|
+
file_path=symbol["file_path"],
|
|
225
|
+
line_start=symbol.get("line_start", 0),
|
|
226
|
+
line_end=symbol.get("line_end"),
|
|
227
|
+
metadata=json.loads(symbol.get("metadata") or "{}"),
|
|
228
|
+
impact_type="direct",
|
|
229
|
+
distance=1,
|
|
230
|
+
))
|
|
231
|
+
|
|
232
|
+
# Trace upstream (who uses/depends on this symbol)
|
|
233
|
+
if rules.get("propagate_upstream", True):
|
|
234
|
+
upstream = self._trace_upstream(
|
|
235
|
+
cursor, source["id"], include_refs, max_depth, visited
|
|
236
|
+
)
|
|
237
|
+
plan.upstream_impacts.extend(upstream)
|
|
238
|
+
|
|
239
|
+
# Trace downstream (what this symbol flows to)
|
|
240
|
+
if rules.get("propagate_downstream", True):
|
|
241
|
+
downstream = self._trace_downstream(
|
|
242
|
+
cursor, source["id"], include_refs, max_depth, visited
|
|
243
|
+
)
|
|
244
|
+
plan.downstream_impacts.extend(downstream)
|
|
245
|
+
|
|
246
|
+
return plan
|
|
247
|
+
|
|
248
|
+
def generate_patches(
|
|
249
|
+
self,
|
|
250
|
+
change_spec: ChangeSpec,
|
|
251
|
+
plan: Optional[PropagationPlan] = None,
|
|
252
|
+
max_depth: int = 5,
|
|
253
|
+
) -> PatchSet:
|
|
254
|
+
"""
|
|
255
|
+
Generate patches for all affected files.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
change_spec: The change to apply
|
|
259
|
+
plan: Optional pre-computed propagation plan
|
|
260
|
+
max_depth: Maximum traversal depth if plan not provided
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
PatchSet with all generated patches
|
|
264
|
+
"""
|
|
265
|
+
if plan is None:
|
|
266
|
+
plan = self.plan_propagation(change_spec, max_depth)
|
|
267
|
+
|
|
268
|
+
patch_set = PatchSet(change_spec=change_spec)
|
|
269
|
+
|
|
270
|
+
# Process each affected file
|
|
271
|
+
for file_path, symbols in plan.by_file().items():
|
|
272
|
+
try:
|
|
273
|
+
patches = self._generate_file_patches(
|
|
274
|
+
change_spec, file_path, symbols
|
|
275
|
+
)
|
|
276
|
+
patch_set.patches.extend(patches)
|
|
277
|
+
except Exception as e:
|
|
278
|
+
logger.error(f"Failed to generate patches for {file_path}: {e}")
|
|
279
|
+
patch_set.errors.append(f"{file_path}: {str(e)}")
|
|
280
|
+
|
|
281
|
+
# Add warnings for incomplete propagation
|
|
282
|
+
self._add_warnings(patch_set, plan)
|
|
283
|
+
|
|
284
|
+
return patch_set
|
|
285
|
+
|
|
286
|
+
def propagate(
|
|
287
|
+
self,
|
|
288
|
+
change_spec: ChangeSpec,
|
|
289
|
+
dry_run: bool = True,
|
|
290
|
+
max_depth: int = 5,
|
|
291
|
+
) -> PatchSet:
|
|
292
|
+
"""
|
|
293
|
+
Full propagation: plan, generate patches, and optionally apply.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
change_spec: The change to propagate
|
|
297
|
+
dry_run: If True, only generate patches without applying
|
|
298
|
+
max_depth: Maximum traversal depth
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
PatchSet with all changes
|
|
302
|
+
"""
|
|
303
|
+
# Plan the propagation
|
|
304
|
+
plan = self.plan_propagation(change_spec, max_depth)
|
|
305
|
+
|
|
306
|
+
# Generate patches
|
|
307
|
+
patch_set = self.generate_patches(change_spec, plan, max_depth)
|
|
308
|
+
|
|
309
|
+
# Apply patches if not dry run
|
|
310
|
+
if not dry_run and not patch_set.has_errors:
|
|
311
|
+
self._apply_patches(patch_set)
|
|
312
|
+
|
|
313
|
+
return patch_set
|
|
314
|
+
|
|
315
|
+
# Private helper methods
|
|
316
|
+
|
|
317
|
+
def _get_symbol(self, cursor, symbol_id: str) -> Optional[Dict[str, Any]]:
|
|
318
|
+
"""Get a symbol by ID."""
|
|
319
|
+
cursor.execute(
|
|
320
|
+
"""
|
|
321
|
+
SELECT id, name, type, file_path, line_start, line_end, metadata, layer
|
|
322
|
+
FROM symbols
|
|
323
|
+
WHERE id = ?
|
|
324
|
+
""",
|
|
325
|
+
(symbol_id,)
|
|
326
|
+
)
|
|
327
|
+
row = cursor.fetchone()
|
|
328
|
+
return dict(row) if row else None
|
|
329
|
+
|
|
330
|
+
def _get_symbols_batch(self, cursor, symbol_ids: List[str]) -> Dict[str, Dict[str, Any]]:
|
|
331
|
+
"""Get multiple symbols by ID in a single query.
|
|
332
|
+
|
|
333
|
+
Returns a dict mapping symbol_id -> symbol dict.
|
|
334
|
+
"""
|
|
335
|
+
if not symbol_ids:
|
|
336
|
+
return {}
|
|
337
|
+
placeholders = ",".join("?" * len(symbol_ids))
|
|
338
|
+
cursor.execute(
|
|
339
|
+
f"""
|
|
340
|
+
SELECT id, name, type, file_path, line_start, line_end, metadata, layer
|
|
341
|
+
FROM symbols
|
|
342
|
+
WHERE id IN ({placeholders})
|
|
343
|
+
""",
|
|
344
|
+
symbol_ids,
|
|
345
|
+
)
|
|
346
|
+
return {row["id"]: dict(row) for row in cursor.fetchall()}
|
|
347
|
+
|
|
348
|
+
def _get_direct_references(
|
|
349
|
+
self,
|
|
350
|
+
cursor,
|
|
351
|
+
symbol_id: str
|
|
352
|
+
) -> List[Dict[str, Any]]:
|
|
353
|
+
"""Get all references to/from a symbol across all reference tables.
|
|
354
|
+
|
|
355
|
+
Queries multiple tables to ensure cross-file references are captured:
|
|
356
|
+
- refs: Primary reference table with confidence and evidence
|
|
357
|
+
- symbol_refs: Unified bidirectional cross-reference table
|
|
358
|
+
- symbol_calls: Call graph edges
|
|
359
|
+
- symbol_imports: Import relationships
|
|
360
|
+
- symbol_bases: Inheritance/extension relationships
|
|
361
|
+
- symbol_dependents: Reverse dependency edges
|
|
362
|
+
- derived_refs: Computed/inferred references from reasoning
|
|
363
|
+
"""
|
|
364
|
+
results = []
|
|
365
|
+
seen_pairs: Set[Tuple[str, str]] = set()
|
|
366
|
+
|
|
367
|
+
def _add_result(related_id: str, ref_type: str, direction: str, confidence: str = "medium"):
|
|
368
|
+
key = (related_id, ref_type)
|
|
369
|
+
if key not in seen_pairs:
|
|
370
|
+
seen_pairs.add(key)
|
|
371
|
+
results.append({
|
|
372
|
+
"related_id": related_id,
|
|
373
|
+
"ref_type": ref_type,
|
|
374
|
+
"confidence": confidence,
|
|
375
|
+
"direction": direction,
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
# 1. Primary refs table (has confidence + evidence)
|
|
379
|
+
try:
|
|
380
|
+
cursor.execute(
|
|
381
|
+
"SELECT target_id, ref_type, confidence FROM refs WHERE source_id = ?",
|
|
382
|
+
(symbol_id,)
|
|
383
|
+
)
|
|
384
|
+
for row in cursor.fetchall():
|
|
385
|
+
_add_result(row["target_id"], row["ref_type"], "outgoing", row["confidence"] or "medium")
|
|
386
|
+
|
|
387
|
+
cursor.execute(
|
|
388
|
+
"SELECT source_id, ref_type, confidence FROM refs WHERE target_id = ?",
|
|
389
|
+
(symbol_id,)
|
|
390
|
+
)
|
|
391
|
+
for row in cursor.fetchall():
|
|
392
|
+
_add_result(row["source_id"], row["ref_type"], "incoming", row["confidence"] or "medium")
|
|
393
|
+
except sqlite3.OperationalError:
|
|
394
|
+
pass # Table may not exist in older schemas
|
|
395
|
+
|
|
396
|
+
# 2. symbol_refs table (unified cross-reference lookup)
|
|
397
|
+
for tbl_query in [
|
|
398
|
+
("SELECT target_id, ref_type FROM symbol_refs WHERE source_id = ?", "outgoing", "target_id"),
|
|
399
|
+
("SELECT source_id, ref_type FROM symbol_refs WHERE target_id = ?", "incoming", "source_id"),
|
|
400
|
+
]:
|
|
401
|
+
try:
|
|
402
|
+
cursor.execute(tbl_query[0], (symbol_id,))
|
|
403
|
+
for row in cursor.fetchall():
|
|
404
|
+
_add_result(row[tbl_query[2]], row["ref_type"], tbl_query[1])
|
|
405
|
+
except sqlite3.OperationalError:
|
|
406
|
+
pass # Table may not exist in older schemas
|
|
407
|
+
|
|
408
|
+
# 3. symbol_calls table (call graph)
|
|
409
|
+
for tbl_query in [
|
|
410
|
+
("SELECT target_id FROM symbol_calls WHERE symbol_id = ?", "outgoing", "calls"),
|
|
411
|
+
("SELECT symbol_id FROM symbol_calls WHERE target_id = ?", "incoming", "calls"),
|
|
412
|
+
]:
|
|
413
|
+
try:
|
|
414
|
+
cursor.execute(tbl_query[0], (symbol_id,))
|
|
415
|
+
for row in cursor.fetchall():
|
|
416
|
+
_add_result(row[0], tbl_query[2], tbl_query[1])
|
|
417
|
+
except sqlite3.OperationalError:
|
|
418
|
+
pass # intentional: table may not exist in older index versions
|
|
419
|
+
|
|
420
|
+
# 4. symbol_imports table
|
|
421
|
+
for tbl_query in [
|
|
422
|
+
("SELECT target_id FROM symbol_imports WHERE symbol_id = ?", "outgoing", "imports"),
|
|
423
|
+
("SELECT symbol_id FROM symbol_imports WHERE target_id = ?", "incoming", "imports"),
|
|
424
|
+
]:
|
|
425
|
+
try:
|
|
426
|
+
cursor.execute(tbl_query[0], (symbol_id,))
|
|
427
|
+
for row in cursor.fetchall():
|
|
428
|
+
_add_result(row[0], tbl_query[2], tbl_query[1])
|
|
429
|
+
except sqlite3.OperationalError:
|
|
430
|
+
pass # intentional: table may not exist in older index versions
|
|
431
|
+
|
|
432
|
+
# 5. symbol_bases table (inheritance)
|
|
433
|
+
for tbl_query in [
|
|
434
|
+
("SELECT base_id FROM symbol_bases WHERE symbol_id = ?", "outgoing", "extends"),
|
|
435
|
+
("SELECT symbol_id FROM symbol_bases WHERE base_id = ?", "incoming", "extends"),
|
|
436
|
+
]:
|
|
437
|
+
try:
|
|
438
|
+
cursor.execute(tbl_query[0], (symbol_id,))
|
|
439
|
+
for row in cursor.fetchall():
|
|
440
|
+
_add_result(row[0], tbl_query[2], tbl_query[1])
|
|
441
|
+
except sqlite3.OperationalError:
|
|
442
|
+
pass # intentional: table may not exist in older index versions
|
|
443
|
+
|
|
444
|
+
# 6. symbol_dependents table (reverse dependencies)
|
|
445
|
+
for tbl_query in [
|
|
446
|
+
("SELECT dependent_id FROM symbol_dependents WHERE symbol_id = ?", "outgoing", "uses"),
|
|
447
|
+
("SELECT symbol_id FROM symbol_dependents WHERE dependent_id = ?", "incoming", "uses"),
|
|
448
|
+
]:
|
|
449
|
+
try:
|
|
450
|
+
cursor.execute(tbl_query[0], (symbol_id,))
|
|
451
|
+
for row in cursor.fetchall():
|
|
452
|
+
_add_result(row[0], tbl_query[2], tbl_query[1])
|
|
453
|
+
except sqlite3.OperationalError:
|
|
454
|
+
pass # intentional: table may not exist in older index versions
|
|
455
|
+
|
|
456
|
+
# 7. derived_refs table (computed/inferred references)
|
|
457
|
+
for tbl_query in [
|
|
458
|
+
("SELECT target_id, ref_type, confidence FROM derived_refs WHERE source_id = ?", "outgoing", "target_id"),
|
|
459
|
+
("SELECT source_id, ref_type, confidence FROM derived_refs WHERE target_id = ?", "incoming", "source_id"),
|
|
460
|
+
]:
|
|
461
|
+
try:
|
|
462
|
+
cursor.execute(tbl_query[0], (symbol_id,))
|
|
463
|
+
for row in cursor.fetchall():
|
|
464
|
+
_add_result(row[tbl_query[2]], row["ref_type"], tbl_query[1], row["confidence"] or "medium")
|
|
465
|
+
except sqlite3.OperationalError:
|
|
466
|
+
pass # intentional: table may not exist in older index versions
|
|
467
|
+
|
|
468
|
+
# 8. Cross-language and name-based reference discovery.
|
|
469
|
+
# Always runs (not just as fallback) to find:
|
|
470
|
+
# a) Import nodes referencing this symbol by name
|
|
471
|
+
# b) Same-name symbols across languages (Java Order <-> TypeScript Order)
|
|
472
|
+
try:
|
|
473
|
+
symbol_name = symbol_id.rsplit('::', 1)[-1] if '::' in symbol_id else symbol_id
|
|
474
|
+
cursor.execute("SELECT file_path FROM symbols WHERE id = ?", (symbol_id,))
|
|
475
|
+
sym_row = cursor.fetchone()
|
|
476
|
+
sym_file = sym_row["file_path"] if sym_row else None
|
|
477
|
+
|
|
478
|
+
if symbol_name and sym_file:
|
|
479
|
+
# Derive project root (up to first 4 path components) for locality filtering
|
|
480
|
+
parts = sym_file.replace("\\", "/").split("/")
|
|
481
|
+
project_prefix = "/".join(parts[:min(4, len(parts) - 1)])
|
|
482
|
+
|
|
483
|
+
# a) Find import nodes in the same project that reference this name
|
|
484
|
+
# Use exact match OR dotted suffix to avoid false positives (e.g., SuperUser matching User)
|
|
485
|
+
cursor.execute("""
|
|
486
|
+
SELECT DISTINCT s.file_path
|
|
487
|
+
FROM symbols s
|
|
488
|
+
WHERE s.type = 'import' AND (s.name = ? OR s.name LIKE ?)
|
|
489
|
+
AND s.file_path LIKE ?
|
|
490
|
+
AND s.file_path != ?
|
|
491
|
+
""", (symbol_name, f'%.{symbol_name}', f'{project_prefix}%', sym_file))
|
|
492
|
+
for row in cursor.fetchall():
|
|
493
|
+
cursor.execute("""
|
|
494
|
+
SELECT id FROM symbols
|
|
495
|
+
WHERE file_path = ?
|
|
496
|
+
AND type IN ('class', 'interface', 'module', 'struct')
|
|
497
|
+
LIMIT 1
|
|
498
|
+
""", (row["file_path"],))
|
|
499
|
+
class_row = cursor.fetchone()
|
|
500
|
+
if class_row:
|
|
501
|
+
_add_result(class_row["id"], "imports", "incoming", "low")
|
|
502
|
+
|
|
503
|
+
# b) Find same-name symbols in different languages within same project
|
|
504
|
+
cursor.execute("""
|
|
505
|
+
SELECT id, type, file_path FROM symbols
|
|
506
|
+
WHERE name = ?
|
|
507
|
+
AND type IN ('class', 'interface', 'struct', 'table')
|
|
508
|
+
AND file_path LIKE ?
|
|
509
|
+
AND id != ?
|
|
510
|
+
""", (symbol_name, f'{project_prefix}%', symbol_id))
|
|
511
|
+
for row in cursor.fetchall():
|
|
512
|
+
_add_result(row["id"], "maps_to", "outgoing", "low")
|
|
513
|
+
|
|
514
|
+
except (sqlite3.OperationalError, TypeError):
|
|
515
|
+
pass # Graceful fallback if tables/columns missing
|
|
516
|
+
|
|
517
|
+
return results
|
|
518
|
+
|
|
519
|
+
def _trace_upstream(
|
|
520
|
+
self,
|
|
521
|
+
cursor,
|
|
522
|
+
symbol_id: str,
|
|
523
|
+
ref_types: Set[str],
|
|
524
|
+
max_depth: int,
|
|
525
|
+
visited: Optional[Set[str]] = None,
|
|
526
|
+
) -> List[AffectedSymbol]:
|
|
527
|
+
"""Trace upstream dependencies (who depends on this symbol).
|
|
528
|
+
|
|
529
|
+
Queries all reference tables to ensure cross-file edges are followed.
|
|
530
|
+
Uses batch symbol lookups to avoid N+1 queries.
|
|
531
|
+
"""
|
|
532
|
+
affected = []
|
|
533
|
+
if visited is None:
|
|
534
|
+
visited = {symbol_id}
|
|
535
|
+
else:
|
|
536
|
+
visited.add(symbol_id)
|
|
537
|
+
queue = [(symbol_id, 0)]
|
|
538
|
+
|
|
539
|
+
while queue:
|
|
540
|
+
current_id, depth = queue.pop(0)
|
|
541
|
+
if depth >= max_depth:
|
|
542
|
+
continue
|
|
543
|
+
|
|
544
|
+
# Collect upstream symbol IDs from all reference tables
|
|
545
|
+
upstream_ids = self._find_upstream_ids(cursor, current_id)
|
|
546
|
+
|
|
547
|
+
# Filter and batch-fetch symbols
|
|
548
|
+
candidates = [
|
|
549
|
+
(rid, rtype) for rid, rtype in upstream_ids
|
|
550
|
+
if rid not in visited and (not ref_types or rtype in ref_types)
|
|
551
|
+
]
|
|
552
|
+
if not candidates:
|
|
553
|
+
continue
|
|
554
|
+
batch_ids = [rid for rid, _ in candidates]
|
|
555
|
+
symbols_by_id = self._get_symbols_batch(cursor, batch_ids)
|
|
556
|
+
|
|
557
|
+
for related_id, ref_type in candidates:
|
|
558
|
+
symbol = symbols_by_id.get(related_id)
|
|
559
|
+
if not symbol:
|
|
560
|
+
continue
|
|
561
|
+
|
|
562
|
+
visited.add(related_id)
|
|
563
|
+
affected.append(AffectedSymbol(
|
|
564
|
+
id=symbol["id"],
|
|
565
|
+
name=symbol["name"],
|
|
566
|
+
type=symbol["type"],
|
|
567
|
+
file_path=symbol["file_path"],
|
|
568
|
+
line_start=symbol.get("line_start", 0),
|
|
569
|
+
line_end=symbol.get("line_end"),
|
|
570
|
+
metadata=json.loads(symbol.get("metadata") or "{}"),
|
|
571
|
+
impact_type="upstream",
|
|
572
|
+
distance=depth + 1,
|
|
573
|
+
))
|
|
574
|
+
queue.append((related_id, depth + 1))
|
|
575
|
+
|
|
576
|
+
return affected
|
|
577
|
+
|
|
578
|
+
def _find_upstream_ids(
|
|
579
|
+
self,
|
|
580
|
+
cursor,
|
|
581
|
+
symbol_id: str,
|
|
582
|
+
) -> List[Tuple[str, str]]:
|
|
583
|
+
"""Find all upstream symbol IDs from all reference tables.
|
|
584
|
+
|
|
585
|
+
Returns list of (related_id, ref_type) tuples.
|
|
586
|
+
"""
|
|
587
|
+
results = []
|
|
588
|
+
seen = set()
|
|
589
|
+
|
|
590
|
+
# Queries that find "who references this symbol" (incoming edges)
|
|
591
|
+
queries = [
|
|
592
|
+
# refs table
|
|
593
|
+
("SELECT source_id, ref_type FROM refs WHERE target_id = ?", "source_id"),
|
|
594
|
+
# symbol_refs table
|
|
595
|
+
("SELECT source_id, ref_type FROM symbol_refs WHERE target_id = ?", "source_id"),
|
|
596
|
+
# symbol_calls: who calls this
|
|
597
|
+
("SELECT symbol_id, 'calls' FROM symbol_calls WHERE target_id = ?", "symbol_id"),
|
|
598
|
+
# symbol_imports: who imports this
|
|
599
|
+
("SELECT symbol_id, 'imports' FROM symbol_imports WHERE target_id = ?", "symbol_id"),
|
|
600
|
+
# symbol_bases: who extends this (children)
|
|
601
|
+
("SELECT symbol_id, 'extends' FROM symbol_bases WHERE base_id = ?", "symbol_id"),
|
|
602
|
+
# symbol_dependents: who depends on this
|
|
603
|
+
("SELECT dependent_id, 'uses' FROM symbol_dependents WHERE symbol_id = ?", "dependent_id"),
|
|
604
|
+
# derived_refs
|
|
605
|
+
("SELECT source_id, ref_type FROM derived_refs WHERE target_id = ?", "source_id"),
|
|
606
|
+
]
|
|
607
|
+
|
|
608
|
+
for query, id_col in queries:
|
|
609
|
+
try:
|
|
610
|
+
cursor.execute(query, (symbol_id,))
|
|
611
|
+
for row in cursor.fetchall():
|
|
612
|
+
rid = row[0]
|
|
613
|
+
rtype = row[1] if len(row) > 1 else "uses"
|
|
614
|
+
if rid not in seen:
|
|
615
|
+
seen.add(rid)
|
|
616
|
+
results.append((rid, rtype))
|
|
617
|
+
except sqlite3.OperationalError:
|
|
618
|
+
pass # Table may not exist
|
|
619
|
+
|
|
620
|
+
return results
|
|
621
|
+
|
|
622
|
+
def _trace_downstream(
|
|
623
|
+
self,
|
|
624
|
+
cursor,
|
|
625
|
+
symbol_id: str,
|
|
626
|
+
ref_types: Set[str],
|
|
627
|
+
max_depth: int,
|
|
628
|
+
visited: Optional[Set[str]] = None,
|
|
629
|
+
) -> List[AffectedSymbol]:
|
|
630
|
+
"""Trace downstream dependencies (what this symbol flows to).
|
|
631
|
+
|
|
632
|
+
Queries all reference tables to ensure cross-file edges are followed.
|
|
633
|
+
Uses batch symbol lookups to avoid N+1 queries.
|
|
634
|
+
"""
|
|
635
|
+
affected = []
|
|
636
|
+
if visited is None:
|
|
637
|
+
visited = {symbol_id}
|
|
638
|
+
else:
|
|
639
|
+
visited.add(symbol_id)
|
|
640
|
+
queue = [(symbol_id, 0)]
|
|
641
|
+
|
|
642
|
+
while queue:
|
|
643
|
+
current_id, depth = queue.pop(0)
|
|
644
|
+
if depth >= max_depth:
|
|
645
|
+
continue
|
|
646
|
+
|
|
647
|
+
# Collect downstream symbol IDs from all reference tables
|
|
648
|
+
downstream_ids = self._find_downstream_ids(cursor, current_id)
|
|
649
|
+
|
|
650
|
+
# Filter and batch-fetch symbols
|
|
651
|
+
candidates = [
|
|
652
|
+
(rid, rtype) for rid, rtype in downstream_ids
|
|
653
|
+
if rid not in visited and (not ref_types or rtype in ref_types)
|
|
654
|
+
]
|
|
655
|
+
if not candidates:
|
|
656
|
+
continue
|
|
657
|
+
batch_ids = [rid for rid, _ in candidates]
|
|
658
|
+
symbols_by_id = self._get_symbols_batch(cursor, batch_ids)
|
|
659
|
+
|
|
660
|
+
for related_id, ref_type in candidates:
|
|
661
|
+
symbol = symbols_by_id.get(related_id)
|
|
662
|
+
if not symbol:
|
|
663
|
+
continue
|
|
664
|
+
|
|
665
|
+
visited.add(related_id)
|
|
666
|
+
affected.append(AffectedSymbol(
|
|
667
|
+
id=symbol["id"],
|
|
668
|
+
name=symbol["name"],
|
|
669
|
+
type=symbol["type"],
|
|
670
|
+
file_path=symbol["file_path"],
|
|
671
|
+
line_start=symbol.get("line_start", 0),
|
|
672
|
+
line_end=symbol.get("line_end"),
|
|
673
|
+
metadata=json.loads(symbol.get("metadata") or "{}"),
|
|
674
|
+
impact_type="downstream",
|
|
675
|
+
distance=depth + 1,
|
|
676
|
+
))
|
|
677
|
+
queue.append((related_id, depth + 1))
|
|
678
|
+
|
|
679
|
+
return affected
|
|
680
|
+
|
|
681
|
+
def _find_downstream_ids(
|
|
682
|
+
self,
|
|
683
|
+
cursor,
|
|
684
|
+
symbol_id: str,
|
|
685
|
+
) -> List[Tuple[str, str]]:
|
|
686
|
+
"""Find all downstream symbol IDs from all reference tables.
|
|
687
|
+
|
|
688
|
+
Returns list of (related_id, ref_type) tuples.
|
|
689
|
+
"""
|
|
690
|
+
results = []
|
|
691
|
+
seen = set()
|
|
692
|
+
|
|
693
|
+
# Queries that find "what this symbol references" (outgoing edges)
|
|
694
|
+
queries = [
|
|
695
|
+
# refs table
|
|
696
|
+
("SELECT target_id, ref_type FROM refs WHERE source_id = ?", "target_id"),
|
|
697
|
+
# symbol_refs table
|
|
698
|
+
("SELECT target_id, ref_type FROM symbol_refs WHERE source_id = ?", "target_id"),
|
|
699
|
+
# symbol_calls: what this calls
|
|
700
|
+
("SELECT target_id, 'calls' FROM symbol_calls WHERE symbol_id = ?", "target_id"),
|
|
701
|
+
# symbol_imports: what this imports
|
|
702
|
+
("SELECT target_id, 'imports' FROM symbol_imports WHERE symbol_id = ?", "target_id"),
|
|
703
|
+
# symbol_bases: what this extends (parents)
|
|
704
|
+
("SELECT base_id, 'extends' FROM symbol_bases WHERE symbol_id = ?", "base_id"),
|
|
705
|
+
# symbol_dependents: what this is a dependency of
|
|
706
|
+
("SELECT symbol_id, 'uses' FROM symbol_dependents WHERE dependent_id = ?", "symbol_id"),
|
|
707
|
+
# derived_refs
|
|
708
|
+
("SELECT target_id, ref_type FROM derived_refs WHERE source_id = ?", "target_id"),
|
|
709
|
+
]
|
|
710
|
+
|
|
711
|
+
for query, id_col in queries:
|
|
712
|
+
try:
|
|
713
|
+
cursor.execute(query, (symbol_id,))
|
|
714
|
+
for row in cursor.fetchall():
|
|
715
|
+
rid = row[0]
|
|
716
|
+
rtype = row[1] if len(row) > 1 else "uses"
|
|
717
|
+
if rid not in seen:
|
|
718
|
+
seen.add(rid)
|
|
719
|
+
results.append((rid, rtype))
|
|
720
|
+
except sqlite3.OperationalError:
|
|
721
|
+
pass # Table may not exist
|
|
722
|
+
|
|
723
|
+
return results
|
|
724
|
+
|
|
725
|
+
def _generate_file_patches(
|
|
726
|
+
self,
|
|
727
|
+
change_spec: ChangeSpec,
|
|
728
|
+
file_path: str,
|
|
729
|
+
symbols: List[AffectedSymbol],
|
|
730
|
+
) -> List[Patch]:
|
|
731
|
+
"""Generate patches for a single file."""
|
|
732
|
+
patches = []
|
|
733
|
+
|
|
734
|
+
# Read file content
|
|
735
|
+
try:
|
|
736
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
737
|
+
file_content = f.read()
|
|
738
|
+
except Exception as e:
|
|
739
|
+
logger.warning(f"Could not read {file_path}: {e}")
|
|
740
|
+
return patches
|
|
741
|
+
|
|
742
|
+
# Get generator for this file type
|
|
743
|
+
generator = GeneratorRegistry.get_generator(file_path, self.db_path)
|
|
744
|
+
if not generator:
|
|
745
|
+
logger.debug(f"No generator for {file_path}")
|
|
746
|
+
return patches
|
|
747
|
+
|
|
748
|
+
# Generate patches for each affected symbol in this file
|
|
749
|
+
for symbol in symbols:
|
|
750
|
+
symbol_dict = {
|
|
751
|
+
"id": symbol.id,
|
|
752
|
+
"name": symbol.name,
|
|
753
|
+
"type": symbol.type,
|
|
754
|
+
"file_path": symbol.file_path,
|
|
755
|
+
"line_start": symbol.line_start,
|
|
756
|
+
"line_end": symbol.line_end,
|
|
757
|
+
"metadata": symbol.metadata,
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
# Adapt the change spec for this symbol
|
|
761
|
+
adapted_spec = self._adapt_change_spec(change_spec, symbol)
|
|
762
|
+
|
|
763
|
+
try:
|
|
764
|
+
symbol_patches = generator.generate(
|
|
765
|
+
adapted_spec, symbol_dict, file_content
|
|
766
|
+
)
|
|
767
|
+
# Update file path in patches
|
|
768
|
+
for patch in symbol_patches:
|
|
769
|
+
patch.file_path = file_path
|
|
770
|
+
patches.extend(symbol_patches)
|
|
771
|
+
except Exception as e:
|
|
772
|
+
logger.warning(f"Generator failed for {symbol.id}: {e}")
|
|
773
|
+
|
|
774
|
+
return patches
|
|
775
|
+
|
|
776
|
+
def _adapt_change_spec(
|
|
777
|
+
self,
|
|
778
|
+
original: ChangeSpec,
|
|
779
|
+
symbol: AffectedSymbol,
|
|
780
|
+
) -> ChangeSpec:
|
|
781
|
+
"""
|
|
782
|
+
Adapt a change spec for a dependent symbol.
|
|
783
|
+
|
|
784
|
+
For example, if adding a field to an entity, the change for
|
|
785
|
+
a JSP binding might become ADD_UI_BINDING instead.
|
|
786
|
+
"""
|
|
787
|
+
# Determine the appropriate change type for this symbol type
|
|
788
|
+
if original.change_type == ChangeType.ADD_FIELD:
|
|
789
|
+
if symbol.type in {"ui_binding", "el_expression", "form_field"}:
|
|
790
|
+
return ChangeSpec(
|
|
791
|
+
change_type=ChangeType.ADD_UI_BINDING,
|
|
792
|
+
target_symbol_id=symbol.id,
|
|
793
|
+
field_spec=original.field_spec,
|
|
794
|
+
metadata={**original.metadata, **symbol.metadata},
|
|
795
|
+
)
|
|
796
|
+
# SQL table/column: translate ADD_FIELD to ADD_COLUMN
|
|
797
|
+
if symbol.type in {"table", "column", "migration"}:
|
|
798
|
+
return ChangeSpec(
|
|
799
|
+
change_type=ChangeType.ADD_COLUMN,
|
|
800
|
+
target_symbol_id=symbol.id,
|
|
801
|
+
field_spec=original.field_spec,
|
|
802
|
+
metadata={**original.metadata, **symbol.metadata},
|
|
803
|
+
)
|
|
804
|
+
# Python ORM model: pass ADD_FIELD directly (python_generator handles it)
|
|
805
|
+
if symbol.type in {"class", "orm_model", "model"}:
|
|
806
|
+
return ChangeSpec(
|
|
807
|
+
change_type=ChangeType.ADD_FIELD,
|
|
808
|
+
target_symbol_id=symbol.id,
|
|
809
|
+
field_spec=original.field_spec,
|
|
810
|
+
metadata={**original.metadata, **symbol.metadata, "language": "python"},
|
|
811
|
+
)
|
|
812
|
+
elif original.change_type == ChangeType.RENAME_FIELD:
|
|
813
|
+
# SQL table/column: translate to RENAME_COLUMN for sql_generator
|
|
814
|
+
if symbol.type in {"table", "column", "migration"}:
|
|
815
|
+
return ChangeSpec(
|
|
816
|
+
change_type=ChangeType.RENAME_COLUMN,
|
|
817
|
+
target_symbol_id=symbol.id,
|
|
818
|
+
old_name=original.old_name,
|
|
819
|
+
new_name=original.new_name,
|
|
820
|
+
metadata={**original.metadata, **symbol.metadata},
|
|
821
|
+
)
|
|
822
|
+
# Rename propagates as rename to all other affected symbols
|
|
823
|
+
return ChangeSpec(
|
|
824
|
+
change_type=ChangeType.RENAME_FIELD,
|
|
825
|
+
target_symbol_id=symbol.id,
|
|
826
|
+
old_name=original.old_name,
|
|
827
|
+
new_name=original.new_name,
|
|
828
|
+
metadata={**original.metadata, **symbol.metadata},
|
|
829
|
+
)
|
|
830
|
+
elif original.change_type == ChangeType.REMOVE_FIELD:
|
|
831
|
+
# SQL table/column: translate for sql_generator DROP COLUMN
|
|
832
|
+
if symbol.type in {"table", "column", "migration"}:
|
|
833
|
+
return ChangeSpec(
|
|
834
|
+
change_type=ChangeType.REMOVE_FIELD,
|
|
835
|
+
target_symbol_id=symbol.id,
|
|
836
|
+
old_name=original.old_name,
|
|
837
|
+
field_spec=original.field_spec,
|
|
838
|
+
metadata={**original.metadata, **symbol.metadata},
|
|
839
|
+
)
|
|
840
|
+
elif original.change_type == ChangeType.MODIFY_FIELD:
|
|
841
|
+
# SQL table/column: pass through as MODIFY_FIELD (sql_generator handles it)
|
|
842
|
+
if symbol.type in {"table", "column", "migration"}:
|
|
843
|
+
return ChangeSpec(
|
|
844
|
+
change_type=ChangeType.MODIFY_FIELD,
|
|
845
|
+
target_symbol_id=symbol.id,
|
|
846
|
+
field_spec=original.field_spec,
|
|
847
|
+
metadata={**original.metadata, **symbol.metadata},
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
# Default: return a copy with updated target
|
|
851
|
+
return ChangeSpec(
|
|
852
|
+
change_type=original.change_type,
|
|
853
|
+
target_symbol_id=symbol.id,
|
|
854
|
+
field_spec=original.field_spec,
|
|
855
|
+
old_name=original.old_name,
|
|
856
|
+
new_name=original.new_name,
|
|
857
|
+
metadata={**original.metadata, **symbol.metadata},
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
def _add_warnings(self, patch_set: PatchSet, plan: PropagationPlan):
|
|
861
|
+
"""Add warnings for potential issues."""
|
|
862
|
+
# Check for symbols without generators
|
|
863
|
+
unsupported_extensions = set()
|
|
864
|
+
for symbol in plan.all_affected:
|
|
865
|
+
if symbol.file_path:
|
|
866
|
+
ext = Path(symbol.file_path).suffix.lower()
|
|
867
|
+
if ext not in GeneratorRegistry.supported_extensions():
|
|
868
|
+
unsupported_extensions.add(ext)
|
|
869
|
+
|
|
870
|
+
if unsupported_extensions:
|
|
871
|
+
patch_set.warnings.append(
|
|
872
|
+
f"No generators for file types: {', '.join(sorted(unsupported_extensions))}"
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
# Check for potential test files
|
|
876
|
+
test_files = [
|
|
877
|
+
s.file_path for s in plan.all_affected
|
|
878
|
+
if s.file_path and ("test" in s.file_path.lower() or "spec" in s.file_path.lower())
|
|
879
|
+
]
|
|
880
|
+
if test_files:
|
|
881
|
+
patch_set.warnings.append(
|
|
882
|
+
f"Test files may need manual updates: {len(test_files)} files"
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
def _apply_patches(self, patch_set: PatchSet):
|
|
886
|
+
"""
|
|
887
|
+
Apply patches to files.
|
|
888
|
+
|
|
889
|
+
Patches are applied in reverse line order (descending by line_start)
|
|
890
|
+
so that earlier patches don't shift the line numbers of later ones.
|
|
891
|
+
For patches at the same line_start, inserts are applied in the order
|
|
892
|
+
they were generated (stable sort preserves creation order).
|
|
893
|
+
"""
|
|
894
|
+
for file_path, patches in patch_set.patches_by_file().items():
|
|
895
|
+
try:
|
|
896
|
+
file_exists = Path(file_path).exists()
|
|
897
|
+
if file_exists:
|
|
898
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
899
|
+
lines = f.read().split("\n")
|
|
900
|
+
else:
|
|
901
|
+
# New file — ensure parent directory exists
|
|
902
|
+
Path(file_path).parent.mkdir(parents=True, exist_ok=True)
|
|
903
|
+
lines = []
|
|
904
|
+
|
|
905
|
+
# patches_by_file() returns patches sorted descending by line_start.
|
|
906
|
+
# Descending order means each patch operates on original line numbers
|
|
907
|
+
# since later patches (lower lines) haven't been shifted yet.
|
|
908
|
+
for patch in patches:
|
|
909
|
+
if patch.is_insert:
|
|
910
|
+
# Insert new content after line_start
|
|
911
|
+
new_lines = patch.new_content.split("\n")
|
|
912
|
+
lines = lines[:patch.line_start] + new_lines + lines[patch.line_start:]
|
|
913
|
+
elif patch.is_delete:
|
|
914
|
+
# Delete lines
|
|
915
|
+
lines = lines[:patch.line_start - 1] + lines[patch.line_end:]
|
|
916
|
+
else:
|
|
917
|
+
# Replace lines
|
|
918
|
+
new_lines = patch.new_content.split("\n")
|
|
919
|
+
lines = lines[:patch.line_start - 1] + new_lines + lines[patch.line_end:]
|
|
920
|
+
|
|
921
|
+
# Write back
|
|
922
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
923
|
+
f.write("\n".join(lines))
|
|
924
|
+
|
|
925
|
+
logger.info(f"Applied {len(patches)} patches to {file_path}")
|
|
926
|
+
|
|
927
|
+
except Exception as e:
|
|
928
|
+
logger.error(f"Failed to apply patches to {file_path}: {e}")
|
|
929
|
+
patch_set.errors.append(f"Apply failed for {file_path}: {str(e)}")
|