wicked-garden 12.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (640) hide show
  1. package/.claude-plugin/archetypes.json +166 -0
  2. package/.claude-plugin/components.json +75 -0
  3. package/.claude-plugin/marketplace.json +15 -0
  4. package/.claude-plugin/plugin.json +174 -0
  5. package/.claude-plugin/specialist.json +48 -0
  6. package/.claude-plugin/stack-registry.json +96 -0
  7. package/AGENTS.md +13 -0
  8. package/CHANGELOG.md +712 -0
  9. package/CONTRIBUTING.md +99 -0
  10. package/ETHOS.md +83 -0
  11. package/LICENSE +21 -0
  12. package/README.md +115 -0
  13. package/WICKED_GARDEN_BUS_EVENTS.md +149 -0
  14. package/agents/agentic/architect.md +449 -0
  15. package/agents/agentic/performance-analyst.md +702 -0
  16. package/agents/agentic/safety-reviewer.md +662 -0
  17. package/agents/crew/implementer.md +163 -0
  18. package/agents/crew/researcher.md +94 -0
  19. package/agents/crew/reviewer.md +184 -0
  20. package/agents/data/data-engineer.md +227 -0
  21. package/agents/engineering/api-documentarian.md +440 -0
  22. package/agents/engineering/migration-engineer.md +301 -0
  23. package/agents/engineering/solution-architect.md +208 -0
  24. package/agents/jam/brainstorm-facilitator.md +297 -0
  25. package/agents/jam/council.md +322 -0
  26. package/agents/jam/quick-facilitator.md +85 -0
  27. package/agents/persona/persona-agent.md +121 -0
  28. package/agents/platform/compliance-officer.md +266 -0
  29. package/agents/platform/privacy-expert.md +496 -0
  30. package/agents/platform/security-engineer.md +287 -0
  31. package/agents/product/a11y-expert.md +323 -0
  32. package/agents/product/requirements-analyst.md +238 -0
  33. package/agents/product/ui-reviewer.md +180 -0
  34. package/agents/product/ux-designer.md +199 -0
  35. package/agents/product/value-strategist.md +269 -0
  36. package/agents/qe/semantic-reviewer.md +78 -0
  37. package/commands/agentic/audit.md +24 -0
  38. package/commands/agentic/design.md +24 -0
  39. package/commands/agentic/frameworks.md +25 -0
  40. package/commands/agentic/review.md +27 -0
  41. package/commands/archetype/build.md +12 -0
  42. package/commands/archetype/decide.md +12 -0
  43. package/commands/archetype/explore.md +12 -0
  44. package/commands/archetype/incident.md +12 -0
  45. package/commands/archetype/migrate.md +12 -0
  46. package/commands/archetype/modernize.md +12 -0
  47. package/commands/archetype/review.md +14 -0
  48. package/commands/archetype/ship.md +12 -0
  49. package/commands/archetype/specify.md +12 -0
  50. package/commands/archetype/triage.md +12 -0
  51. package/commands/compile.md +37 -0
  52. package/commands/data/analyze.md +18 -0
  53. package/commands/data/data.md +21 -0
  54. package/commands/data/ml.md +18 -0
  55. package/commands/data/ontology.md +22 -0
  56. package/commands/data/pipeline.md +17 -0
  57. package/commands/deliberate.md +181 -0
  58. package/commands/engineering/add-field.md +77 -0
  59. package/commands/engineering/apply.md +58 -0
  60. package/commands/engineering/arch.md +26 -0
  61. package/commands/engineering/debug.md +27 -0
  62. package/commands/engineering/docs.md +27 -0
  63. package/commands/engineering/new-generator.md +257 -0
  64. package/commands/engineering/patch-plan.md +59 -0
  65. package/commands/engineering/plan.md +29 -0
  66. package/commands/engineering/remove.md +48 -0
  67. package/commands/engineering/rename.md +44 -0
  68. package/commands/engineering/review.md +32 -0
  69. package/commands/help.md +128 -0
  70. package/commands/install.md +118 -0
  71. package/commands/intent.md +54 -0
  72. package/commands/jam/brainstorm.md +37 -0
  73. package/commands/jam/council.md +49 -0
  74. package/commands/jam/quick.md +21 -0
  75. package/commands/jam/revisit.md +79 -0
  76. package/commands/persona/as.md +136 -0
  77. package/commands/persona/define.md +24 -0
  78. package/commands/persona/list.md +118 -0
  79. package/commands/platform/actions.md +20 -0
  80. package/commands/platform/assert.md +18 -0
  81. package/commands/platform/audit.md +23 -0
  82. package/commands/platform/compliance.md +20 -0
  83. package/commands/platform/gh.md +22 -0
  84. package/commands/platform/health.md +22 -0
  85. package/commands/platform/incident.md +23 -0
  86. package/commands/platform/infra.md +24 -0
  87. package/commands/platform/peer-health.md +103 -0
  88. package/commands/platform/plugin-health.md +58 -0
  89. package/commands/platform/security.md +38 -0
  90. package/commands/platform/toolchain.md +96 -0
  91. package/commands/platform/traces.md +20 -0
  92. package/commands/product/a11y.md +19 -0
  93. package/commands/product/acceptance.md +19 -0
  94. package/commands/product/align.md +20 -0
  95. package/commands/product/analyze.md +23 -0
  96. package/commands/product/elicit.md +19 -0
  97. package/commands/product/listen.md +24 -0
  98. package/commands/product/mockup.md +20 -0
  99. package/commands/product/screenshot.md +19 -0
  100. package/commands/product/strategy.md +40 -0
  101. package/commands/product/synthesize.md +23 -0
  102. package/commands/product/ux-review.md +42 -0
  103. package/commands/product/ux.md +20 -0
  104. package/commands/product/visual-direction.md +34 -0
  105. package/commands/prove.md +47 -0
  106. package/commands/report-issue.md +243 -0
  107. package/commands/reset.md +144 -0
  108. package/commands/search/blast-radius.md +58 -0
  109. package/commands/search/hotspots.md +55 -0
  110. package/commands/search/index.md +38 -0
  111. package/commands/search/lineage.md +47 -0
  112. package/commands/search/service-map.md +79 -0
  113. package/commands/setup.md +363 -0
  114. package/commands/smaht/briefing.md +200 -0
  115. package/commands/smaht/events-import.md +95 -0
  116. package/commands/smaht/state.md +19 -0
  117. package/commands/where-am-i.md +58 -0
  118. package/docs/README.md +32 -0
  119. package/docs/adr/0001-code-relationship-graph-engine.md +116 -0
  120. package/docs/adr/0002-major-cleanup-keep-cut.md +91 -0
  121. package/docs/adr/0003-peers-gate-required-layers-optin.md +41 -0
  122. package/docs/adr/0004-code-graph-moves-to-wicked-brain.md +47 -0
  123. package/docs/brain-chunk-format.md +135 -0
  124. package/docs/compiler.md +95 -0
  125. package/docs/daemon-api.md +70 -0
  126. package/docs/domains.md +197 -0
  127. package/docs/getting-started.md +208 -0
  128. package/docs/plans/2026-06-08-wicked-loom-conduct.md +1337 -0
  129. package/docs/plans/2026-06-08-wicked-loom-contract.md +638 -0
  130. package/docs/plans/2026-06-08-wicked-loom-cutover.md +1300 -0
  131. package/docs/plans/2026-06-08-wicked-loom-expand-compose.md +843 -0
  132. package/docs/plans/2026-06-08-wicked-loom-flow-cutover.md +403 -0
  133. package/docs/plans/2026-06-10-codegraph-to-brain-phase1a-graph-core.md +838 -0
  134. package/docs/plans/2026-06-10-codegraph-to-brain-phase1b-injected-edges.md +44 -0
  135. package/docs/required-peers.md +60 -0
  136. package/docs/specs/2026-06-08-wicked-loom-extraction.md +225 -0
  137. package/docs/specs/2026-06-10-codegraph-to-brain-migration.md +168 -0
  138. package/docs/v11/archetypes.md +231 -0
  139. package/hooks/hooks.json +275 -0
  140. package/hooks/scripts/bootstrap.py +1797 -0
  141. package/hooks/scripts/invoke.py +62 -0
  142. package/hooks/scripts/notification.py +166 -0
  143. package/hooks/scripts/permission_request.py +223 -0
  144. package/hooks/scripts/post_tool.py +1374 -0
  145. package/hooks/scripts/pre_compact.py +272 -0
  146. package/hooks/scripts/pre_tool.py +1278 -0
  147. package/hooks/scripts/prompt_submit.py +1109 -0
  148. package/hooks/scripts/session_end.py +95 -0
  149. package/hooks/scripts/stop.py +624 -0
  150. package/hooks/scripts/subagent_lifecycle.py +464 -0
  151. package/hooks/scripts/subscribers/on_gate_decided.py +84 -0
  152. package/hooks/scripts/task_completed.py +322 -0
  153. package/hooks/scripts/worktree_guard.py +148 -0
  154. package/hooks/subscriptions/on_gate_decided.json +9 -0
  155. package/install.mjs +128 -0
  156. package/package.json +41 -0
  157. package/pyproject.toml +59 -0
  158. package/schemas/README.md +32 -0
  159. package/schemas/assert_contracts.json +37 -0
  160. package/schemas/health_probe.json +46 -0
  161. package/schemas/plugin_status.json +37 -0
  162. package/scripts/_adapters/__init__.py +166 -0
  163. package/scripts/_adapters/jira_adapter.py +18 -0
  164. package/scripts/_adapters/linear_adapter.py +18 -0
  165. package/scripts/_adapters/miro_adapter.py +18 -0
  166. package/scripts/_adapters/notion_adapter.py +18 -0
  167. package/scripts/_agents.py +357 -0
  168. package/scripts/_brain_ingest/__init__.py +2 -0
  169. package/scripts/_brain_ingest/session_fact_extractor.py +341 -0
  170. package/scripts/_brain_port.py +163 -0
  171. package/scripts/_bus.py +1081 -0
  172. package/scripts/_bus_catalog_gen.py +93 -0
  173. package/scripts/_bus_consumers.json +46 -0
  174. package/scripts/_bus_emit.py +43 -0
  175. package/scripts/_capability_registry.py +334 -0
  176. package/scripts/_capability_resolver.py +322 -0
  177. package/scripts/_domain_store.py +559 -0
  178. package/scripts/_event_log_reader.py +154 -0
  179. package/scripts/_event_schema.py +305 -0
  180. package/scripts/_event_store.py +397 -0
  181. package/scripts/_heavy_cadence.py +324 -0
  182. package/scripts/_integration_resolver.py +321 -0
  183. package/scripts/_logger.py +151 -0
  184. package/scripts/_loom.py +482 -0
  185. package/scripts/_paths.py +133 -0
  186. package/scripts/_python.sh +22 -0
  187. package/scripts/_run.py +137 -0
  188. package/scripts/_session.py +502 -0
  189. package/scripts/_validate_registry.py +1042 -0
  190. package/scripts/_wicked_testing_probe.py +277 -0
  191. package/scripts/agentic/analyze_agents.py +391 -0
  192. package/scripts/agentic/detect_framework.py +420 -0
  193. package/scripts/agentic/issue_taxonomy.py +357 -0
  194. package/scripts/agentic/pattern_scorer.py +434 -0
  195. package/scripts/classify/persist.py +169 -0
  196. package/scripts/compiler/compile.py +552 -0
  197. package/scripts/compiler/phase0/VERDICT.md +60 -0
  198. package/scripts/compiler/phase0/detect.py +292 -0
  199. package/scripts/compiler/phase0/emit.py +228 -0
  200. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.bindings.json +30 -0
  201. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.lint-output.txt +17 -0
  202. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/.wg/evidence/failing-tests.txt +4 -0
  203. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/.wg/evidence/honest-tests.txt +2 -0
  204. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/fab-fail.build.md +8 -0
  205. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/fab-missing.build.md +8 -0
  206. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/honest.build.md +8 -0
  207. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/wg_check_claims.py +174 -0
  208. package/scripts/compiler/phase0/evidence/command_iq.bindings.json +45 -0
  209. package/scripts/compiler/phase0/evidence/command_iq.lint-output.txt +17 -0
  210. package/scripts/compiler/phase0/evidence/command_iq.stage/.projects/evidence-domain/phase-1-clarify/evidence/failing-tests.txt +4 -0
  211. package/scripts/compiler/phase0/evidence/command_iq.stage/.projects/evidence-domain/phase-1-clarify/evidence/honest-tests.txt +3 -0
  212. package/scripts/compiler/phase0/evidence/command_iq.stage/fab-fail.build.md +8 -0
  213. package/scripts/compiler/phase0/evidence/command_iq.stage/fab-missing.build.md +8 -0
  214. package/scripts/compiler/phase0/evidence/command_iq.stage/honest.build.md +8 -0
  215. package/scripts/compiler/phase0/evidence/command_iq.stage/wg_check_claims.py +174 -0
  216. package/scripts/compiler/phase0/evidence/memos.bindings.json +28 -0
  217. package/scripts/compiler/phase0/evidence/memos.lint-output.txt +17 -0
  218. package/scripts/compiler/phase0/evidence/memos.stage/.wg/evidence/failing-tests.txt +4 -0
  219. package/scripts/compiler/phase0/evidence/memos.stage/.wg/evidence/honest-tests.txt +2 -0
  220. package/scripts/compiler/phase0/evidence/memos.stage/fab-fail.build.md +8 -0
  221. package/scripts/compiler/phase0/evidence/memos.stage/fab-missing.build.md +8 -0
  222. package/scripts/compiler/phase0/evidence/memos.stage/honest.build.md +8 -0
  223. package/scripts/compiler/phase0/evidence/memos.stage/wg_check_claims.py +174 -0
  224. package/scripts/compiler/phase0/evidence/verdict.json +104 -0
  225. package/scripts/compiler/phase0/evidence/wicked-bus.bindings.json +29 -0
  226. package/scripts/compiler/phase0/evidence/wicked-bus.lint-output.txt +17 -0
  227. package/scripts/compiler/phase0/evidence/wicked-bus.stage/coverage/failing-tests.txt +4 -0
  228. package/scripts/compiler/phase0/evidence/wicked-bus.stage/coverage/honest-tests.txt +3 -0
  229. package/scripts/compiler/phase0/evidence/wicked-bus.stage/fab-fail.build.md +8 -0
  230. package/scripts/compiler/phase0/evidence/wicked-bus.stage/fab-missing.build.md +8 -0
  231. package/scripts/compiler/phase0/evidence/wicked-bus.stage/honest.build.md +8 -0
  232. package/scripts/compiler/phase0/evidence/wicked-bus.stage/wg_check_claims.py +174 -0
  233. package/scripts/compiler/phase0/evidence/wire-vault-transcript.txt +60 -0
  234. package/scripts/compiler/phase0/run_probe.py +234 -0
  235. package/scripts/compiler/phase0/wire_vault.py +310 -0
  236. package/scripts/crew/__init__.py +0 -0
  237. package/scripts/crew/_task_audit_writer.py +243 -0
  238. package/scripts/crew/_task_reader.py +389 -0
  239. package/scripts/crew/archetypes_v11.py +348 -0
  240. package/scripts/crew/detectors/__init__.py +12 -0
  241. package/scripts/crew/flow_compiler.py +121 -0
  242. package/scripts/crew/phase_manager.py +703 -0
  243. package/scripts/crew/scope_delta.py +264 -0
  244. package/scripts/crew/specialist_resolver.py +218 -0
  245. package/scripts/crew/stack_registry.py +189 -0
  246. package/scripts/data/data_profiler.py +271 -0
  247. package/scripts/data/ontology_recommender.py +231 -0
  248. package/scripts/data/schema_validator.py +267 -0
  249. package/scripts/delivery/drift.py +739 -0
  250. package/scripts/delivery/telemetry.py +614 -0
  251. package/scripts/engineering/patch/codegraph_db.py +125 -0
  252. package/scripts/engineering/patch/generators/__init__.py +56 -0
  253. package/scripts/engineering/patch/generators/base.py +373 -0
  254. package/scripts/engineering/patch/generators/csharp_generator.py +494 -0
  255. package/scripts/engineering/patch/generators/go_generator.py +430 -0
  256. package/scripts/engineering/patch/generators/java_generator.py +928 -0
  257. package/scripts/engineering/patch/generators/jsp_generator.py +454 -0
  258. package/scripts/engineering/patch/generators/kotlin_generator.py +561 -0
  259. package/scripts/engineering/patch/generators/perl_generator.py +510 -0
  260. package/scripts/engineering/patch/generators/php_generator.py +644 -0
  261. package/scripts/engineering/patch/generators/propagation_engine.py +929 -0
  262. package/scripts/engineering/patch/generators/python_generator.py +487 -0
  263. package/scripts/engineering/patch/generators/ruby_generator.py +463 -0
  264. package/scripts/engineering/patch/generators/rust_generator.py +400 -0
  265. package/scripts/engineering/patch/generators/sql_generator.py +594 -0
  266. package/scripts/engineering/patch/generators/typescript_generator.py +439 -0
  267. package/scripts/engineering/patch/patch.py +800 -0
  268. package/scripts/engineering/patch/safety.py +429 -0
  269. package/scripts/jam/__init__.py +0 -0
  270. package/scripts/jam/_bus_consumers.py +271 -0
  271. package/scripts/jam/agentic_cli_registry.py +703 -0
  272. package/scripts/jam/consensus.py +737 -0
  273. package/scripts/jam/detect_clis.py +409 -0
  274. package/scripts/jam/jam.py +283 -0
  275. package/scripts/jam/save_transcript.py +171 -0
  276. package/scripts/loom/__init__.py +22 -0
  277. package/scripts/loom/cli.py +170 -0
  278. package/scripts/loom/compose.py +180 -0
  279. package/scripts/loom/gate.py +120 -0
  280. package/scripts/loom/manifest.py +140 -0
  281. package/scripts/loom/resolve.py +59 -0
  282. package/scripts/persona/__init__.py +0 -0
  283. package/scripts/persona/registry.py +688 -0
  284. package/scripts/platform/guard_pipeline.py +1214 -0
  285. package/scripts/platform/guard_profiles.py +216 -0
  286. package/scripts/platform/observability/assert_contracts.py +434 -0
  287. package/scripts/platform/observability/health_probe.py +915 -0
  288. package/scripts/platform/observability/ops_log_viewer.py +209 -0
  289. package/scripts/platform/prereq_doctor.py +567 -0
  290. package/scripts/qe/conditions_manifest.py +260 -0
  291. package/scripts/qe/content_sanitizer.py +160 -0
  292. package/scripts/qe/coverage_tracker.py +535 -0
  293. package/scripts/qe/discover_scenarios.py +243 -0
  294. package/scripts/qe/evidence_tracker.py +232 -0
  295. package/scripts/qe/prove.py +196 -0
  296. package/scripts/qe/registry_lookup.py +80 -0
  297. package/scripts/qe/registry_store.py +176 -0
  298. package/scripts/qe/semantic_review.py +1005 -0
  299. package/scripts/qe/vault_gate.py +382 -0
  300. package/scripts/qe/verdict_audit.py +175 -0
  301. package/scripts/qe/verdict_schema.py +335 -0
  302. package/scripts/reset.py +220 -0
  303. package/scripts/resolve_path.py +22 -0
  304. package/scripts/sentinel/invariants.py +402 -0
  305. package/scripts/sentinel/pre_push.py +73 -0
  306. package/scripts/setup/__init__.py +1 -0
  307. package/scripts/setup/detect_state.py +114 -0
  308. package/scripts/setup/migrate_legacy.py +67 -0
  309. package/scripts/setup/migrate_v6_projects.py +232 -0
  310. package/scripts/setup/onboarding.py +114 -0
  311. package/scripts/smaht/_bus_consumers.py +98 -0
  312. package/scripts/smaht/adapters/__init__.py +83 -0
  313. package/scripts/smaht/adapters/brain_adapter.py +254 -0
  314. package/scripts/smaht/adapters/context7_adapter.py +438 -0
  315. package/scripts/smaht/adapters/delegation_adapter.py +104 -0
  316. package/scripts/smaht/adapters/domain_adapter.py +206 -0
  317. package/scripts/smaht/adapters/events_adapter.py +231 -0
  318. package/scripts/smaht/adapters/tools_adapter.py +49 -0
  319. package/scripts/smaht/cheatsheet_store.py +236 -0
  320. package/scripts/smaht/context_package.py +386 -0
  321. package/scripts/smaht/knowledge_graph.py +395 -0
  322. package/scripts/smaht/propose_skills.py +856 -0
  323. package/scripts/smaht/test_cheatsheet_store.py +428 -0
  324. package/scripts/statusline.py +151 -0
  325. package/scripts/where_am_i.py +236 -0
  326. package/skills/agentic/agentic-patterns/SKILL.md +91 -0
  327. package/skills/agentic/agentic-patterns/refs/anti-patterns-design.md +283 -0
  328. package/skills/agentic/agentic-patterns/refs/anti-patterns-operational.md +216 -0
  329. package/skills/agentic/agentic-patterns/refs/pattern-catalog.md +329 -0
  330. package/skills/agentic/context-engineering/SKILL.md +142 -0
  331. package/skills/agentic/context-engineering/refs/caching-and-optimization.md +251 -0
  332. package/skills/agentic/context-engineering/refs/compression-techniques.md +171 -0
  333. package/skills/agentic/context-engineering/refs/cost-calculation-budget.md +250 -0
  334. package/skills/agentic/context-engineering/refs/cost-optimization-reporting.md +300 -0
  335. package/skills/agentic/context-engineering/refs/selective-loading.md +179 -0
  336. package/skills/agentic/frameworks/SKILL.md +191 -0
  337. package/skills/agentic/frameworks/refs/framework-profiles-1.md +209 -0
  338. package/skills/agentic/frameworks/refs/framework-profiles-2.md +214 -0
  339. package/skills/agentic/frameworks/refs/migration-patterns-paths.md +267 -0
  340. package/skills/agentic/frameworks/refs/migration-patterns-testing.md +208 -0
  341. package/skills/agentic/refs/audit.md +134 -0
  342. package/skills/agentic/refs/design.md +137 -0
  343. package/skills/agentic/refs/frameworks.md +102 -0
  344. package/skills/agentic/refs/review.md +160 -0
  345. package/skills/agentic/review-methodology/SKILL.md +186 -0
  346. package/skills/agentic/review-methodology/refs/deliverable-templates.md +246 -0
  347. package/skills/agentic/review-methodology/refs/issue-taxonomy-quality-testing.md +277 -0
  348. package/skills/agentic/review-methodology/refs/issue-taxonomy-reliability-safety.md +253 -0
  349. package/skills/agentic/trust-and-safety/SKILL.md +177 -0
  350. package/skills/agentic/trust-and-safety/refs/guardrails-actions.md +246 -0
  351. package/skills/agentic/trust-and-safety/refs/guardrails-input-output.md +238 -0
  352. package/skills/agentic/trust-and-safety/refs/guardrails-resources.md +209 -0
  353. package/skills/agentic/trust-and-safety/refs/safety-checklist-advanced.md +209 -0
  354. package/skills/agentic/trust-and-safety/refs/safety-checklist-core.md +215 -0
  355. package/skills/archetype/SKILL.md +104 -0
  356. package/skills/archetype/refs/build.md +142 -0
  357. package/skills/archetype/refs/decide.md +117 -0
  358. package/skills/archetype/refs/explore.md +66 -0
  359. package/skills/archetype/refs/incident.md +149 -0
  360. package/skills/archetype/refs/migrate.md +170 -0
  361. package/skills/archetype/refs/modernize.md +187 -0
  362. package/skills/archetype/refs/review.md +145 -0
  363. package/skills/archetype/refs/ship.md +117 -0
  364. package/skills/archetype/refs/specify.md +108 -0
  365. package/skills/archetype/refs/triage.md +62 -0
  366. package/skills/classify/SKILL.md +157 -0
  367. package/skills/data/analysis/SKILL.md +125 -0
  368. package/skills/data/analysis/refs/templates.md +170 -0
  369. package/skills/data/data/SKILL.md +118 -0
  370. package/skills/data/data/refs/examples.md +235 -0
  371. package/skills/data/ml/SKILL.md +125 -0
  372. package/skills/data/ml/refs/templates.md +172 -0
  373. package/skills/data/pipeline/SKILL.md +136 -0
  374. package/skills/data/pipeline/refs/templates.md +175 -0
  375. package/skills/data/refs/analyze.md +123 -0
  376. package/skills/data/refs/data.md +95 -0
  377. package/skills/data/refs/ml.md +118 -0
  378. package/skills/data/refs/pipeline.md +92 -0
  379. package/skills/deliberate/SKILL.md +125 -0
  380. package/skills/deliberate/refs/architecture-lens.md +37 -0
  381. package/skills/deliberate/refs/assumptions-lens.md +43 -0
  382. package/skills/deliberate/refs/code-lens.md +38 -0
  383. package/skills/deliberate/refs/content-lens.md +37 -0
  384. package/skills/deliberate/refs/data-lens.md +43 -0
  385. package/skills/deliberate/refs/design-lens.md +37 -0
  386. package/skills/deliberate/refs/opportunity-patterns.md +107 -0
  387. package/skills/deliberate/refs/ops-lens.md +43 -0
  388. package/skills/deliberate/refs/product-lens.md +46 -0
  389. package/skills/deliberate/refs/rethink-framework.md +77 -0
  390. package/skills/deliberate/refs/security-lens.md +47 -0
  391. package/skills/deliberate/refs/testing-lens.md +45 -0
  392. package/skills/deliberate/refs/timing-lens.md +42 -0
  393. package/skills/engineering/architecture/SKILL.md +177 -0
  394. package/skills/engineering/architecture/refs/adr-template.md +268 -0
  395. package/skills/engineering/architecture/refs/architecture-template-deployment.md +176 -0
  396. package/skills/engineering/architecture/refs/architecture-template-design.md +300 -0
  397. package/skills/engineering/architecture/refs/examples-ecommerce-analytics.md +299 -0
  398. package/skills/engineering/architecture/refs/examples-saas-trading.md +291 -0
  399. package/skills/engineering/architecture/refs/patterns-advanced.md +238 -0
  400. package/skills/engineering/architecture/refs/patterns-structural.md +303 -0
  401. package/skills/engineering/backend/SKILL.md +102 -0
  402. package/skills/engineering/backend/refs/checklists.md +146 -0
  403. package/skills/engineering/backend/refs/patterns.md +181 -0
  404. package/skills/engineering/debugging/SKILL.md +99 -0
  405. package/skills/engineering/debugging/refs/patterns.md +248 -0
  406. package/skills/engineering/debugging/refs/process.md +203 -0
  407. package/skills/engineering/docs/SKILL.md +97 -0
  408. package/skills/engineering/engineering/SKILL.md +88 -0
  409. package/skills/engineering/engineering/refs/checklists.md +91 -0
  410. package/skills/engineering/engineering/refs/templates.md +71 -0
  411. package/skills/engineering/frontend/SKILL.md +92 -0
  412. package/skills/engineering/frontend/refs/checklists.md +96 -0
  413. package/skills/engineering/frontend/refs/patterns.md +167 -0
  414. package/skills/engineering/integration/SKILL.md +183 -0
  415. package/skills/engineering/integration/refs/error-handling-best-practices.md +139 -0
  416. package/skills/engineering/integration/refs/error-handling-error-classes-typescript.md +212 -0
  417. package/skills/engineering/integration/refs/error-handling-error-response-format.md +301 -0
  418. package/skills/engineering/integration/refs/error-handling-retry-strategies.md +190 -0
  419. package/skills/engineering/integration/refs/event-schemas-best-practices.md +225 -0
  420. package/skills/engineering/integration/refs/event-schemas-event-naming-conventions.md +303 -0
  421. package/skills/engineering/integration/refs/event-schemas-event-structure.md +288 -0
  422. package/skills/engineering/integration/refs/graphql-best-practices.md +138 -0
  423. package/skills/engineering/integration/refs/graphql-error-handling.md +198 -0
  424. package/skills/engineering/integration/refs/graphql-graphql-basics.md +197 -0
  425. package/skills/engineering/integration/refs/graphql-query-examples.md +226 -0
  426. package/skills/engineering/integration/refs/graphql-schema-design-patterns.md +280 -0
  427. package/skills/engineering/integration/refs/grpc-grpc-overview.md +246 -0
  428. package/skills/engineering/integration/refs/grpc-implementation-examples.md +269 -0
  429. package/skills/engineering/integration/refs/grpc-metadata-headers.md +304 -0
  430. package/skills/engineering/integration/refs/grpc-testing.md +49 -0
  431. package/skills/engineering/integration/refs/openapi-template.yaml +682 -0
  432. package/skills/engineering/integration/refs/resilience-best-practices.md +109 -0
  433. package/skills/engineering/integration/refs/resilience-circuit-breaker-pattern.md +302 -0
  434. package/skills/engineering/integration/refs/resilience-rate-limiting.md +245 -0
  435. package/skills/engineering/integration/refs/resilience-timeout-pattern.md +212 -0
  436. package/skills/engineering/integration/refs/rest-api-fundamentals.md +299 -0
  437. package/skills/engineering/integration/refs/rest-api-responses-security.md +302 -0
  438. package/skills/engineering/integration/refs/versioning-breaking-changes.md +237 -0
  439. package/skills/engineering/integration/refs/versioning-migration-strategies.md +257 -0
  440. package/skills/engineering/integration/refs/versioning-versioning-strategies.md +296 -0
  441. package/skills/engineering/large-scale-migration/SKILL.md +65 -0
  442. package/skills/engineering/patch/SKILL.md +184 -0
  443. package/skills/engineering/refs/arch.md +126 -0
  444. package/skills/engineering/refs/debug.md +142 -0
  445. package/skills/engineering/refs/docs.md +190 -0
  446. package/skills/engineering/refs/plan.md +109 -0
  447. package/skills/engineering/refs/review.md +138 -0
  448. package/skills/engineering/system-design/SKILL.md +154 -0
  449. package/skills/engineering/system-design/refs/anti-patterns-big-ball-of-mud.md +254 -0
  450. package/skills/engineering/system-design/refs/anti-patterns-circular-dependencies.md +241 -0
  451. package/skills/engineering/system-design/refs/anti-patterns-feature-envy.md +168 -0
  452. package/skills/engineering/system-design/refs/anti-patterns-god-object.md +247 -0
  453. package/skills/engineering/system-design/refs/component-template-lifecycle.md +210 -0
  454. package/skills/engineering/system-design/refs/component-template-maintenance.md +219 -0
  455. package/skills/engineering/system-design/refs/component-template-structure.md +276 -0
  456. package/skills/engineering/system-design/refs/dependencies-best-practices.md +260 -0
  457. package/skills/engineering/system-design/refs/dependencies-dependency-principles.md +295 -0
  458. package/skills/engineering/system-design/refs/dependencies-dependency-tracking.md +261 -0
  459. package/skills/engineering/system-design/refs/interface-template-lifecycle.md +211 -0
  460. package/skills/engineering/system-design/refs/interface-template-maintenance.md +212 -0
  461. package/skills/engineering/system-design/refs/interface-template-structure.md +269 -0
  462. package/skills/engineering/system-design/refs/patterns-dependency-injection.md +151 -0
  463. package/skills/engineering/system-design/refs/patterns-facade-pattern.md +257 -0
  464. package/skills/engineering/system-design/refs/patterns-layered-pattern.md +204 -0
  465. package/skills/engineering/system-design/refs/patterns-plugin-architecture.md +229 -0
  466. package/skills/engineering/unit-test-quality/SKILL.md +194 -0
  467. package/skills/ground/SKILL.md +130 -0
  468. package/skills/integration-discovery/SKILL.md +168 -0
  469. package/skills/integration-discovery/refs/capabilities.md +282 -0
  470. package/skills/integration-discovery/refs/cli-detection.md +295 -0
  471. package/skills/integration-discovery/refs/discovery-sources.md +233 -0
  472. package/skills/integration-discovery/refs/task-patterns.md +206 -0
  473. package/skills/jam/SKILL.md +45 -0
  474. package/skills/jam/refs/facilitation-patterns.md +125 -0
  475. package/skills/jam/refs/quick.md +64 -0
  476. package/skills/jam/refs/synthesis-patterns.md +114 -0
  477. package/skills/multi-model/SKILL.md +168 -0
  478. package/skills/multi-model/refs/aichat.md +119 -0
  479. package/skills/multi-model/refs/aider.md +100 -0
  480. package/skills/multi-model/refs/auditability.md +238 -0
  481. package/skills/multi-model/refs/codex.md +169 -0
  482. package/skills/multi-model/refs/context.md +306 -0
  483. package/skills/multi-model/refs/copilot.md +161 -0
  484. package/skills/multi-model/refs/examples.md +224 -0
  485. package/skills/multi-model/refs/gemini.md +185 -0
  486. package/skills/multi-model/refs/goose.md +112 -0
  487. package/skills/multi-model/refs/llm.md +124 -0
  488. package/skills/multi-model/refs/opencode.md +196 -0
  489. package/skills/multi-model/refs/orchestration.md +188 -0
  490. package/skills/multi-model/refs/pi.md +208 -0
  491. package/skills/persona/SKILL.md +144 -0
  492. package/skills/persona/refs/define.md +133 -0
  493. package/skills/platform/actions/refs/actions.md +114 -0
  494. package/skills/platform/audit/SKILL.md +174 -0
  495. package/skills/platform/audit/refs/audit.md +145 -0
  496. package/skills/platform/audit/refs/checklists-evidence-operations.md +144 -0
  497. package/skills/platform/audit/refs/checklists-gdpr-pci-evidence.md +213 -0
  498. package/skills/platform/audit/refs/checklists-soc2-hipaa.md +120 -0
  499. package/skills/platform/audit/refs/frameworks-gdpr-pci.md +225 -0
  500. package/skills/platform/audit/refs/frameworks-soc2-hipaa.md +228 -0
  501. package/skills/platform/compliance/SKILL.md +169 -0
  502. package/skills/platform/compliance/refs/checklists.md +303 -0
  503. package/skills/platform/compliance/refs/compliance.md +117 -0
  504. package/skills/platform/compliance/refs/frameworks.md +282 -0
  505. package/skills/platform/errors/SKILL.md +183 -0
  506. package/skills/platform/errors/refs/patterns.md +271 -0
  507. package/skills/platform/errors/refs/severity.md +264 -0
  508. package/skills/platform/gate-benchmark-rebaseline/SKILL.md +108 -0
  509. package/skills/platform/gh/refs/gh.md +105 -0
  510. package/skills/platform/gh-cli/SKILL.md +120 -0
  511. package/skills/platform/gh-cli/refs/automation.md +63 -0
  512. package/skills/platform/gh-cli/refs/gh-ops.md +103 -0
  513. package/skills/platform/gh-cli/refs/patterns.md +132 -0
  514. package/skills/platform/gh-cli/scripts/gh_ops.py +454 -0
  515. package/skills/platform/github-actions/SKILL.md +159 -0
  516. package/skills/platform/github-actions/refs/security.md +150 -0
  517. package/skills/platform/github-actions/refs/templates.md +230 -0
  518. package/skills/platform/github-actions/refs/troubleshooting.md +174 -0
  519. package/skills/platform/github-actions/scripts/workflow_gen.py +215 -0
  520. package/skills/platform/gitlab-ci/SKILL.md +140 -0
  521. package/skills/platform/gitlab-ci/refs/templates.md +223 -0
  522. package/skills/platform/gitlab-ci/refs/troubleshooting.md +213 -0
  523. package/skills/platform/glab-cli/SKILL.md +133 -0
  524. package/skills/platform/glab-cli/refs/ci-debugging.md +95 -0
  525. package/skills/platform/glab-cli/refs/glab-ops.md +78 -0
  526. package/skills/platform/glab-cli/refs/patterns.md +131 -0
  527. package/skills/platform/glab-cli/scripts/glab_ops.py +313 -0
  528. package/skills/platform/health/SKILL.md +177 -0
  529. package/skills/platform/health/refs/health.md +72 -0
  530. package/skills/platform/health/refs/sources.md +302 -0
  531. package/skills/platform/incident/refs/incident.md +136 -0
  532. package/skills/platform/infra/refs/infra.md +104 -0
  533. package/skills/platform/observability/SKILL.md +128 -0
  534. package/skills/platform/observability/refs/toolchain-discovery.md +243 -0
  535. package/skills/platform/prereq-doctor/SKILL.md +57 -0
  536. package/skills/platform/prereq-doctor/refs/tool-registry.md +58 -0
  537. package/skills/platform/traces/refs/traces.md +101 -0
  538. package/skills/product/acceptance-criteria/SKILL.md +193 -0
  539. package/skills/product/acceptance-criteria/refs/ac-anti-patterns.md +291 -0
  540. package/skills/product/acceptance-criteria/refs/ac-examples.md +277 -0
  541. package/skills/product/accessibility/SKILL.md +197 -0
  542. package/skills/product/accessibility/refs/aria-patterns-basics.md +250 -0
  543. package/skills/product/accessibility/refs/aria-patterns-dynamic.md +187 -0
  544. package/skills/product/accessibility/refs/aria-patterns-interactive.md +246 -0
  545. package/skills/product/accessibility/refs/audit-report-findings.md +248 -0
  546. package/skills/product/accessibility/refs/audit-report-results.md +276 -0
  547. package/skills/product/accessibility/refs/common-violations-html-css.md +270 -0
  548. package/skills/product/accessibility/refs/common-violations-interactive.md +303 -0
  549. package/skills/product/accessibility/refs/keyboard-testing-basics.md +222 -0
  550. package/skills/product/accessibility/refs/keyboard-testing-patterns.md +173 -0
  551. package/skills/product/accessibility/refs/screen-reader-testing-advanced.md +294 -0
  552. package/skills/product/accessibility/refs/screen-reader-testing-commands.md +255 -0
  553. package/skills/product/accessibility/refs/wcag-checklist.md +262 -0
  554. package/skills/product/analyze/SKILL.md +164 -0
  555. package/skills/product/analyze/refs/algorithms.md +238 -0
  556. package/skills/product/analyze/refs/sentiment-patterns.md +180 -0
  557. package/skills/product/imagery/SKILL.md +97 -0
  558. package/skills/product/imagery/alter/SKILL.md +105 -0
  559. package/skills/product/imagery/alter/refs/editing-reference.md +146 -0
  560. package/skills/product/imagery/create/SKILL.md +109 -0
  561. package/skills/product/imagery/create/refs/prompt-engineering.md +112 -0
  562. package/skills/product/imagery/create/refs/provider-reference.md +219 -0
  563. package/skills/product/imagery/review/SKILL.md +101 -0
  564. package/skills/product/imagery/review/refs/analysis-lenses.md +136 -0
  565. package/skills/product/imagery/review/refs/quality-gates.md +153 -0
  566. package/skills/product/imagery/scripts/analyze.sh +23 -0
  567. package/skills/product/imagery/scripts/edit.sh +14 -0
  568. package/skills/product/imagery/scripts/generate.sh +13 -0
  569. package/skills/product/imagery/scripts/provider.py +787 -0
  570. package/skills/product/listen/SKILL.md +175 -0
  571. package/skills/product/listen/refs/channels.md +220 -0
  572. package/skills/product/mockup/SKILL.md +106 -0
  573. package/skills/product/mockup/refs/templates.md +95 -0
  574. package/skills/product/refs/a11y.md +109 -0
  575. package/skills/product/refs/acceptance.md +79 -0
  576. package/skills/product/refs/align.md +67 -0
  577. package/skills/product/refs/analyze.md +77 -0
  578. package/skills/product/refs/elicit.md +67 -0
  579. package/skills/product/refs/listen.md +58 -0
  580. package/skills/product/refs/mockup.md +77 -0
  581. package/skills/product/refs/screenshot.md +59 -0
  582. package/skills/product/refs/strategy.md +77 -0
  583. package/skills/product/refs/synthesize.md +54 -0
  584. package/skills/product/refs/ux-review.md +68 -0
  585. package/skills/product/refs/ux.md +101 -0
  586. package/skills/product/requirements-analysis/SKILL.md +136 -0
  587. package/skills/product/requirements-analysis/refs/requirements-example-auth.md +264 -0
  588. package/skills/product/requirements-analysis/refs/requirements-example-export-integration.md +88 -0
  589. package/skills/product/requirements-analysis/refs/requirements-output-format-template.md +247 -0
  590. package/skills/product/requirements-analysis/refs/user-story-guide.md +296 -0
  591. package/skills/product/requirements-graph/SKILL.md +155 -0
  592. package/skills/product/requirements-graph/refs/examples.md +223 -0
  593. package/skills/product/requirements-graph/refs/schema.md +162 -0
  594. package/skills/product/requirements-migrate/SKILL.md +90 -0
  595. package/skills/product/requirements-navigate/SKILL.md +141 -0
  596. package/skills/product/requirements-navigate/refs/meta-templates.md +141 -0
  597. package/skills/product/screenshot/SKILL.md +147 -0
  598. package/skills/product/strategy/SKILL.md +198 -0
  599. package/skills/product/strategy/refs/frameworks.md +295 -0
  600. package/skills/product/strategy/refs/templates-competitive-market.md +299 -0
  601. package/skills/product/strategy/refs/templates-roi-value.md +207 -0
  602. package/skills/product/synthesize/SKILL.md +116 -0
  603. package/skills/product/synthesize/refs/journey-mapping.md +212 -0
  604. package/skills/product/synthesize/refs/prioritization.md +193 -0
  605. package/skills/product/ux-review/SKILL.md +164 -0
  606. package/skills/product/visual-direction/SKILL.md +137 -0
  607. package/skills/product/visual-review/SKILL.md +131 -0
  608. package/skills/runtime-exec/SKILL.md +138 -0
  609. package/skills/search/codebase-narrator/SKILL.md +131 -0
  610. package/skills/search/codebase-narrator/refs/output-template.md +100 -0
  611. package/skills/smaht/SKILL.md +72 -0
  612. package/skills/smaht/discovery/SKILL.md +65 -0
  613. package/skills/smaht/intent/SKILL.md +89 -0
  614. package/skills/smaht/propose-skills/SKILL.md +142 -0
  615. package/skills/smaht/refs/state.md +76 -0
  616. package/skills/swarm/SKILL.md +132 -0
  617. package/skills/swarm/refs/fan-out.md +104 -0
  618. package/skills/swarm/refs/independent-verification.md +86 -0
  619. package/skills/swarm/refs/receipts-and-evidence.md +69 -0
  620. package/skills/swarm/refs/recon-synthesis.md +87 -0
  621. package/skills/swarm/refs/ship-discipline.md +90 -0
  622. package/skills/wickedizer/SKILL.md +147 -0
  623. package/skills/wickedizer/refs/artifact-types.md +238 -0
  624. package/skills/wickedizer/refs/complexity.md +278 -0
  625. package/skills/wickedizer/refs/de-ai-pass.md +140 -0
  626. package/skills/wickedizer/refs/failure-modes.md +249 -0
  627. package/skills/wickedizer/refs/patterns.md +282 -0
  628. package/skills/wickedizer/refs/review.md +221 -0
  629. package/skills/wickedizer/refs/structure.md +282 -0
  630. package/skills/wickedizer/refs/voice-profile.md +125 -0
  631. package/skills/workflow/SKILL.md +132 -0
  632. package/skills/workflow/refs/archetype-adjustments.md +205 -0
  633. package/skills/workflow/refs/evidence.md +58 -0
  634. package/skills/workflow/refs/integration.md +130 -0
  635. package/skills/workflow/refs/risk-dimension-signals.md +260 -0
  636. package/skills/workflow/refs/scoring-rubric.md +49 -0
  637. package/skills/workflow/refs/signal-keywords.md +176 -0
  638. package/skills/workflow/refs/specialist-routing-rules.md +159 -0
  639. package/skills/worktrees/SKILL.md +87 -0
  640. package/skills/worktrees/refs/recipes.md +96 -0
@@ -0,0 +1,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)}")