claude-code-workflow 6.2.5 → 6.2.6

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 (964) hide show
  1. package/.claude/CLAUDE.md +10 -0
  2. package/.claude/agents/action-planning-agent.md +857 -0
  3. package/.claude/agents/cli-execution-agent.md +267 -0
  4. package/.claude/agents/cli-explore-agent.md +182 -0
  5. package/.claude/agents/cli-lite-planning-agent.md +446 -0
  6. package/.claude/agents/cli-planning-agent.md +558 -0
  7. package/.claude/agents/code-developer.md +311 -0
  8. package/.claude/agents/conceptual-planning-agent.md +308 -0
  9. package/.claude/agents/context-search-agent.md +581 -0
  10. package/.claude/agents/doc-generator.md +330 -0
  11. package/.claude/agents/memory-bridge.md +94 -0
  12. package/.claude/agents/test-context-search-agent.md +400 -0
  13. package/.claude/agents/test-fix-agent.md +344 -0
  14. package/.claude/agents/ui-design-agent.md +593 -0
  15. package/.claude/agents/universal-executor.md +131 -0
  16. package/.claude/commands/clean.md +516 -0
  17. package/.claude/commands/cli/cli-init.md +440 -0
  18. package/.claude/commands/enhance-prompt.md +93 -0
  19. package/.claude/commands/memory/code-map-memory.md +687 -0
  20. package/.claude/commands/memory/compact.md +383 -0
  21. package/.claude/commands/memory/docs-full-cli.md +471 -0
  22. package/.claude/commands/memory/docs-related-cli.md +386 -0
  23. package/.claude/commands/memory/docs.md +615 -0
  24. package/.claude/commands/memory/load-skill-memory.md +182 -0
  25. package/.claude/commands/memory/load.md +240 -0
  26. package/.claude/commands/memory/skill-memory.md +525 -0
  27. package/.claude/commands/memory/style-skill-memory.md +396 -0
  28. package/.claude/commands/memory/tech-research-rules.md +310 -0
  29. package/.claude/commands/memory/update-full.md +332 -0
  30. package/.claude/commands/memory/update-related.md +332 -0
  31. package/.claude/commands/memory/workflow-skill-memory.md +517 -0
  32. package/.claude/commands/task/breakdown.md +204 -0
  33. package/.claude/commands/task/create.md +152 -0
  34. package/.claude/commands/task/execute.md +270 -0
  35. package/.claude/commands/task/replan.md +437 -0
  36. package/.claude/commands/version.md +254 -0
  37. package/.claude/commands/workflow/action-plan-verify.md +447 -0
  38. package/.claude/commands/workflow/brainstorm/api-designer.md +587 -0
  39. package/.claude/commands/workflow/brainstorm/artifacts.md +453 -0
  40. package/.claude/commands/workflow/brainstorm/auto-parallel.md +443 -0
  41. package/.claude/commands/workflow/brainstorm/data-architect.md +220 -0
  42. package/.claude/commands/workflow/brainstorm/product-manager.md +200 -0
  43. package/.claude/commands/workflow/brainstorm/product-owner.md +200 -0
  44. package/.claude/commands/workflow/brainstorm/scrum-master.md +200 -0
  45. package/.claude/commands/workflow/brainstorm/subject-matter-expert.md +200 -0
  46. package/.claude/commands/workflow/brainstorm/synthesis.md +398 -0
  47. package/.claude/commands/workflow/brainstorm/system-architect.md +389 -0
  48. package/.claude/commands/workflow/brainstorm/ui-designer.md +221 -0
  49. package/.claude/commands/workflow/brainstorm/ux-expert.md +221 -0
  50. package/.claude/commands/workflow/debug.md +321 -0
  51. package/.claude/commands/workflow/execute.md +475 -0
  52. package/.claude/commands/workflow/init.md +165 -0
  53. package/.claude/commands/workflow/lite-execute.md +792 -0
  54. package/.claude/commands/workflow/lite-fix.md +623 -0
  55. package/.claude/commands/workflow/lite-plan.md +610 -0
  56. package/.claude/commands/workflow/plan.md +551 -0
  57. package/.claude/commands/workflow/replan.md +515 -0
  58. package/.claude/commands/workflow/review-fix.md +606 -0
  59. package/.claude/commands/workflow/review-module-cycle.md +767 -0
  60. package/.claude/commands/workflow/review-session-cycle.md +778 -0
  61. package/.claude/commands/workflow/review.md +297 -0
  62. package/.claude/commands/workflow/session/complete.md +153 -0
  63. package/.claude/commands/workflow/session/list.md +96 -0
  64. package/.claude/commands/workflow/session/resume.md +61 -0
  65. package/.claude/commands/workflow/session/start.md +200 -0
  66. package/.claude/commands/workflow/tdd-plan.md +460 -0
  67. package/.claude/commands/workflow/tdd-verify.md +400 -0
  68. package/.claude/commands/workflow/test-cycle-execute.md +500 -0
  69. package/.claude/commands/workflow/test-fix-gen.md +699 -0
  70. package/.claude/commands/workflow/test-gen.md +529 -0
  71. package/.claude/commands/workflow/tools/conflict-resolution.md +766 -0
  72. package/.claude/commands/workflow/tools/context-gather.md +436 -0
  73. package/.claude/commands/workflow/tools/task-generate-agent.md +490 -0
  74. package/.claude/commands/workflow/tools/task-generate-tdd.md +526 -0
  75. package/.claude/commands/workflow/tools/tdd-coverage-analysis.md +309 -0
  76. package/.claude/commands/workflow/tools/test-concept-enhanced.md +164 -0
  77. package/.claude/commands/workflow/tools/test-context-gather.md +236 -0
  78. package/.claude/commands/workflow/tools/test-task-generate.md +257 -0
  79. package/.claude/commands/workflow/ui-design/animation-extract.md +1150 -0
  80. package/.claude/commands/workflow/ui-design/codify-style.md +652 -0
  81. package/.claude/commands/workflow/ui-design/design-sync.md +454 -0
  82. package/.claude/commands/workflow/ui-design/explore-auto.md +678 -0
  83. package/.claude/commands/workflow/ui-design/generate.md +504 -0
  84. package/.claude/commands/workflow/ui-design/imitate-auto.md +745 -0
  85. package/.claude/commands/workflow/ui-design/import-from-code.md +540 -0
  86. package/.claude/commands/workflow/ui-design/layout-extract.md +788 -0
  87. package/.claude/commands/workflow/ui-design/reference-page-generator.md +356 -0
  88. package/.claude/commands/workflow/ui-design/style-extract.md +773 -0
  89. package/.claude/skills/command-guide/SKILL.md +388 -0
  90. package/.claude/skills/command-guide/UPDATE-GUIDELINE.md +592 -0
  91. package/.claude/skills/command-guide/guides/cli-tools-guide.md +410 -0
  92. package/.claude/skills/command-guide/guides/examples.md +537 -0
  93. package/.claude/skills/command-guide/guides/getting-started.md +242 -0
  94. package/.claude/skills/command-guide/guides/implementation-details.md +1010 -0
  95. package/.claude/skills/command-guide/guides/index-structure.md +326 -0
  96. package/.claude/skills/command-guide/guides/troubleshooting.md +92 -0
  97. package/.claude/skills/command-guide/guides/ui-design-workflow-guide.md +316 -0
  98. package/.claude/skills/command-guide/guides/workflow-patterns.md +662 -0
  99. package/.claude/skills/command-guide/index/all-commands.json +772 -0
  100. package/.claude/skills/command-guide/index/by-category.json +800 -0
  101. package/.claude/skills/command-guide/index/by-use-case.json +786 -0
  102. package/.claude/skills/command-guide/index/command-relationships.json +307 -0
  103. package/.claude/skills/command-guide/index/essential-commands.json +112 -0
  104. package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +857 -0
  105. package/.claude/skills/command-guide/reference/agents/cli-execution-agent.md +267 -0
  106. package/.claude/skills/command-guide/reference/agents/cli-explore-agent.md +182 -0
  107. package/.claude/skills/command-guide/reference/agents/cli-lite-planning-agent.md +446 -0
  108. package/.claude/skills/command-guide/reference/agents/cli-planning-agent.md +558 -0
  109. package/.claude/skills/command-guide/reference/agents/code-developer.md +311 -0
  110. package/.claude/skills/command-guide/reference/agents/conceptual-planning-agent.md +308 -0
  111. package/.claude/skills/command-guide/reference/agents/context-search-agent.md +581 -0
  112. package/.claude/skills/command-guide/reference/agents/doc-generator.md +330 -0
  113. package/.claude/skills/command-guide/reference/agents/memory-bridge.md +94 -0
  114. package/.claude/skills/command-guide/reference/agents/test-context-search-agent.md +400 -0
  115. package/.claude/skills/command-guide/reference/agents/test-fix-agent.md +344 -0
  116. package/.claude/skills/command-guide/reference/agents/ui-design-agent.md +593 -0
  117. package/.claude/skills/command-guide/reference/agents/universal-executor.md +131 -0
  118. package/.claude/skills/command-guide/reference/commands/cli/cli-init.md +440 -0
  119. package/.claude/skills/command-guide/reference/commands/enhance-prompt.md +93 -0
  120. package/.claude/skills/command-guide/reference/commands/memory/code-map-memory.md +687 -0
  121. package/.claude/skills/command-guide/reference/commands/memory/docs-full-cli.md +471 -0
  122. package/.claude/skills/command-guide/reference/commands/memory/docs-related-cli.md +386 -0
  123. package/.claude/skills/command-guide/reference/commands/memory/docs.md +616 -0
  124. package/.claude/skills/command-guide/reference/commands/memory/load-skill-memory.md +182 -0
  125. package/.claude/skills/command-guide/reference/commands/memory/load.md +240 -0
  126. package/.claude/skills/command-guide/reference/commands/memory/skill-memory.md +525 -0
  127. package/.claude/skills/command-guide/reference/commands/memory/style-skill-memory.md +396 -0
  128. package/.claude/skills/command-guide/reference/commands/memory/tech-research.md +314 -0
  129. package/.claude/skills/command-guide/reference/commands/memory/update-full.md +332 -0
  130. package/.claude/skills/command-guide/reference/commands/memory/update-related.md +332 -0
  131. package/.claude/skills/command-guide/reference/commands/memory/workflow-skill-memory.md +517 -0
  132. package/.claude/skills/command-guide/reference/commands/task/breakdown.md +204 -0
  133. package/.claude/skills/command-guide/reference/commands/task/create.md +152 -0
  134. package/.claude/skills/command-guide/reference/commands/task/execute.md +270 -0
  135. package/.claude/skills/command-guide/reference/commands/task/replan.md +437 -0
  136. package/.claude/skills/command-guide/reference/commands/version.md +254 -0
  137. package/.claude/skills/command-guide/reference/commands/workflow/action-plan-verify.md +447 -0
  138. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/api-designer.md +585 -0
  139. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/artifacts.md +452 -0
  140. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/auto-parallel.md +443 -0
  141. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/data-architect.md +220 -0
  142. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/product-manager.md +200 -0
  143. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/product-owner.md +200 -0
  144. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/scrum-master.md +200 -0
  145. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/subject-matter-expert.md +200 -0
  146. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/synthesis.md +398 -0
  147. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/system-architect.md +387 -0
  148. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/ui-designer.md +221 -0
  149. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/ux-expert.md +221 -0
  150. package/.claude/skills/command-guide/reference/commands/workflow/execute.md +465 -0
  151. package/.claude/skills/command-guide/reference/commands/workflow/init.md +164 -0
  152. package/.claude/skills/command-guide/reference/commands/workflow/lite-execute.md +748 -0
  153. package/.claude/skills/command-guide/reference/commands/workflow/lite-fix.md +664 -0
  154. package/.claude/skills/command-guide/reference/commands/workflow/lite-plan.md +645 -0
  155. package/.claude/skills/command-guide/reference/commands/workflow/plan.md +551 -0
  156. package/.claude/skills/command-guide/reference/commands/workflow/replan.md +515 -0
  157. package/.claude/skills/command-guide/reference/commands/workflow/review-fix.md +606 -0
  158. package/.claude/skills/command-guide/reference/commands/workflow/review-module-cycle.md +765 -0
  159. package/.claude/skills/command-guide/reference/commands/workflow/review-session-cycle.md +776 -0
  160. package/.claude/skills/command-guide/reference/commands/workflow/review.md +298 -0
  161. package/.claude/skills/command-guide/reference/commands/workflow/session/complete.md +547 -0
  162. package/.claude/skills/command-guide/reference/commands/workflow/session/list.md +114 -0
  163. package/.claude/skills/command-guide/reference/commands/workflow/session/resume.md +77 -0
  164. package/.claude/skills/command-guide/reference/commands/workflow/session/start.md +257 -0
  165. package/.claude/skills/command-guide/reference/commands/workflow/tdd-plan.md +460 -0
  166. package/.claude/skills/command-guide/reference/commands/workflow/tdd-verify.md +400 -0
  167. package/.claude/skills/command-guide/reference/commands/workflow/test-cycle-execute.md +498 -0
  168. package/.claude/skills/command-guide/reference/commands/workflow/test-fix-gen.md +699 -0
  169. package/.claude/skills/command-guide/reference/commands/workflow/test-gen.md +529 -0
  170. package/.claude/skills/command-guide/reference/commands/workflow/tools/conflict-resolution.md +766 -0
  171. package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +434 -0
  172. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-agent.md +487 -0
  173. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +525 -0
  174. package/.claude/skills/command-guide/reference/commands/workflow/tools/tdd-coverage-analysis.md +309 -0
  175. package/.claude/skills/command-guide/reference/commands/workflow/tools/test-concept-enhanced.md +163 -0
  176. package/.claude/skills/command-guide/reference/commands/workflow/tools/test-context-gather.md +235 -0
  177. package/.claude/skills/command-guide/reference/commands/workflow/tools/test-task-generate.md +256 -0
  178. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/animation-extract.md +1150 -0
  179. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/codify-style.md +652 -0
  180. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/design-sync.md +454 -0
  181. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/explore-auto.md +678 -0
  182. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/generate.md +504 -0
  183. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/imitate-auto.md +745 -0
  184. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/import-from-code.md +537 -0
  185. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/layout-extract.md +788 -0
  186. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/reference-page-generator.md +356 -0
  187. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/style-extract.md +773 -0
  188. package/.claude/skills/command-guide/scripts/analyze_commands.py +502 -0
  189. package/.claude/skills/command-guide/scripts/update-index.sh +130 -0
  190. package/.claude/skills/command-guide/templates/issue-bug.md +104 -0
  191. package/.claude/skills/command-guide/templates/issue-diagnosis.md +275 -0
  192. package/.claude/skills/command-guide/templates/issue-feature.md +97 -0
  193. package/.claude/skills/command-guide/templates/issue-question.md +141 -0
  194. package/.claude/skills/prompt-enhancer/SKILL.md +124 -0
  195. package/.claude/workflows/_template-compare-matrix.html +692 -0
  196. package/.claude/workflows/chinese-response.md +38 -0
  197. package/.claude/workflows/cli-templates/fix-plan-template.json +75 -0
  198. package/.claude/workflows/cli-templates/fix-progress-template.json +48 -0
  199. package/.claude/workflows/cli-templates/memory/style-skill-memory/skill-md-template.md +299 -0
  200. package/.claude/workflows/cli-templates/planning-roles/data-architect.md +120 -0
  201. package/.claude/workflows/cli-templates/planning-roles/product-manager.md +119 -0
  202. package/.claude/workflows/cli-templates/planning-roles/product-owner.md +261 -0
  203. package/.claude/workflows/cli-templates/planning-roles/scrum-master.md +186 -0
  204. package/.claude/workflows/cli-templates/planning-roles/subject-matter-expert.md +281 -0
  205. package/.claude/workflows/cli-templates/planning-roles/synthesis-role.md +414 -0
  206. package/.claude/workflows/cli-templates/planning-roles/system-architect.md +106 -0
  207. package/.claude/workflows/cli-templates/planning-roles/test-strategist.md +124 -0
  208. package/.claude/workflows/cli-templates/planning-roles/ui-designer.md +379 -0
  209. package/.claude/workflows/cli-templates/planning-roles/ux-expert.md +240 -0
  210. package/.claude/workflows/cli-templates/prompts/analysis/01-diagnose-bug-root-cause.txt +127 -0
  211. package/.claude/workflows/cli-templates/prompts/analysis/01-trace-code-execution.txt +115 -0
  212. package/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt +37 -0
  213. package/.claude/workflows/cli-templates/prompts/analysis/02-analyze-technical-document.txt +33 -0
  214. package/.claude/workflows/cli-templates/prompts/analysis/02-review-architecture.txt +29 -0
  215. package/.claude/workflows/cli-templates/prompts/analysis/02-review-code-quality.txt +28 -0
  216. package/.claude/workflows/cli-templates/prompts/analysis/03-analyze-performance.txt +29 -0
  217. package/.claude/workflows/cli-templates/prompts/analysis/03-assess-security-risks.txt +29 -0
  218. package/.claude/workflows/cli-templates/prompts/analysis/03-review-quality-standards.txt +29 -0
  219. package/.claude/workflows/cli-templates/prompts/development/02-generate-tests.txt +70 -0
  220. package/.claude/workflows/cli-templates/prompts/development/02-implement-component-ui.txt +55 -0
  221. package/.claude/workflows/cli-templates/prompts/development/02-implement-feature.txt +58 -0
  222. package/.claude/workflows/cli-templates/prompts/development/02-refactor-codebase.txt +55 -0
  223. package/.claude/workflows/cli-templates/prompts/development/03-debug-runtime-issues.txt +55 -0
  224. package/.claude/workflows/cli-templates/prompts/documentation/api.txt +15 -0
  225. package/.claude/workflows/cli-templates/prompts/documentation/folder-navigation.txt +27 -0
  226. package/.claude/workflows/cli-templates/prompts/documentation/module-readme.txt +49 -0
  227. package/.claude/workflows/cli-templates/prompts/documentation/project-architecture.txt +41 -0
  228. package/.claude/workflows/cli-templates/prompts/documentation/project-examples.txt +35 -0
  229. package/.claude/workflows/cli-templates/prompts/documentation/project-readme.txt +35 -0
  230. package/.claude/workflows/cli-templates/prompts/memory/02-document-module-structure.txt +165 -0
  231. package/.claude/workflows/cli-templates/prompts/planning/01-plan-architecture-design.txt +109 -0
  232. package/.claude/workflows/cli-templates/prompts/planning/02-breakdown-task-steps.txt +30 -0
  233. package/.claude/workflows/cli-templates/prompts/planning/02-design-component-spec.txt +28 -0
  234. package/.claude/workflows/cli-templates/prompts/planning/03-evaluate-concept-feasibility.txt +127 -0
  235. package/.claude/workflows/cli-templates/prompts/planning/03-plan-migration-strategy.txt +30 -0
  236. package/.claude/workflows/cli-templates/prompts/rules/rule-api.txt +122 -0
  237. package/.claude/workflows/cli-templates/prompts/rules/rule-components.txt +122 -0
  238. package/.claude/workflows/cli-templates/prompts/rules/rule-config.txt +89 -0
  239. package/.claude/workflows/cli-templates/prompts/rules/rule-core.txt +60 -0
  240. package/.claude/workflows/cli-templates/prompts/rules/rule-patterns.txt +70 -0
  241. package/.claude/workflows/cli-templates/prompts/rules/rule-testing.txt +81 -0
  242. package/.claude/workflows/cli-templates/prompts/rules/tech-rules-agent-prompt.txt +89 -0
  243. package/.claude/workflows/cli-templates/prompts/tech/tech-module-format.txt +359 -0
  244. package/.claude/workflows/cli-templates/prompts/tech/tech-skill-index.txt +185 -0
  245. package/.claude/workflows/cli-templates/prompts/test/test-concept-analysis.txt +179 -0
  246. package/.claude/workflows/cli-templates/prompts/universal/00-universal-creative-style.txt +95 -0
  247. package/.claude/workflows/cli-templates/prompts/universal/00-universal-rigorous-style.txt +92 -0
  248. package/.claude/workflows/cli-templates/prompts/verification/codex-technical.txt +28 -0
  249. package/.claude/workflows/cli-templates/prompts/verification/cross-validation.txt +28 -0
  250. package/.claude/workflows/cli-templates/prompts/verification/gemini-strategic.txt +27 -0
  251. package/.claude/workflows/cli-templates/prompts/workflow/analysis-results-structure.txt +224 -0
  252. package/.claude/workflows/cli-templates/prompts/workflow/codex-feasibility-validation.txt +176 -0
  253. package/.claude/workflows/cli-templates/prompts/workflow/gemini-solution-design.txt +131 -0
  254. package/.claude/workflows/cli-templates/prompts/workflow/impl-plan-template.txt +286 -0
  255. package/.claude/workflows/cli-templates/prompts/workflow/skill-aggregation.txt +172 -0
  256. package/.claude/workflows/cli-templates/prompts/workflow/skill-conflict-patterns.txt +94 -0
  257. package/.claude/workflows/cli-templates/prompts/workflow/skill-index.txt +224 -0
  258. package/.claude/workflows/cli-templates/prompts/workflow/skill-lessons-learned.txt +94 -0
  259. package/.claude/workflows/cli-templates/prompts/workflow/skill-sessions-timeline.txt +53 -0
  260. package/.claude/workflows/cli-templates/prompts/workflow/task-json-agent-mode.txt +123 -0
  261. package/.claude/workflows/cli-templates/prompts/workflow/task-json-cli-mode.txt +182 -0
  262. package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +112 -0
  263. package/.claude/workflows/cli-templates/protocols/write-protocol.md +201 -0
  264. package/.claude/workflows/cli-templates/schemas/conflict-resolution-schema.json +137 -0
  265. package/.claude/workflows/cli-templates/schemas/debug-log-json-schema.json +127 -0
  266. package/.claude/workflows/cli-templates/schemas/diagnosis-json-schema.json +234 -0
  267. package/.claude/workflows/cli-templates/schemas/explore-json-schema.json +124 -0
  268. package/.claude/workflows/cli-templates/schemas/fix-plan-json-schema.json +298 -0
  269. package/.claude/workflows/cli-templates/schemas/plan-json-schema.json +244 -0
  270. package/.claude/workflows/cli-templates/schemas/project-json-schema.json +221 -0
  271. package/.claude/workflows/cli-templates/schemas/review-deep-dive-results-schema.json +82 -0
  272. package/.claude/workflows/cli-templates/schemas/review-dimension-results-schema.json +51 -0
  273. package/.claude/workflows/cli-templates/tech-stacks/go-dev.md +91 -0
  274. package/.claude/workflows/cli-templates/tech-stacks/java-dev.md +107 -0
  275. package/.claude/workflows/cli-templates/tech-stacks/javascript-dev.md +58 -0
  276. package/.claude/workflows/cli-templates/tech-stacks/python-dev.md +79 -0
  277. package/.claude/workflows/cli-templates/tech-stacks/react-dev.md +103 -0
  278. package/.claude/workflows/cli-templates/tech-stacks/typescript-dev.md +83 -0
  279. package/.claude/workflows/cli-templates/ui-design/systems/animation-tokens.json +247 -0
  280. package/.claude/workflows/cli-templates/ui-design/systems/design-tokens.json +342 -0
  281. package/.claude/workflows/cli-templates/ui-design/systems/layout-templates.json +145 -0
  282. package/.claude/workflows/cli-tools-usage.md +526 -0
  283. package/.claude/workflows/coding-philosophy.md +70 -0
  284. package/.claude/workflows/context-tools.md +84 -0
  285. package/.claude/workflows/file-modification.md +64 -0
  286. package/.claude/workflows/review-directory-specification.md +336 -0
  287. package/.claude/workflows/task-core.md +214 -0
  288. package/.claude/workflows/tool-strategy.md +216 -0
  289. package/.claude/workflows/windows-platform.md +16 -0
  290. package/.claude/workflows/workflow-architecture.md +942 -0
  291. package/.codex/AGENTS.md +63 -0
  292. package/.codex/prompts/debug.md +318 -0
  293. package/.codex/prompts/execute.md +273 -0
  294. package/.codex/prompts/lite-execute.md +164 -0
  295. package/.codex/prompts/lite-plan.md +469 -0
  296. package/.codex/prompts.zip +0 -0
  297. package/.gemini/GEMINI.md +25 -0
  298. package/.qwen/QWEN.md +25 -0
  299. package/LICENSE +21 -0
  300. package/README.md +294 -145
  301. package/ccw/README.md +145 -0
  302. package/ccw/dist/core/lite-scanner-complete.d.ts.map +1 -0
  303. package/ccw/dist/core/lite-scanner-complete.js +371 -0
  304. package/ccw/dist/core/lite-scanner-complete.js.map +1 -0
  305. package/ccw/dist/core/lite-scanner.d.ts.map +1 -0
  306. package/ccw/dist/core/lite-scanner.js +371 -0
  307. package/ccw/dist/core/lite-scanner.js.map +1 -0
  308. package/ccw/dist/core/routes/claude-routes.d.ts.map +1 -0
  309. package/ccw/dist/core/routes/claude-routes.js +1015 -0
  310. package/ccw/dist/core/routes/claude-routes.js.map +1 -0
  311. package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -0
  312. package/ccw/dist/core/routes/cli-routes.js +469 -0
  313. package/ccw/dist/core/routes/cli-routes.js.map +1 -0
  314. package/ccw/dist/core/routes/rules-routes.d.ts.map +1 -0
  315. package/ccw/dist/core/routes/rules-routes.js +443 -0
  316. package/ccw/dist/core/routes/rules-routes.js.map +1 -0
  317. package/ccw/dist/core/routes/skills-routes.d.ts.map +1 -0
  318. package/ccw/dist/core/routes/skills-routes.js +651 -0
  319. package/ccw/dist/core/routes/skills-routes.js.map +1 -0
  320. package/ccw/dist/tools/cli-executor.d.ts +376 -0
  321. package/ccw/dist/tools/cli-executor.d.ts.map +1 -0
  322. package/ccw/dist/tools/cli-executor.js +1677 -0
  323. package/ccw/dist/tools/cli-executor.js.map +1 -0
  324. package/ccw/package.json +65 -0
  325. package/ccw/src/core/lite-scanner-complete.ts +473 -0
  326. package/ccw/src/core/lite-scanner.ts +473 -0
  327. package/ccw/src/core/routes/claude-routes.ts +1181 -0
  328. package/ccw/src/core/routes/cli-routes.ts +562 -0
  329. package/ccw/src/core/routes/rules-routes.ts +527 -0
  330. package/ccw/src/core/routes/skills-routes.ts +737 -0
  331. package/ccw/src/templates/dashboard-js/i18n.js +2969 -0
  332. package/ccw/src/templates/dashboard-js/views/claude-manager.js +926 -0
  333. package/ccw/src/templates/dashboard-js/views/cli-manager.js +2274 -0
  334. package/ccw/src/templates/dashboard-js/views/rules-manager.js +857 -0
  335. package/ccw/src/templates/dashboard-js/views/skills-manager.js +1183 -0
  336. package/ccw/src/tools/cli-executor.ts +2082 -0
  337. package/codex-lens/pyproject.toml +66 -0
  338. package/codex-lens/src/codexlens/.workflow/.cli-history/history.db +0 -0
  339. package/codex-lens/src/codexlens/__init__.py +28 -0
  340. package/codex-lens/src/codexlens/__main__.py +14 -0
  341. package/codex-lens/src/codexlens/__pycache__/__init__.cpython-313.pyc +0 -0
  342. package/codex-lens/src/codexlens/__pycache__/__main__.cpython-313.pyc +0 -0
  343. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  344. package/codex-lens/src/codexlens/__pycache__/entities.cpython-313.pyc +0 -0
  345. package/codex-lens/src/codexlens/__pycache__/errors.cpython-313.pyc +0 -0
  346. package/codex-lens/src/codexlens/cli/__init__.py +27 -0
  347. package/codex-lens/src/codexlens/cli/__pycache__/__init__.cpython-313.pyc +0 -0
  348. package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
  349. package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
  350. package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
  351. package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
  352. package/codex-lens/src/codexlens/cli/commands.py +1931 -0
  353. package/codex-lens/src/codexlens/cli/embedding_manager.py +620 -0
  354. package/codex-lens/src/codexlens/cli/model_manager.py +311 -0
  355. package/codex-lens/src/codexlens/cli/output.py +124 -0
  356. package/codex-lens/src/codexlens/config.py +201 -0
  357. package/codex-lens/src/codexlens/entities.py +121 -0
  358. package/codex-lens/src/codexlens/errors.py +55 -0
  359. package/codex-lens/src/codexlens/indexing/README.md +77 -0
  360. package/codex-lens/src/codexlens/indexing/__init__.py +4 -0
  361. package/codex-lens/src/codexlens/indexing/__pycache__/__init__.cpython-313.pyc +0 -0
  362. package/codex-lens/src/codexlens/indexing/__pycache__/symbol_extractor.cpython-313.pyc +0 -0
  363. package/codex-lens/src/codexlens/indexing/symbol_extractor.py +243 -0
  364. package/codex-lens/src/codexlens/parsers/__init__.py +8 -0
  365. package/codex-lens/src/codexlens/parsers/__pycache__/__init__.cpython-313.pyc +0 -0
  366. package/codex-lens/src/codexlens/parsers/__pycache__/encoding.cpython-313.pyc +0 -0
  367. package/codex-lens/src/codexlens/parsers/__pycache__/factory.cpython-313.pyc +0 -0
  368. package/codex-lens/src/codexlens/parsers/__pycache__/tokenizer.cpython-313.pyc +0 -0
  369. package/codex-lens/src/codexlens/parsers/__pycache__/treesitter_parser.cpython-313.pyc +0 -0
  370. package/codex-lens/src/codexlens/parsers/encoding.py +202 -0
  371. package/codex-lens/src/codexlens/parsers/factory.py +256 -0
  372. package/codex-lens/src/codexlens/parsers/tokenizer.py +98 -0
  373. package/codex-lens/src/codexlens/parsers/treesitter_parser.py +335 -0
  374. package/codex-lens/src/codexlens/search/__init__.py +15 -0
  375. package/codex-lens/src/codexlens/search/__pycache__/__init__.cpython-313.pyc +0 -0
  376. package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
  377. package/codex-lens/src/codexlens/search/__pycache__/enrichment.cpython-313.pyc +0 -0
  378. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  379. package/codex-lens/src/codexlens/search/__pycache__/query_parser.cpython-313.pyc +0 -0
  380. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  381. package/codex-lens/src/codexlens/search/chain_search.py +647 -0
  382. package/codex-lens/src/codexlens/search/enrichment.py +150 -0
  383. package/codex-lens/src/codexlens/search/hybrid_search.py +313 -0
  384. package/codex-lens/src/codexlens/search/query_parser.py +242 -0
  385. package/codex-lens/src/codexlens/search/ranking.py +274 -0
  386. package/codex-lens/src/codexlens/semantic/__init__.py +76 -0
  387. package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
  388. package/codex-lens/src/codexlens/semantic/__pycache__/ann_index.cpython-313.pyc +0 -0
  389. package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
  390. package/codex-lens/src/codexlens/semantic/__pycache__/code_extractor.cpython-313.pyc +0 -0
  391. package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
  392. package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
  393. package/codex-lens/src/codexlens/semantic/__pycache__/graph_analyzer.cpython-313.pyc +0 -0
  394. package/codex-lens/src/codexlens/semantic/__pycache__/llm_enhancer.cpython-313.pyc +0 -0
  395. package/codex-lens/src/codexlens/semantic/__pycache__/ollama_backend.cpython-313.pyc +0 -0
  396. package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
  397. package/codex-lens/src/codexlens/semantic/ann_index.py +414 -0
  398. package/codex-lens/src/codexlens/semantic/chunker.py +448 -0
  399. package/codex-lens/src/codexlens/semantic/code_extractor.py +274 -0
  400. package/codex-lens/src/codexlens/semantic/embedder.py +244 -0
  401. package/codex-lens/src/codexlens/semantic/gpu_support.py +192 -0
  402. package/codex-lens/src/codexlens/semantic/vector_store.py +955 -0
  403. package/codex-lens/src/codexlens/storage/__init__.py +29 -0
  404. package/codex-lens/src/codexlens/storage/__pycache__/__init__.cpython-313.pyc +0 -0
  405. package/codex-lens/src/codexlens/storage/__pycache__/dir_index.cpython-313.pyc +0 -0
  406. package/codex-lens/src/codexlens/storage/__pycache__/file_cache.cpython-313.pyc +0 -0
  407. package/codex-lens/src/codexlens/storage/__pycache__/index_tree.cpython-313.pyc +0 -0
  408. package/codex-lens/src/codexlens/storage/__pycache__/migration_manager.cpython-313.pyc +0 -0
  409. package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
  410. package/codex-lens/src/codexlens/storage/__pycache__/registry.cpython-313.pyc +0 -0
  411. package/codex-lens/src/codexlens/storage/__pycache__/sqlite_store.cpython-313.pyc +0 -0
  412. package/codex-lens/src/codexlens/storage/__pycache__/sqlite_utils.cpython-313.pyc +0 -0
  413. package/codex-lens/src/codexlens/storage/dir_index.py +1850 -0
  414. package/codex-lens/src/codexlens/storage/file_cache.py +32 -0
  415. package/codex-lens/src/codexlens/storage/index_tree.py +776 -0
  416. package/codex-lens/src/codexlens/storage/migration_manager.py +154 -0
  417. package/codex-lens/src/codexlens/storage/migrations/__init__.py +1 -0
  418. package/codex-lens/src/codexlens/storage/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
  419. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_001_normalize_keywords.cpython-313.pyc +0 -0
  420. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_002_add_token_metadata.cpython-313.pyc +0 -0
  421. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_003_code_relationships.cpython-313.pyc +0 -0
  422. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
  423. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_005_cleanup_unused_fields.cpython-313.pyc +0 -0
  424. package/codex-lens/src/codexlens/storage/migrations/migration_001_normalize_keywords.py +123 -0
  425. package/codex-lens/src/codexlens/storage/migrations/migration_002_add_token_metadata.py +48 -0
  426. package/codex-lens/src/codexlens/storage/migrations/migration_004_dual_fts.py +232 -0
  427. package/codex-lens/src/codexlens/storage/migrations/migration_005_cleanup_unused_fields.py +196 -0
  428. package/codex-lens/src/codexlens/storage/path_mapper.py +274 -0
  429. package/codex-lens/src/codexlens/storage/registry.py +670 -0
  430. package/codex-lens/src/codexlens/storage/sqlite_store.py +576 -0
  431. package/codex-lens/src/codexlens/storage/sqlite_utils.py +64 -0
  432. package/package.json +36 -28
  433. package/dist/core/lite-scanner-complete.d.ts.map +0 -1
  434. package/dist/core/lite-scanner-complete.js +0 -368
  435. package/dist/core/lite-scanner-complete.js.map +0 -1
  436. package/dist/core/lite-scanner.d.ts.map +0 -1
  437. package/dist/core/lite-scanner.js +0 -368
  438. package/dist/core/lite-scanner.js.map +0 -1
  439. package/dist/core/routes/claude-routes.d.ts.map +0 -1
  440. package/dist/core/routes/claude-routes.js +0 -1017
  441. package/dist/core/routes/claude-routes.js.map +0 -1
  442. package/dist/core/routes/cli-routes.d.ts.map +0 -1
  443. package/dist/core/routes/cli-routes.js +0 -468
  444. package/dist/core/routes/cli-routes.js.map +0 -1
  445. package/dist/core/routes/rules-routes.d.ts.map +0 -1
  446. package/dist/core/routes/rules-routes.js +0 -442
  447. package/dist/core/routes/rules-routes.js.map +0 -1
  448. package/dist/core/routes/skills-routes.d.ts.map +0 -1
  449. package/dist/core/routes/skills-routes.js +0 -533
  450. package/dist/core/routes/skills-routes.js.map +0 -1
  451. package/dist/tools/cli-executor.d.ts +0 -373
  452. package/dist/tools/cli-executor.d.ts.map +0 -1
  453. package/dist/tools/cli-executor.js +0 -1625
  454. package/dist/tools/cli-executor.js.map +0 -1
  455. package/src/core/lite-scanner-complete.ts +0 -469
  456. package/src/core/lite-scanner.ts +0 -469
  457. package/src/core/routes/claude-routes.ts +0 -1183
  458. package/src/core/routes/cli-routes.ts +0 -561
  459. package/src/core/routes/rules-routes.ts +0 -526
  460. package/src/core/routes/skills-routes.ts +0 -599
  461. package/src/templates/dashboard-js/i18n.js +0 -2963
  462. package/src/templates/dashboard-js/views/claude-manager.js +0 -912
  463. package/src/templates/dashboard-js/views/cli-manager.js +0 -2272
  464. package/src/templates/dashboard-js/views/rules-manager.js +0 -828
  465. package/src/templates/dashboard-js/views/skills-manager.js +0 -819
  466. package/src/tools/cli-executor.ts +0 -2014
  467. /package/{bin → ccw/bin}/ccw-mcp.js +0 -0
  468. /package/{bin → ccw/bin}/ccw.js +0 -0
  469. /package/{dist → ccw/dist}/cli.d.ts +0 -0
  470. /package/{dist → ccw/dist}/cli.d.ts.map +0 -0
  471. /package/{dist → ccw/dist}/cli.js +0 -0
  472. /package/{dist → ccw/dist}/cli.js.map +0 -0
  473. /package/{dist → ccw/dist}/commands/cli.d.ts +0 -0
  474. /package/{dist → ccw/dist}/commands/cli.d.ts.map +0 -0
  475. /package/{dist → ccw/dist}/commands/cli.js +0 -0
  476. /package/{dist → ccw/dist}/commands/cli.js.map +0 -0
  477. /package/{dist → ccw/dist}/commands/core-memory.d.ts +0 -0
  478. /package/{dist → ccw/dist}/commands/core-memory.d.ts.map +0 -0
  479. /package/{dist → ccw/dist}/commands/core-memory.js +0 -0
  480. /package/{dist → ccw/dist}/commands/core-memory.js.map +0 -0
  481. /package/{dist → ccw/dist}/commands/hook.d.ts +0 -0
  482. /package/{dist → ccw/dist}/commands/hook.d.ts.map +0 -0
  483. /package/{dist → ccw/dist}/commands/hook.js +0 -0
  484. /package/{dist → ccw/dist}/commands/hook.js.map +0 -0
  485. /package/{dist → ccw/dist}/commands/install.d.ts +0 -0
  486. /package/{dist → ccw/dist}/commands/install.d.ts.map +0 -0
  487. /package/{dist → ccw/dist}/commands/install.js +0 -0
  488. /package/{dist → ccw/dist}/commands/install.js.map +0 -0
  489. /package/{dist → ccw/dist}/commands/list.d.ts +0 -0
  490. /package/{dist → ccw/dist}/commands/list.d.ts.map +0 -0
  491. /package/{dist → ccw/dist}/commands/list.js +0 -0
  492. /package/{dist → ccw/dist}/commands/list.js.map +0 -0
  493. /package/{dist → ccw/dist}/commands/memory.d.ts +0 -0
  494. /package/{dist → ccw/dist}/commands/memory.d.ts.map +0 -0
  495. /package/{dist → ccw/dist}/commands/memory.js +0 -0
  496. /package/{dist → ccw/dist}/commands/memory.js.map +0 -0
  497. /package/{dist → ccw/dist}/commands/serve.d.ts +0 -0
  498. /package/{dist → ccw/dist}/commands/serve.d.ts.map +0 -0
  499. /package/{dist → ccw/dist}/commands/serve.js +0 -0
  500. /package/{dist → ccw/dist}/commands/serve.js.map +0 -0
  501. /package/{dist → ccw/dist}/commands/session-path-resolver.d.ts +0 -0
  502. /package/{dist → ccw/dist}/commands/session-path-resolver.d.ts.map +0 -0
  503. /package/{dist → ccw/dist}/commands/session-path-resolver.js +0 -0
  504. /package/{dist → ccw/dist}/commands/session-path-resolver.js.map +0 -0
  505. /package/{dist → ccw/dist}/commands/session.d.ts +0 -0
  506. /package/{dist → ccw/dist}/commands/session.d.ts.map +0 -0
  507. /package/{dist → ccw/dist}/commands/session.js +0 -0
  508. /package/{dist → ccw/dist}/commands/session.js.map +0 -0
  509. /package/{dist → ccw/dist}/commands/stop.d.ts +0 -0
  510. /package/{dist → ccw/dist}/commands/stop.d.ts.map +0 -0
  511. /package/{dist → ccw/dist}/commands/stop.js +0 -0
  512. /package/{dist → ccw/dist}/commands/stop.js.map +0 -0
  513. /package/{dist → ccw/dist}/commands/tool.d.ts +0 -0
  514. /package/{dist → ccw/dist}/commands/tool.d.ts.map +0 -0
  515. /package/{dist → ccw/dist}/commands/tool.js +0 -0
  516. /package/{dist → ccw/dist}/commands/tool.js.map +0 -0
  517. /package/{dist → ccw/dist}/commands/uninstall.d.ts +0 -0
  518. /package/{dist → ccw/dist}/commands/uninstall.d.ts.map +0 -0
  519. /package/{dist → ccw/dist}/commands/uninstall.js +0 -0
  520. /package/{dist → ccw/dist}/commands/uninstall.js.map +0 -0
  521. /package/{dist → ccw/dist}/commands/upgrade.d.ts +0 -0
  522. /package/{dist → ccw/dist}/commands/upgrade.d.ts.map +0 -0
  523. /package/{dist → ccw/dist}/commands/upgrade.js +0 -0
  524. /package/{dist → ccw/dist}/commands/upgrade.js.map +0 -0
  525. /package/{dist → ccw/dist}/commands/view.d.ts +0 -0
  526. /package/{dist → ccw/dist}/commands/view.d.ts.map +0 -0
  527. /package/{dist → ccw/dist}/commands/view.js +0 -0
  528. /package/{dist → ccw/dist}/commands/view.js.map +0 -0
  529. /package/{dist → ccw/dist}/config/storage-paths.d.ts +0 -0
  530. /package/{dist → ccw/dist}/config/storage-paths.d.ts.map +0 -0
  531. /package/{dist → ccw/dist}/config/storage-paths.js +0 -0
  532. /package/{dist → ccw/dist}/config/storage-paths.js.map +0 -0
  533. /package/{dist → ccw/dist}/core/cache-manager.d.ts +0 -0
  534. /package/{dist → ccw/dist}/core/cache-manager.d.ts.map +0 -0
  535. /package/{dist → ccw/dist}/core/cache-manager.js +0 -0
  536. /package/{dist → ccw/dist}/core/cache-manager.js.map +0 -0
  537. /package/{dist → ccw/dist}/core/claude-freshness.d.ts +0 -0
  538. /package/{dist → ccw/dist}/core/claude-freshness.d.ts.map +0 -0
  539. /package/{dist → ccw/dist}/core/claude-freshness.js +0 -0
  540. /package/{dist → ccw/dist}/core/claude-freshness.js.map +0 -0
  541. /package/{dist → ccw/dist}/core/core-memory-store.d.ts +0 -0
  542. /package/{dist → ccw/dist}/core/core-memory-store.d.ts.map +0 -0
  543. /package/{dist → ccw/dist}/core/core-memory-store.js +0 -0
  544. /package/{dist → ccw/dist}/core/core-memory-store.js.map +0 -0
  545. /package/{dist → ccw/dist}/core/dashboard-generator-patch.d.ts +0 -0
  546. /package/{dist → ccw/dist}/core/dashboard-generator-patch.d.ts.map +0 -0
  547. /package/{dist → ccw/dist}/core/dashboard-generator-patch.js +0 -0
  548. /package/{dist → ccw/dist}/core/dashboard-generator-patch.js.map +0 -0
  549. /package/{dist → ccw/dist}/core/dashboard-generator.d.ts +0 -0
  550. /package/{dist → ccw/dist}/core/dashboard-generator.d.ts.map +0 -0
  551. /package/{dist → ccw/dist}/core/dashboard-generator.js +0 -0
  552. /package/{dist → ccw/dist}/core/dashboard-generator.js.map +0 -0
  553. /package/{dist → ccw/dist}/core/data-aggregator.d.ts +0 -0
  554. /package/{dist → ccw/dist}/core/data-aggregator.d.ts.map +0 -0
  555. /package/{dist → ccw/dist}/core/data-aggregator.js +0 -0
  556. /package/{dist → ccw/dist}/core/data-aggregator.js.map +0 -0
  557. /package/{dist → ccw/dist}/core/history-importer.d.ts +0 -0
  558. /package/{dist → ccw/dist}/core/history-importer.d.ts.map +0 -0
  559. /package/{dist → ccw/dist}/core/history-importer.js +0 -0
  560. /package/{dist → ccw/dist}/core/history-importer.js.map +0 -0
  561. /package/{dist → ccw/dist}/core/lite-scanner-complete.d.ts +0 -0
  562. /package/{dist → ccw/dist}/core/lite-scanner.d.ts +0 -0
  563. /package/{dist → ccw/dist}/core/manifest.d.ts +0 -0
  564. /package/{dist → ccw/dist}/core/manifest.d.ts.map +0 -0
  565. /package/{dist → ccw/dist}/core/manifest.js +0 -0
  566. /package/{dist → ccw/dist}/core/manifest.js.map +0 -0
  567. /package/{dist → ccw/dist}/core/memory-embedder-bridge.d.ts +0 -0
  568. /package/{dist → ccw/dist}/core/memory-embedder-bridge.d.ts.map +0 -0
  569. /package/{dist → ccw/dist}/core/memory-embedder-bridge.js +0 -0
  570. /package/{dist → ccw/dist}/core/memory-embedder-bridge.js.map +0 -0
  571. /package/{dist → ccw/dist}/core/memory-store.d.ts +0 -0
  572. /package/{dist → ccw/dist}/core/memory-store.d.ts.map +0 -0
  573. /package/{dist → ccw/dist}/core/memory-store.js +0 -0
  574. /package/{dist → ccw/dist}/core/memory-store.js.map +0 -0
  575. /package/{dist → ccw/dist}/core/routes/ccw-routes.d.ts +0 -0
  576. /package/{dist → ccw/dist}/core/routes/ccw-routes.d.ts.map +0 -0
  577. /package/{dist → ccw/dist}/core/routes/ccw-routes.js +0 -0
  578. /package/{dist → ccw/dist}/core/routes/ccw-routes.js.map +0 -0
  579. /package/{dist → ccw/dist}/core/routes/claude-routes.d.ts +0 -0
  580. /package/{dist → ccw/dist}/core/routes/cli-routes.d.ts +0 -0
  581. /package/{dist → ccw/dist}/core/routes/codexlens-routes.d.ts +0 -0
  582. /package/{dist → ccw/dist}/core/routes/codexlens-routes.d.ts.map +0 -0
  583. /package/{dist → ccw/dist}/core/routes/codexlens-routes.js +0 -0
  584. /package/{dist → ccw/dist}/core/routes/codexlens-routes.js.map +0 -0
  585. /package/{dist → ccw/dist}/core/routes/core-memory-routes.d.ts +0 -0
  586. /package/{dist → ccw/dist}/core/routes/core-memory-routes.d.ts.map +0 -0
  587. /package/{dist → ccw/dist}/core/routes/core-memory-routes.js +0 -0
  588. /package/{dist → ccw/dist}/core/routes/core-memory-routes.js.map +0 -0
  589. /package/{dist → ccw/dist}/core/routes/files-routes.d.ts +0 -0
  590. /package/{dist → ccw/dist}/core/routes/files-routes.d.ts.map +0 -0
  591. /package/{dist → ccw/dist}/core/routes/files-routes.js +0 -0
  592. /package/{dist → ccw/dist}/core/routes/files-routes.js.map +0 -0
  593. /package/{dist → ccw/dist}/core/routes/graph-routes.d.ts +0 -0
  594. /package/{dist → ccw/dist}/core/routes/graph-routes.d.ts.map +0 -0
  595. /package/{dist → ccw/dist}/core/routes/graph-routes.js +0 -0
  596. /package/{dist → ccw/dist}/core/routes/graph-routes.js.map +0 -0
  597. /package/{dist → ccw/dist}/core/routes/help-routes.d.ts +0 -0
  598. /package/{dist → ccw/dist}/core/routes/help-routes.d.ts.map +0 -0
  599. /package/{dist → ccw/dist}/core/routes/help-routes.js +0 -0
  600. /package/{dist → ccw/dist}/core/routes/help-routes.js.map +0 -0
  601. /package/{dist → ccw/dist}/core/routes/hooks-routes.d.ts +0 -0
  602. /package/{dist → ccw/dist}/core/routes/hooks-routes.d.ts.map +0 -0
  603. /package/{dist → ccw/dist}/core/routes/hooks-routes.js +0 -0
  604. /package/{dist → ccw/dist}/core/routes/hooks-routes.js.map +0 -0
  605. /package/{dist → ccw/dist}/core/routes/mcp-routes.d.ts +0 -0
  606. /package/{dist → ccw/dist}/core/routes/mcp-routes.d.ts.map +0 -0
  607. /package/{dist → ccw/dist}/core/routes/mcp-routes.js +0 -0
  608. /package/{dist → ccw/dist}/core/routes/mcp-routes.js.map +0 -0
  609. /package/{dist → ccw/dist}/core/routes/mcp-templates-db.d.ts +0 -0
  610. /package/{dist → ccw/dist}/core/routes/mcp-templates-db.d.ts.map +0 -0
  611. /package/{dist → ccw/dist}/core/routes/mcp-templates-db.js +0 -0
  612. /package/{dist → ccw/dist}/core/routes/mcp-templates-db.js.map +0 -0
  613. /package/{dist → ccw/dist}/core/routes/memory-routes.d.ts +0 -0
  614. /package/{dist → ccw/dist}/core/routes/memory-routes.d.ts.map +0 -0
  615. /package/{dist → ccw/dist}/core/routes/memory-routes.js +0 -0
  616. /package/{dist → ccw/dist}/core/routes/memory-routes.js.map +0 -0
  617. /package/{dist → ccw/dist}/core/routes/rules-routes.d.ts +0 -0
  618. /package/{dist → ccw/dist}/core/routes/session-routes.d.ts +0 -0
  619. /package/{dist → ccw/dist}/core/routes/session-routes.d.ts.map +0 -0
  620. /package/{dist → ccw/dist}/core/routes/session-routes.js +0 -0
  621. /package/{dist → ccw/dist}/core/routes/session-routes.js.map +0 -0
  622. /package/{dist → ccw/dist}/core/routes/skills-routes.d.ts +0 -0
  623. /package/{dist → ccw/dist}/core/routes/status-routes.d.ts +0 -0
  624. /package/{dist → ccw/dist}/core/routes/status-routes.d.ts.map +0 -0
  625. /package/{dist → ccw/dist}/core/routes/status-routes.js +0 -0
  626. /package/{dist → ccw/dist}/core/routes/status-routes.js.map +0 -0
  627. /package/{dist → ccw/dist}/core/routes/system-routes.d.ts +0 -0
  628. /package/{dist → ccw/dist}/core/routes/system-routes.d.ts.map +0 -0
  629. /package/{dist → ccw/dist}/core/routes/system-routes.js +0 -0
  630. /package/{dist → ccw/dist}/core/routes/system-routes.js.map +0 -0
  631. /package/{dist → ccw/dist}/core/server.d.ts +0 -0
  632. /package/{dist → ccw/dist}/core/server.d.ts.map +0 -0
  633. /package/{dist → ccw/dist}/core/server.js +0 -0
  634. /package/{dist → ccw/dist}/core/server.js.map +0 -0
  635. /package/{dist → ccw/dist}/core/session-clustering-service.d.ts +0 -0
  636. /package/{dist → ccw/dist}/core/session-clustering-service.d.ts.map +0 -0
  637. /package/{dist → ccw/dist}/core/session-clustering-service.js +0 -0
  638. /package/{dist → ccw/dist}/core/session-clustering-service.js.map +0 -0
  639. /package/{dist → ccw/dist}/core/session-scanner.d.ts +0 -0
  640. /package/{dist → ccw/dist}/core/session-scanner.d.ts.map +0 -0
  641. /package/{dist → ccw/dist}/core/session-scanner.js +0 -0
  642. /package/{dist → ccw/dist}/core/session-scanner.js.map +0 -0
  643. /package/{dist → ccw/dist}/core/websocket.d.ts +0 -0
  644. /package/{dist → ccw/dist}/core/websocket.d.ts.map +0 -0
  645. /package/{dist → ccw/dist}/core/websocket.js +0 -0
  646. /package/{dist → ccw/dist}/core/websocket.js.map +0 -0
  647. /package/{dist → ccw/dist}/index.d.ts +0 -0
  648. /package/{dist → ccw/dist}/index.d.ts.map +0 -0
  649. /package/{dist → ccw/dist}/index.js +0 -0
  650. /package/{dist → ccw/dist}/index.js.map +0 -0
  651. /package/{dist → ccw/dist}/mcp-server/index.d.ts +0 -0
  652. /package/{dist → ccw/dist}/mcp-server/index.d.ts.map +0 -0
  653. /package/{dist → ccw/dist}/mcp-server/index.js +0 -0
  654. /package/{dist → ccw/dist}/mcp-server/index.js.map +0 -0
  655. /package/{dist → ccw/dist}/tools/classify-folders.d.ts +0 -0
  656. /package/{dist → ccw/dist}/tools/classify-folders.d.ts.map +0 -0
  657. /package/{dist → ccw/dist}/tools/classify-folders.js +0 -0
  658. /package/{dist → ccw/dist}/tools/classify-folders.js.map +0 -0
  659. /package/{dist → ccw/dist}/tools/cli-config-manager.d.ts +0 -0
  660. /package/{dist → ccw/dist}/tools/cli-config-manager.d.ts.map +0 -0
  661. /package/{dist → ccw/dist}/tools/cli-config-manager.js +0 -0
  662. /package/{dist → ccw/dist}/tools/cli-config-manager.js.map +0 -0
  663. /package/{dist → ccw/dist}/tools/cli-history-store.d.ts +0 -0
  664. /package/{dist → ccw/dist}/tools/cli-history-store.d.ts.map +0 -0
  665. /package/{dist → ccw/dist}/tools/cli-history-store.js +0 -0
  666. /package/{dist → ccw/dist}/tools/cli-history-store.js.map +0 -0
  667. /package/{dist → ccw/dist}/tools/codex-lens.d.ts +0 -0
  668. /package/{dist → ccw/dist}/tools/codex-lens.d.ts.map +0 -0
  669. /package/{dist → ccw/dist}/tools/codex-lens.js +0 -0
  670. /package/{dist → ccw/dist}/tools/codex-lens.js.map +0 -0
  671. /package/{dist → ccw/dist}/tools/convert-tokens-to-css.d.ts +0 -0
  672. /package/{dist → ccw/dist}/tools/convert-tokens-to-css.d.ts.map +0 -0
  673. /package/{dist → ccw/dist}/tools/convert-tokens-to-css.js +0 -0
  674. /package/{dist → ccw/dist}/tools/convert-tokens-to-css.js.map +0 -0
  675. /package/{dist → ccw/dist}/tools/core-memory.d.ts +0 -0
  676. /package/{dist → ccw/dist}/tools/core-memory.d.ts.map +0 -0
  677. /package/{dist → ccw/dist}/tools/core-memory.js +0 -0
  678. /package/{dist → ccw/dist}/tools/core-memory.js.map +0 -0
  679. /package/{dist → ccw/dist}/tools/detect-changed-modules.d.ts +0 -0
  680. /package/{dist → ccw/dist}/tools/detect-changed-modules.d.ts.map +0 -0
  681. /package/{dist → ccw/dist}/tools/detect-changed-modules.js +0 -0
  682. /package/{dist → ccw/dist}/tools/detect-changed-modules.js.map +0 -0
  683. /package/{dist → ccw/dist}/tools/discover-design-files.d.ts +0 -0
  684. /package/{dist → ccw/dist}/tools/discover-design-files.d.ts.map +0 -0
  685. /package/{dist → ccw/dist}/tools/discover-design-files.js +0 -0
  686. /package/{dist → ccw/dist}/tools/discover-design-files.js.map +0 -0
  687. /package/{dist → ccw/dist}/tools/edit-file.d.ts +0 -0
  688. /package/{dist → ccw/dist}/tools/edit-file.d.ts.map +0 -0
  689. /package/{dist → ccw/dist}/tools/edit-file.js +0 -0
  690. /package/{dist → ccw/dist}/tools/edit-file.js.map +0 -0
  691. /package/{dist → ccw/dist}/tools/generate-module-docs.d.ts +0 -0
  692. /package/{dist → ccw/dist}/tools/generate-module-docs.d.ts.map +0 -0
  693. /package/{dist → ccw/dist}/tools/generate-module-docs.js +0 -0
  694. /package/{dist → ccw/dist}/tools/generate-module-docs.js.map +0 -0
  695. /package/{dist → ccw/dist}/tools/get-modules-by-depth.d.ts +0 -0
  696. /package/{dist → ccw/dist}/tools/get-modules-by-depth.d.ts.map +0 -0
  697. /package/{dist → ccw/dist}/tools/get-modules-by-depth.js +0 -0
  698. /package/{dist → ccw/dist}/tools/get-modules-by-depth.js.map +0 -0
  699. /package/{dist → ccw/dist}/tools/index.d.ts +0 -0
  700. /package/{dist → ccw/dist}/tools/index.d.ts.map +0 -0
  701. /package/{dist → ccw/dist}/tools/index.js +0 -0
  702. /package/{dist → ccw/dist}/tools/index.js.map +0 -0
  703. /package/{dist → ccw/dist}/tools/native-session-discovery.d.ts +0 -0
  704. /package/{dist → ccw/dist}/tools/native-session-discovery.d.ts.map +0 -0
  705. /package/{dist → ccw/dist}/tools/native-session-discovery.js +0 -0
  706. /package/{dist → ccw/dist}/tools/native-session-discovery.js.map +0 -0
  707. /package/{dist → ccw/dist}/tools/notifier.d.ts +0 -0
  708. /package/{dist → ccw/dist}/tools/notifier.d.ts.map +0 -0
  709. /package/{dist → ccw/dist}/tools/notifier.js +0 -0
  710. /package/{dist → ccw/dist}/tools/notifier.js.map +0 -0
  711. /package/{dist → ccw/dist}/tools/read-file.d.ts +0 -0
  712. /package/{dist → ccw/dist}/tools/read-file.d.ts.map +0 -0
  713. /package/{dist → ccw/dist}/tools/read-file.js +0 -0
  714. /package/{dist → ccw/dist}/tools/read-file.js.map +0 -0
  715. /package/{dist → ccw/dist}/tools/resume-strategy.d.ts +0 -0
  716. /package/{dist → ccw/dist}/tools/resume-strategy.d.ts.map +0 -0
  717. /package/{dist → ccw/dist}/tools/resume-strategy.js +0 -0
  718. /package/{dist → ccw/dist}/tools/resume-strategy.js.map +0 -0
  719. /package/{dist → ccw/dist}/tools/session-content-parser.d.ts +0 -0
  720. /package/{dist → ccw/dist}/tools/session-content-parser.d.ts.map +0 -0
  721. /package/{dist → ccw/dist}/tools/session-content-parser.js +0 -0
  722. /package/{dist → ccw/dist}/tools/session-content-parser.js.map +0 -0
  723. /package/{dist → ccw/dist}/tools/session-manager.d.ts +0 -0
  724. /package/{dist → ccw/dist}/tools/session-manager.d.ts.map +0 -0
  725. /package/{dist → ccw/dist}/tools/session-manager.js +0 -0
  726. /package/{dist → ccw/dist}/tools/session-manager.js.map +0 -0
  727. /package/{dist → ccw/dist}/tools/smart-context.d.ts +0 -0
  728. /package/{dist → ccw/dist}/tools/smart-context.d.ts.map +0 -0
  729. /package/{dist → ccw/dist}/tools/smart-context.js +0 -0
  730. /package/{dist → ccw/dist}/tools/smart-context.js.map +0 -0
  731. /package/{dist → ccw/dist}/tools/smart-search.d.ts +0 -0
  732. /package/{dist → ccw/dist}/tools/smart-search.d.ts.map +0 -0
  733. /package/{dist → ccw/dist}/tools/smart-search.js +0 -0
  734. /package/{dist → ccw/dist}/tools/smart-search.js.map +0 -0
  735. /package/{dist → ccw/dist}/tools/storage-manager.d.ts +0 -0
  736. /package/{dist → ccw/dist}/tools/storage-manager.d.ts.map +0 -0
  737. /package/{dist → ccw/dist}/tools/storage-manager.js +0 -0
  738. /package/{dist → ccw/dist}/tools/storage-manager.js.map +0 -0
  739. /package/{dist → ccw/dist}/tools/ui-generate-preview.d.ts +0 -0
  740. /package/{dist → ccw/dist}/tools/ui-generate-preview.d.ts.map +0 -0
  741. /package/{dist → ccw/dist}/tools/ui-generate-preview.js +0 -0
  742. /package/{dist → ccw/dist}/tools/ui-generate-preview.js.map +0 -0
  743. /package/{dist → ccw/dist}/tools/ui-instantiate-prototypes.d.ts +0 -0
  744. /package/{dist → ccw/dist}/tools/ui-instantiate-prototypes.d.ts.map +0 -0
  745. /package/{dist → ccw/dist}/tools/ui-instantiate-prototypes.js +0 -0
  746. /package/{dist → ccw/dist}/tools/ui-instantiate-prototypes.js.map +0 -0
  747. /package/{dist → ccw/dist}/tools/update-module-claude.d.ts +0 -0
  748. /package/{dist → ccw/dist}/tools/update-module-claude.d.ts.map +0 -0
  749. /package/{dist → ccw/dist}/tools/update-module-claude.js +0 -0
  750. /package/{dist → ccw/dist}/tools/update-module-claude.js.map +0 -0
  751. /package/{dist → ccw/dist}/tools/write-file.d.ts +0 -0
  752. /package/{dist → ccw/dist}/tools/write-file.d.ts.map +0 -0
  753. /package/{dist → ccw/dist}/tools/write-file.js +0 -0
  754. /package/{dist → ccw/dist}/tools/write-file.js.map +0 -0
  755. /package/{dist → ccw/dist}/types/config.d.ts +0 -0
  756. /package/{dist → ccw/dist}/types/config.d.ts.map +0 -0
  757. /package/{dist → ccw/dist}/types/config.js +0 -0
  758. /package/{dist → ccw/dist}/types/config.js.map +0 -0
  759. /package/{dist → ccw/dist}/types/index.d.ts +0 -0
  760. /package/{dist → ccw/dist}/types/index.d.ts.map +0 -0
  761. /package/{dist → ccw/dist}/types/index.js +0 -0
  762. /package/{dist → ccw/dist}/types/index.js.map +0 -0
  763. /package/{dist → ccw/dist}/types/session.d.ts +0 -0
  764. /package/{dist → ccw/dist}/types/session.d.ts.map +0 -0
  765. /package/{dist → ccw/dist}/types/session.js +0 -0
  766. /package/{dist → ccw/dist}/types/session.js.map +0 -0
  767. /package/{dist → ccw/dist}/types/tool.d.ts +0 -0
  768. /package/{dist → ccw/dist}/types/tool.d.ts.map +0 -0
  769. /package/{dist → ccw/dist}/types/tool.js +0 -0
  770. /package/{dist → ccw/dist}/types/tool.js.map +0 -0
  771. /package/{dist → ccw/dist}/utils/browser-launcher.d.ts +0 -0
  772. /package/{dist → ccw/dist}/utils/browser-launcher.d.ts.map +0 -0
  773. /package/{dist → ccw/dist}/utils/browser-launcher.js +0 -0
  774. /package/{dist → ccw/dist}/utils/browser-launcher.js.map +0 -0
  775. /package/{dist → ccw/dist}/utils/file-utils.d.ts +0 -0
  776. /package/{dist → ccw/dist}/utils/file-utils.d.ts.map +0 -0
  777. /package/{dist → ccw/dist}/utils/file-utils.js +0 -0
  778. /package/{dist → ccw/dist}/utils/file-utils.js.map +0 -0
  779. /package/{dist → ccw/dist}/utils/path-resolver.d.ts +0 -0
  780. /package/{dist → ccw/dist}/utils/path-resolver.d.ts.map +0 -0
  781. /package/{dist → ccw/dist}/utils/path-resolver.js +0 -0
  782. /package/{dist → ccw/dist}/utils/path-resolver.js.map +0 -0
  783. /package/{dist → ccw/dist}/utils/path-validator.d.ts +0 -0
  784. /package/{dist → ccw/dist}/utils/path-validator.d.ts.map +0 -0
  785. /package/{dist → ccw/dist}/utils/path-validator.js +0 -0
  786. /package/{dist → ccw/dist}/utils/path-validator.js.map +0 -0
  787. /package/{dist → ccw/dist}/utils/ui.d.ts +0 -0
  788. /package/{dist → ccw/dist}/utils/ui.d.ts.map +0 -0
  789. /package/{dist → ccw/dist}/utils/ui.js +0 -0
  790. /package/{dist → ccw/dist}/utils/ui.js.map +0 -0
  791. /package/{src → ccw/src}/.workflow/.cli-history/history.db +0 -0
  792. /package/{src → ccw/src}/.workflow/.cli-history/history.db-shm +0 -0
  793. /package/{src → ccw/src}/.workflow/.cli-history/history.db-wal +0 -0
  794. /package/{src → ccw/src}/cli.ts +0 -0
  795. /package/{src → ccw/src}/commands/cli.ts +0 -0
  796. /package/{src → ccw/src}/commands/core-memory.ts +0 -0
  797. /package/{src → ccw/src}/commands/hook.ts +0 -0
  798. /package/{src → ccw/src}/commands/install.ts +0 -0
  799. /package/{src → ccw/src}/commands/list.ts +0 -0
  800. /package/{src → ccw/src}/commands/memory.ts +0 -0
  801. /package/{src → ccw/src}/commands/serve.ts +0 -0
  802. /package/{src → ccw/src}/commands/session-path-resolver.ts +0 -0
  803. /package/{src → ccw/src}/commands/session.ts +0 -0
  804. /package/{src → ccw/src}/commands/stop.ts +0 -0
  805. /package/{src → ccw/src}/commands/tool.ts +0 -0
  806. /package/{src → ccw/src}/commands/uninstall.ts +0 -0
  807. /package/{src → ccw/src}/commands/upgrade.ts +0 -0
  808. /package/{src → ccw/src}/commands/view.ts +0 -0
  809. /package/{src → ccw/src}/config/storage-paths.ts +0 -0
  810. /package/{src → ccw/src}/core/cache-manager.ts +0 -0
  811. /package/{src → ccw/src}/core/claude-freshness.ts +0 -0
  812. /package/{src → ccw/src}/core/core-memory-store.ts +0 -0
  813. /package/{src → ccw/src}/core/dashboard-generator-patch.ts +0 -0
  814. /package/{src → ccw/src}/core/dashboard-generator.ts +0 -0
  815. /package/{src → ccw/src}/core/data-aggregator.ts +0 -0
  816. /package/{src → ccw/src}/core/history-importer.ts +0 -0
  817. /package/{src → ccw/src}/core/manifest.ts +0 -0
  818. /package/{src → ccw/src}/core/memory-embedder-bridge.ts +0 -0
  819. /package/{src → ccw/src}/core/memory-store.ts +0 -0
  820. /package/{src → ccw/src}/core/routes/ccw-routes.ts +0 -0
  821. /package/{src → ccw/src}/core/routes/codexlens-routes.ts +0 -0
  822. /package/{src → ccw/src}/core/routes/core-memory-routes.ts +0 -0
  823. /package/{src → ccw/src}/core/routes/files-routes.ts +0 -0
  824. /package/{src → ccw/src}/core/routes/graph-routes.md +0 -0
  825. /package/{src → ccw/src}/core/routes/graph-routes.ts +0 -0
  826. /package/{src → ccw/src}/core/routes/help-routes.ts +0 -0
  827. /package/{src → ccw/src}/core/routes/hooks-routes.ts +0 -0
  828. /package/{src → ccw/src}/core/routes/mcp-routes.ts +0 -0
  829. /package/{src → ccw/src}/core/routes/mcp-routes.ts.backup +0 -0
  830. /package/{src → ccw/src}/core/routes/mcp-templates-db.ts +0 -0
  831. /package/{src → ccw/src}/core/routes/memory-routes.ts +0 -0
  832. /package/{src → ccw/src}/core/routes/session-routes.ts +0 -0
  833. /package/{src → ccw/src}/core/routes/status-routes.ts +0 -0
  834. /package/{src → ccw/src}/core/routes/system-routes.ts +0 -0
  835. /package/{src → ccw/src}/core/server.ts +0 -0
  836. /package/{src → ccw/src}/core/session-clustering-service.ts +0 -0
  837. /package/{src → ccw/src}/core/session-scanner.ts +0 -0
  838. /package/{src → ccw/src}/core/websocket.ts +0 -0
  839. /package/{src → ccw/src}/index.ts +0 -0
  840. /package/{src → ccw/src}/mcp-server/index.ts +0 -0
  841. /package/{src → ccw/src}/templates/assets/css/github-dark.min.css +0 -0
  842. /package/{src → ccw/src}/templates/assets/css/github.min.css +0 -0
  843. /package/{src → ccw/src}/templates/assets/js/cytoscape.min.js +0 -0
  844. /package/{src → ccw/src}/templates/assets/js/d3.min.js +0 -0
  845. /package/{src → ccw/src}/templates/assets/js/highlight.min.js +0 -0
  846. /package/{src → ccw/src}/templates/assets/js/lucide.min.js +0 -0
  847. /package/{src → ccw/src}/templates/assets/js/marked.min.js +0 -0
  848. /package/{src → ccw/src}/templates/assets/js/tailwind.js +0 -0
  849. /package/{src → ccw/src}/templates/dashboard-css/01-base.css +0 -0
  850. /package/{src → ccw/src}/templates/dashboard-css/02-session.css +0 -0
  851. /package/{src → ccw/src}/templates/dashboard-css/03-tasks.css +0 -0
  852. /package/{src → ccw/src}/templates/dashboard-css/04-lite-tasks.css +0 -0
  853. /package/{src → ccw/src}/templates/dashboard-css/05-context.css +0 -0
  854. /package/{src → ccw/src}/templates/dashboard-css/06-cards.css +0 -0
  855. /package/{src → ccw/src}/templates/dashboard-css/07-managers.css +0 -0
  856. /package/{src → ccw/src}/templates/dashboard-css/08-review.css +0 -0
  857. /package/{src → ccw/src}/templates/dashboard-css/09-explorer.css +0 -0
  858. /package/{src → ccw/src}/templates/dashboard-css/10-cli-status.css +0 -0
  859. /package/{src → ccw/src}/templates/dashboard-css/11-cli-history.css +0 -0
  860. /package/{src → ccw/src}/templates/dashboard-css/12-cli-legacy.css +0 -0
  861. /package/{src → ccw/src}/templates/dashboard-css/13-cli-ccw.css +0 -0
  862. /package/{src → ccw/src}/templates/dashboard-css/14-cli-modals.css +0 -0
  863. /package/{src → ccw/src}/templates/dashboard-css/15-cli-endpoints.css +0 -0
  864. /package/{src → ccw/src}/templates/dashboard-css/16-cli-session.css +0 -0
  865. /package/{src → ccw/src}/templates/dashboard-css/17-cli-conversation.css +0 -0
  866. /package/{src → ccw/src}/templates/dashboard-css/18-cli-settings.css +0 -0
  867. /package/{src → ccw/src}/templates/dashboard-css/19-cli-native-session.css +0 -0
  868. /package/{src → ccw/src}/templates/dashboard-css/20-cli-taskqueue.css +0 -0
  869. /package/{src → ccw/src}/templates/dashboard-css/21-cli-toolmgmt.css +0 -0
  870. /package/{src → ccw/src}/templates/dashboard-css/22-cli-semantic.css +0 -0
  871. /package/{src → ccw/src}/templates/dashboard-css/23-memory.css +0 -0
  872. /package/{src → ccw/src}/templates/dashboard-css/24-prompt-history.css +0 -0
  873. /package/{src → ccw/src}/templates/dashboard-css/25-skills-rules.css +0 -0
  874. /package/{src → ccw/src}/templates/dashboard-css/26-claude-manager.css +0 -0
  875. /package/{src → ccw/src}/templates/dashboard-css/27-graph-explorer.css +0 -0
  876. /package/{src → ccw/src}/templates/dashboard-css/28-mcp-manager.css +0 -0
  877. /package/{src → ccw/src}/templates/dashboard-css/29-help.css +0 -0
  878. /package/{src → ccw/src}/templates/dashboard-css/30-core-memory.css +0 -0
  879. /package/{src → ccw/src}/templates/dashboard-js/api.js +0 -0
  880. /package/{src → ccw/src}/templates/dashboard-js/components/_conflict_tab.js +0 -0
  881. /package/{src → ccw/src}/templates/dashboard-js/components/_exp_helpers.js +0 -0
  882. /package/{src → ccw/src}/templates/dashboard-js/components/_review_tab.js +0 -0
  883. /package/{src → ccw/src}/templates/dashboard-js/components/carousel.js +0 -0
  884. /package/{src → ccw/src}/templates/dashboard-js/components/cli-history.js +0 -0
  885. /package/{src → ccw/src}/templates/dashboard-js/components/cli-status.js +0 -0
  886. /package/{src → ccw/src}/templates/dashboard-js/components/flowchart.js +0 -0
  887. /package/{src → ccw/src}/templates/dashboard-js/components/global-notifications.js +0 -0
  888. /package/{src → ccw/src}/templates/dashboard-js/components/hook-manager.js +0 -0
  889. /package/{src → ccw/src}/templates/dashboard-js/components/index-manager.js +0 -0
  890. /package/{src → ccw/src}/templates/dashboard-js/components/mcp-manager.js +0 -0
  891. /package/{src → ccw/src}/templates/dashboard-js/components/modals.js +0 -0
  892. /package/{src → ccw/src}/templates/dashboard-js/components/navigation.js +0 -0
  893. /package/{src → ccw/src}/templates/dashboard-js/components/notifications.js +0 -0
  894. /package/{src → ccw/src}/templates/dashboard-js/components/sidebar.js +0 -0
  895. /package/{src → ccw/src}/templates/dashboard-js/components/storage-manager.js +0 -0
  896. /package/{src → ccw/src}/templates/dashboard-js/components/tabs-context.js +0 -0
  897. /package/{src → ccw/src}/templates/dashboard-js/components/tabs-other.js +0 -0
  898. /package/{src → ccw/src}/templates/dashboard-js/components/task-drawer-core.js +0 -0
  899. /package/{src → ccw/src}/templates/dashboard-js/components/task-drawer-renderers.js +0 -0
  900. /package/{src → ccw/src}/templates/dashboard-js/components/task-queue-sidebar.js +0 -0
  901. /package/{src → ccw/src}/templates/dashboard-js/components/theme.js +0 -0
  902. /package/{src → ccw/src}/templates/dashboard-js/components/version-check.js +0 -0
  903. /package/{src → ccw/src}/templates/dashboard-js/help-i18n.js +0 -0
  904. /package/{src → ccw/src}/templates/dashboard-js/main.js +0 -0
  905. /package/{src → ccw/src}/templates/dashboard-js/state.js +0 -0
  906. /package/{src → ccw/src}/templates/dashboard-js/utils.js +0 -0
  907. /package/{src → ccw/src}/templates/dashboard-js/views/codexlens-manager.js +0 -0
  908. /package/{src → ccw/src}/templates/dashboard-js/views/core-memory-clusters.js +0 -0
  909. /package/{src → ccw/src}/templates/dashboard-js/views/core-memory.js +0 -0
  910. /package/{src → ccw/src}/templates/dashboard-js/views/explorer.js +0 -0
  911. /package/{src → ccw/src}/templates/dashboard-js/views/fix-session.js +0 -0
  912. /package/{src → ccw/src}/templates/dashboard-js/views/graph-explorer.js +0 -0
  913. /package/{src → ccw/src}/templates/dashboard-js/views/help.js +0 -0
  914. /package/{src → ccw/src}/templates/dashboard-js/views/history.js +0 -0
  915. /package/{src → ccw/src}/templates/dashboard-js/views/home.js +0 -0
  916. /package/{src → ccw/src}/templates/dashboard-js/views/hook-manager.js +0 -0
  917. /package/{src → ccw/src}/templates/dashboard-js/views/lite-tasks.js +0 -0
  918. /package/{src → ccw/src}/templates/dashboard-js/views/mcp-manager.js +0 -0
  919. /package/{src → ccw/src}/templates/dashboard-js/views/mcp-manager.js.backup +0 -0
  920. /package/{src → ccw/src}/templates/dashboard-js/views/mcp-manager.js.new +0 -0
  921. /package/{src → ccw/src}/templates/dashboard-js/views/memory.js +0 -0
  922. /package/{src → ccw/src}/templates/dashboard-js/views/project-overview.js +0 -0
  923. /package/{src → ccw/src}/templates/dashboard-js/views/prompt-history.js +0 -0
  924. /package/{src → ccw/src}/templates/dashboard-js/views/review-session.js +0 -0
  925. /package/{src → ccw/src}/templates/dashboard-js/views/session-detail.js +0 -0
  926. /package/{src → ccw/src}/templates/dashboard.html +0 -0
  927. /package/{src → ccw/src}/templates/hooks-config-example.json +0 -0
  928. /package/{src → ccw/src}/templates/review-cycle-dashboard.html +0 -0
  929. /package/{src → ccw/src}/templates/workflow-dashboard.html +0 -0
  930. /package/{src → ccw/src}/tools/classify-folders.ts +0 -0
  931. /package/{src → ccw/src}/tools/cli-config-manager.ts +0 -0
  932. /package/{src → ccw/src}/tools/cli-history-store.ts +0 -0
  933. /package/{src → ccw/src}/tools/codex-lens.ts +0 -0
  934. /package/{src → ccw/src}/tools/convert-tokens-to-css.ts +0 -0
  935. /package/{src → ccw/src}/tools/core-memory.ts +0 -0
  936. /package/{src → ccw/src}/tools/detect-changed-modules.ts +0 -0
  937. /package/{src → ccw/src}/tools/discover-design-files.ts +0 -0
  938. /package/{src → ccw/src}/tools/edit-file.ts +0 -0
  939. /package/{src → ccw/src}/tools/generate-module-docs.ts +0 -0
  940. /package/{src → ccw/src}/tools/get-modules-by-depth.ts +0 -0
  941. /package/{src → ccw/src}/tools/index.ts +0 -0
  942. /package/{src → ccw/src}/tools/native-session-discovery.ts +0 -0
  943. /package/{src → ccw/src}/tools/notifier.ts +0 -0
  944. /package/{src → ccw/src}/tools/read-file.ts +0 -0
  945. /package/{src → ccw/src}/tools/resume-strategy.ts +0 -0
  946. /package/{src → ccw/src}/tools/session-content-parser.ts +0 -0
  947. /package/{src → ccw/src}/tools/session-manager.ts +0 -0
  948. /package/{src → ccw/src}/tools/smart-context.ts +0 -0
  949. /package/{src → ccw/src}/tools/smart-search.ts +0 -0
  950. /package/{src → ccw/src}/tools/smart-search.ts.backup +0 -0
  951. /package/{src → ccw/src}/tools/storage-manager.ts +0 -0
  952. /package/{src → ccw/src}/tools/ui-generate-preview.js +0 -0
  953. /package/{src → ccw/src}/tools/ui-instantiate-prototypes.js +0 -0
  954. /package/{src → ccw/src}/tools/update-module-claude.js +0 -0
  955. /package/{src → ccw/src}/tools/write-file.ts +0 -0
  956. /package/{src → ccw/src}/types/config.ts +0 -0
  957. /package/{src → ccw/src}/types/index.ts +0 -0
  958. /package/{src → ccw/src}/types/session.ts +0 -0
  959. /package/{src → ccw/src}/types/tool.ts +0 -0
  960. /package/{src → ccw/src}/utils/browser-launcher.ts +0 -0
  961. /package/{src → ccw/src}/utils/file-utils.ts +0 -0
  962. /package/{src → ccw/src}/utils/path-resolver.ts +0 -0
  963. /package/{src → ccw/src}/utils/path-validator.ts +0 -0
  964. /package/{src → ccw/src}/utils/ui.ts +0 -0
@@ -0,0 +1,1850 @@
1
+ """Single-directory index storage with hierarchical linking.
2
+
3
+ Each directory maintains its own _index.db with:
4
+ - Files in the current directory
5
+ - Links to subdirectory indexes
6
+ - Full-text search via FTS5
7
+ - Symbol table for code navigation
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ import re
14
+ import sqlite3
15
+ import threading
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+ from typing import Any, Dict, List, Optional, Tuple
19
+
20
+ from codexlens.entities import SearchResult, Symbol
21
+ from codexlens.errors import StorageError
22
+
23
+
24
+ @dataclass
25
+ class SubdirLink:
26
+ """Link to a subdirectory's index database."""
27
+
28
+ id: int
29
+ name: str
30
+ index_path: Path
31
+ files_count: int
32
+ last_updated: float
33
+
34
+
35
+ @dataclass
36
+ class FileEntry:
37
+ """Metadata for an indexed file in current directory."""
38
+
39
+ id: int
40
+ name: str
41
+ full_path: Path
42
+ language: str
43
+ mtime: float
44
+ line_count: int
45
+
46
+
47
+ class DirIndexStore:
48
+ """Single-directory index storage with hierarchical subdirectory linking.
49
+
50
+ Each directory has an independent _index.db containing:
51
+ - Files table: Files in this directory only
52
+ - Subdirs table: Links to child directory indexes
53
+ - Symbols table: Code symbols from files
54
+ - FTS5 index: Full-text search on file content
55
+
56
+ Thread-safe operations with WAL mode enabled.
57
+ """
58
+
59
+ # Schema version for migration tracking
60
+ # Increment this when schema changes require migration
61
+ SCHEMA_VERSION = 5
62
+
63
+ def __init__(self, db_path: str | Path) -> None:
64
+ """Initialize directory index store.
65
+
66
+ Args:
67
+ db_path: Path to _index.db file for this directory
68
+ """
69
+ self.db_path = Path(db_path).resolve()
70
+ self._lock = threading.RLock()
71
+ self._conn: Optional[sqlite3.Connection] = None
72
+ self.logger = logging.getLogger(__name__)
73
+
74
+ def initialize(self) -> None:
75
+ """Create database and schema if not exists."""
76
+ with self._lock:
77
+ self.db_path.parent.mkdir(parents=True, exist_ok=True)
78
+ conn = self._get_connection()
79
+
80
+ # Check current schema version
81
+ current_version = self._get_schema_version(conn)
82
+
83
+ # Fail gracefully if database is from a newer version
84
+ if current_version > self.SCHEMA_VERSION:
85
+ raise StorageError(
86
+ f"Database schema version {current_version} is newer than "
87
+ f"supported version {self.SCHEMA_VERSION}. "
88
+ f"Please update the application or use a compatible database.",
89
+ db_path=str(self.db_path),
90
+ operation="initialize",
91
+ details={
92
+ "current_version": current_version,
93
+ "supported_version": self.SCHEMA_VERSION
94
+ }
95
+ )
96
+
97
+ # Create or migrate schema
98
+ if current_version == 0:
99
+ # New database - create schema directly
100
+ self._create_schema(conn)
101
+ self._create_fts_triggers(conn)
102
+ self._set_schema_version(conn, self.SCHEMA_VERSION)
103
+ elif current_version < self.SCHEMA_VERSION:
104
+ # Existing database - apply migrations
105
+ self._apply_migrations(conn, current_version)
106
+ self._set_schema_version(conn, self.SCHEMA_VERSION)
107
+
108
+ conn.commit()
109
+
110
+ def _get_schema_version(self, conn: sqlite3.Connection) -> int:
111
+ """Get current schema version from database."""
112
+ try:
113
+ row = conn.execute("PRAGMA user_version").fetchone()
114
+ return row[0] if row else 0
115
+ except Exception:
116
+ return 0
117
+
118
+ def _set_schema_version(self, conn: sqlite3.Connection, version: int) -> None:
119
+ """Set schema version in database."""
120
+ conn.execute(f"PRAGMA user_version = {version}")
121
+
122
+ def _apply_migrations(self, conn: sqlite3.Connection, from_version: int) -> None:
123
+ """Apply schema migrations from current version to latest.
124
+
125
+ Args:
126
+ conn: Database connection
127
+ from_version: Current schema version
128
+ """
129
+ # Migration v0/v1 -> v2: Add 'name' column to files table
130
+ if from_version < 2:
131
+ self._migrate_v2_add_name_column(conn)
132
+
133
+ # Migration v2 -> v4: Add dual FTS tables (exact + fuzzy)
134
+ if from_version < 4:
135
+ from codexlens.storage.migrations.migration_004_dual_fts import upgrade
136
+ upgrade(conn)
137
+
138
+ # Migration v4 -> v5: Remove unused/redundant fields
139
+ if from_version < 5:
140
+ from codexlens.storage.migrations.migration_005_cleanup_unused_fields import upgrade
141
+ upgrade(conn)
142
+
143
+ def close(self) -> None:
144
+ """Close database connection."""
145
+ with self._lock:
146
+ if self._conn is not None:
147
+ try:
148
+ self._conn.close()
149
+ except Exception:
150
+ pass
151
+ finally:
152
+ self._conn = None
153
+
154
+ def __enter__(self) -> DirIndexStore:
155
+ """Context manager entry."""
156
+ self.initialize()
157
+ return self
158
+
159
+ def __exit__(self, exc_type: object, exc: object, tb: object) -> None:
160
+ """Context manager exit."""
161
+ self.close()
162
+
163
+ # === File Operations ===
164
+
165
+ def add_file(
166
+ self,
167
+ name: str,
168
+ full_path: str | Path,
169
+ content: str,
170
+ language: str,
171
+ symbols: Optional[List[Symbol]] = None,
172
+ ) -> int:
173
+ """Add or update a file in the current directory index.
174
+
175
+ Args:
176
+ name: Filename without path
177
+ full_path: Complete source file path
178
+ content: File content for indexing
179
+ language: Programming language identifier
180
+ symbols: List of Symbol objects from the file
181
+
182
+ Returns:
183
+ Database file_id
184
+
185
+ Raises:
186
+ StorageError: If database operations fail
187
+ """
188
+ with self._lock:
189
+ conn = self._get_connection()
190
+ full_path_str = str(Path(full_path).resolve())
191
+ mtime = Path(full_path_str).stat().st_mtime if Path(full_path_str).exists() else None
192
+ line_count = content.count('\n') + 1
193
+
194
+ try:
195
+ conn.execute(
196
+ """
197
+ INSERT INTO files(name, full_path, language, content, mtime, line_count)
198
+ VALUES(?, ?, ?, ?, ?, ?)
199
+ ON CONFLICT(full_path) DO UPDATE SET
200
+ name=excluded.name,
201
+ language=excluded.language,
202
+ content=excluded.content,
203
+ mtime=excluded.mtime,
204
+ line_count=excluded.line_count
205
+ """,
206
+ (name, full_path_str, language, content, mtime, line_count),
207
+ )
208
+
209
+ row = conn.execute("SELECT id FROM files WHERE full_path=?", (full_path_str,)).fetchone()
210
+ if not row:
211
+ raise StorageError(f"Failed to retrieve file_id for {full_path_str}")
212
+
213
+ file_id = int(row["id"])
214
+
215
+ # Replace symbols
216
+ conn.execute("DELETE FROM symbols WHERE file_id=?", (file_id,))
217
+ if symbols:
218
+ # Insert symbols without token_count and symbol_type
219
+ symbol_rows = []
220
+ for s in symbols:
221
+ symbol_rows.append(
222
+ (file_id, s.name, s.kind, s.range[0], s.range[1])
223
+ )
224
+
225
+ conn.executemany(
226
+ """
227
+ INSERT INTO symbols(file_id, name, kind, start_line, end_line)
228
+ VALUES(?, ?, ?, ?, ?)
229
+ """,
230
+ symbol_rows,
231
+ )
232
+
233
+ conn.commit()
234
+ return file_id
235
+
236
+ except sqlite3.DatabaseError as exc:
237
+ conn.rollback()
238
+ raise StorageError(f"Failed to add file {name}: {exc}") from exc
239
+
240
+ def add_files_batch(
241
+ self, files: List[Tuple[str, Path, str, str, Optional[List[Symbol]]]]
242
+ ) -> int:
243
+ """Add multiple files in a single transaction.
244
+
245
+ Args:
246
+ files: List of (name, full_path, content, language, symbols) tuples
247
+
248
+ Returns:
249
+ Number of files added
250
+
251
+ Raises:
252
+ StorageError: If batch operation fails
253
+ """
254
+ with self._lock:
255
+ conn = self._get_connection()
256
+ count = 0
257
+
258
+ try:
259
+ conn.execute("BEGIN")
260
+
261
+ for name, full_path, content, language, symbols in files:
262
+ full_path_str = str(Path(full_path).resolve())
263
+ mtime = Path(full_path_str).stat().st_mtime if Path(full_path_str).exists() else None
264
+ line_count = content.count('\n') + 1
265
+
266
+ conn.execute(
267
+ """
268
+ INSERT INTO files(name, full_path, language, content, mtime, line_count)
269
+ VALUES(?, ?, ?, ?, ?, ?)
270
+ ON CONFLICT(full_path) DO UPDATE SET
271
+ name=excluded.name,
272
+ language=excluded.language,
273
+ content=excluded.content,
274
+ mtime=excluded.mtime,
275
+ line_count=excluded.line_count
276
+ """,
277
+ (name, full_path_str, language, content, mtime, line_count),
278
+ )
279
+
280
+ row = conn.execute("SELECT id FROM files WHERE full_path=?", (full_path_str,)).fetchone()
281
+ if not row:
282
+ raise StorageError(f"Failed to retrieve file_id for {full_path_str}")
283
+
284
+ file_id = int(row["id"])
285
+ count += 1
286
+
287
+ conn.execute("DELETE FROM symbols WHERE file_id=?", (file_id,))
288
+ if symbols:
289
+ # Insert symbols
290
+ symbol_rows = []
291
+ for s in symbols:
292
+ symbol_rows.append(
293
+ (file_id, s.name, s.kind, s.range[0], s.range[1])
294
+ )
295
+
296
+ conn.executemany(
297
+ """
298
+ INSERT INTO symbols(file_id, name, kind, start_line, end_line)
299
+ VALUES(?, ?, ?, ?, ?)
300
+ """,
301
+ symbol_rows,
302
+ )
303
+
304
+ conn.commit()
305
+ return count
306
+
307
+ except sqlite3.DatabaseError as exc:
308
+ conn.rollback()
309
+ raise StorageError(f"Batch insert failed: {exc}") from exc
310
+
311
+ def remove_file(self, full_path: str | Path) -> bool:
312
+ """Remove a file from the index.
313
+
314
+ Args:
315
+ full_path: Complete source file path
316
+
317
+ Returns:
318
+ True if file was removed, False if not found
319
+ """
320
+ with self._lock:
321
+ conn = self._get_connection()
322
+ full_path_str = str(Path(full_path).resolve())
323
+
324
+ row = conn.execute("SELECT id FROM files WHERE full_path=?", (full_path_str,)).fetchone()
325
+ if not row:
326
+ return False
327
+
328
+ file_id = int(row["id"])
329
+ conn.execute("DELETE FROM files WHERE id=?", (file_id,))
330
+ conn.commit()
331
+ return True
332
+
333
+ def get_file(self, full_path: str | Path) -> Optional[FileEntry]:
334
+ """Get file metadata.
335
+
336
+ Args:
337
+ full_path: Complete source file path
338
+
339
+ Returns:
340
+ FileEntry if found, None otherwise
341
+ """
342
+ with self._lock:
343
+ conn = self._get_connection()
344
+ full_path_str = str(Path(full_path).resolve())
345
+
346
+ row = conn.execute(
347
+ """
348
+ SELECT id, name, full_path, language, mtime, line_count
349
+ FROM files WHERE full_path=?
350
+ """,
351
+ (full_path_str,),
352
+ ).fetchone()
353
+
354
+ if not row:
355
+ return None
356
+
357
+ return FileEntry(
358
+ id=int(row["id"]),
359
+ name=row["name"],
360
+ full_path=Path(row["full_path"]),
361
+ language=row["language"],
362
+ mtime=float(row["mtime"]) if row["mtime"] else 0.0,
363
+ line_count=int(row["line_count"]) if row["line_count"] else 0,
364
+ )
365
+
366
+ def get_file_mtime(self, full_path: str | Path) -> Optional[float]:
367
+ """Get stored modification time for a file.
368
+
369
+ Args:
370
+ full_path: Complete source file path
371
+
372
+ Returns:
373
+ Modification time as float, or None if not found
374
+ """
375
+ with self._lock:
376
+ conn = self._get_connection()
377
+ full_path_str = str(Path(full_path).resolve())
378
+
379
+ row = conn.execute(
380
+ "SELECT mtime FROM files WHERE full_path=?", (full_path_str,)
381
+ ).fetchone()
382
+
383
+ return float(row["mtime"]) if row and row["mtime"] else None
384
+
385
+ def needs_reindex(self, full_path: str | Path) -> bool:
386
+ """Check if a file needs reindexing based on mtime comparison.
387
+
388
+ Uses 1ms tolerance to handle filesystem timestamp precision variations.
389
+
390
+ Args:
391
+ full_path: Complete source file path
392
+
393
+ Returns:
394
+ True if file should be reindexed (new, modified, or missing from index)
395
+ """
396
+ full_path_obj = Path(full_path).resolve()
397
+ if not full_path_obj.exists():
398
+ return False # File doesn't exist, skip indexing
399
+
400
+ # Get current filesystem mtime
401
+ try:
402
+ current_mtime = full_path_obj.stat().st_mtime
403
+ except OSError:
404
+ return False # Can't read file stats, skip
405
+
406
+ # Get stored mtime from database
407
+ stored_mtime = self.get_file_mtime(full_path_obj)
408
+
409
+ # File not in index, needs indexing
410
+ if stored_mtime is None:
411
+ return True
412
+
413
+ # Compare with 1ms tolerance for floating point precision
414
+ MTIME_TOLERANCE = 0.001
415
+ return abs(current_mtime - stored_mtime) > MTIME_TOLERANCE
416
+
417
+ def add_file_incremental(
418
+ self,
419
+ name: str,
420
+ full_path: str | Path,
421
+ content: str,
422
+ language: str,
423
+ symbols: Optional[List[Symbol]] = None,
424
+ ) -> Optional[int]:
425
+ """Add or update a file only if it has changed (incremental indexing).
426
+
427
+ Checks mtime before indexing to skip unchanged files.
428
+
429
+ Args:
430
+ name: Filename without path
431
+ full_path: Complete source file path
432
+ content: File content for indexing
433
+ language: Programming language identifier
434
+ symbols: List of Symbol objects from the file
435
+
436
+ Returns:
437
+ Database file_id if indexed, None if skipped (unchanged)
438
+
439
+ Raises:
440
+ StorageError: If database operations fail
441
+ """
442
+ # Check if reindexing is needed
443
+ if not self.needs_reindex(full_path):
444
+ return None # Skip unchanged file
445
+
446
+ # File changed or new, perform full indexing
447
+ return self.add_file(name, full_path, content, language, symbols)
448
+
449
+ def cleanup_deleted_files(self, source_dir: Path) -> int:
450
+ """Remove indexed files that no longer exist in the source directory.
451
+
452
+ Scans the source directory and removes database entries for deleted files.
453
+
454
+ Args:
455
+ source_dir: Source directory to scan
456
+
457
+ Returns:
458
+ Number of deleted file entries removed
459
+
460
+ Raises:
461
+ StorageError: If cleanup operations fail
462
+ """
463
+ with self._lock:
464
+ conn = self._get_connection()
465
+ source_dir = source_dir.resolve()
466
+
467
+ try:
468
+ # Get all indexed file paths
469
+ rows = conn.execute("SELECT full_path FROM files").fetchall()
470
+ indexed_paths = {row["full_path"] for row in rows}
471
+
472
+ # Build set of existing files in source directory
473
+ existing_paths = set()
474
+ for file_path in source_dir.rglob("*"):
475
+ if file_path.is_file():
476
+ existing_paths.add(str(file_path.resolve()))
477
+
478
+ # Find orphaned entries (indexed but no longer exist)
479
+ deleted_paths = indexed_paths - existing_paths
480
+
481
+ # Remove orphaned entries
482
+ deleted_count = 0
483
+ for deleted_path in deleted_paths:
484
+ conn.execute("DELETE FROM files WHERE full_path=?", (deleted_path,))
485
+ deleted_count += 1
486
+
487
+ if deleted_count > 0:
488
+ conn.commit()
489
+
490
+ return deleted_count
491
+
492
+ except Exception as exc:
493
+ conn.rollback()
494
+ raise StorageError(f"Failed to cleanup deleted files: {exc}") from exc
495
+
496
+ def list_files(self) -> List[FileEntry]:
497
+ """List all files in current directory.
498
+
499
+ Returns:
500
+ List of FileEntry objects
501
+ """
502
+ with self._lock:
503
+ conn = self._get_connection()
504
+ rows = conn.execute(
505
+ """
506
+ SELECT id, name, full_path, language, mtime, line_count
507
+ FROM files
508
+ ORDER BY name
509
+ """
510
+ ).fetchall()
511
+
512
+ return [
513
+ FileEntry(
514
+ id=int(row["id"]),
515
+ name=row["name"],
516
+ full_path=Path(row["full_path"]),
517
+ language=row["language"],
518
+ mtime=float(row["mtime"]) if row["mtime"] else 0.0,
519
+ line_count=int(row["line_count"]) if row["line_count"] else 0,
520
+ )
521
+ for row in rows
522
+ ]
523
+
524
+ def file_count(self) -> int:
525
+ """Get number of files in current directory.
526
+
527
+ Returns:
528
+ File count
529
+ """
530
+ with self._lock:
531
+ conn = self._get_connection()
532
+ row = conn.execute("SELECT COUNT(*) AS c FROM files").fetchone()
533
+ return int(row["c"]) if row else 0
534
+
535
+ # === Semantic Metadata ===
536
+
537
+ def add_semantic_metadata(
538
+ self,
539
+ file_id: int,
540
+ summary: str,
541
+ keywords: List[str],
542
+ purpose: str,
543
+ llm_tool: str
544
+ ) -> None:
545
+ """Add or update semantic metadata for a file.
546
+
547
+ Args:
548
+ file_id: File ID from files table
549
+ summary: LLM-generated summary
550
+ keywords: List of keywords
551
+ purpose: Purpose/role of the file
552
+ llm_tool: Tool used to generate metadata (gemini/qwen)
553
+ """
554
+ with self._lock:
555
+ conn = self._get_connection()
556
+
557
+ import time
558
+
559
+ generated_at = time.time()
560
+
561
+ # Write to semantic_metadata table (without keywords column)
562
+ conn.execute(
563
+ """
564
+ INSERT INTO semantic_metadata(file_id, summary, purpose, llm_tool, generated_at)
565
+ VALUES(?, ?, ?, ?, ?)
566
+ ON CONFLICT(file_id) DO UPDATE SET
567
+ summary=excluded.summary,
568
+ purpose=excluded.purpose,
569
+ llm_tool=excluded.llm_tool,
570
+ generated_at=excluded.generated_at
571
+ """,
572
+ (file_id, summary, purpose, llm_tool, generated_at),
573
+ )
574
+
575
+ # Write to normalized keywords tables for optimized search
576
+ # First, remove existing keyword associations
577
+ conn.execute("DELETE FROM file_keywords WHERE file_id = ?", (file_id,))
578
+
579
+ # Then add new keywords
580
+ for keyword in keywords:
581
+ keyword = keyword.strip()
582
+ if not keyword:
583
+ continue
584
+
585
+ # Insert keyword if it doesn't exist
586
+ conn.execute(
587
+ "INSERT OR IGNORE INTO keywords(keyword) VALUES(?)",
588
+ (keyword,)
589
+ )
590
+
591
+ # Get keyword_id
592
+ row = conn.execute(
593
+ "SELECT id FROM keywords WHERE keyword = ?",
594
+ (keyword,)
595
+ ).fetchone()
596
+
597
+ if row:
598
+ keyword_id = row["id"]
599
+ # Link file to keyword
600
+ conn.execute(
601
+ "INSERT OR IGNORE INTO file_keywords(file_id, keyword_id) VALUES(?, ?)",
602
+ (file_id, keyword_id)
603
+ )
604
+
605
+ conn.commit()
606
+
607
+ def get_semantic_metadata(self, file_id: int) -> Optional[Dict[str, Any]]:
608
+ """Get semantic metadata for a file.
609
+
610
+ Args:
611
+ file_id: File ID from files table
612
+
613
+ Returns:
614
+ Dict with summary, keywords, purpose, llm_tool, generated_at, or None if not found
615
+ """
616
+ with self._lock:
617
+ conn = self._get_connection()
618
+
619
+ # Get semantic metadata (without keywords column)
620
+ row = conn.execute(
621
+ """
622
+ SELECT summary, purpose, llm_tool, generated_at
623
+ FROM semantic_metadata WHERE file_id=?
624
+ """,
625
+ (file_id,),
626
+ ).fetchone()
627
+
628
+ if not row:
629
+ return None
630
+
631
+ # Get keywords from normalized file_keywords table
632
+ keyword_rows = conn.execute(
633
+ """
634
+ SELECT k.keyword
635
+ FROM file_keywords fk
636
+ JOIN keywords k ON fk.keyword_id = k.id
637
+ WHERE fk.file_id = ?
638
+ ORDER BY k.keyword
639
+ """,
640
+ (file_id,),
641
+ ).fetchall()
642
+
643
+ keywords = [kw["keyword"] for kw in keyword_rows]
644
+
645
+ return {
646
+ "summary": row["summary"],
647
+ "keywords": keywords,
648
+ "purpose": row["purpose"],
649
+ "llm_tool": row["llm_tool"],
650
+ "generated_at": float(row["generated_at"]) if row["generated_at"] else 0.0,
651
+ }
652
+
653
+ def get_files_without_semantic(self) -> List[FileEntry]:
654
+ """Get all files that don't have semantic metadata.
655
+
656
+ Returns:
657
+ List of FileEntry objects without semantic metadata
658
+ """
659
+ with self._lock:
660
+ conn = self._get_connection()
661
+
662
+ rows = conn.execute(
663
+ """
664
+ SELECT f.id, f.name, f.full_path, f.language, f.mtime, f.line_count
665
+ FROM files f
666
+ LEFT JOIN semantic_metadata sm ON f.id = sm.file_id
667
+ WHERE sm.id IS NULL
668
+ ORDER BY f.name
669
+ """
670
+ ).fetchall()
671
+
672
+ return [
673
+ FileEntry(
674
+ id=int(row["id"]),
675
+ name=row["name"],
676
+ full_path=Path(row["full_path"]),
677
+ language=row["language"],
678
+ mtime=float(row["mtime"]) if row["mtime"] else 0.0,
679
+ line_count=int(row["line_count"]) if row["line_count"] else 0,
680
+ )
681
+ for row in rows
682
+ ]
683
+
684
+ def search_semantic_keywords(self, keyword: str, use_normalized: bool = True) -> List[Tuple[FileEntry, List[str]]]:
685
+ """Search files by semantic keywords.
686
+
687
+ Args:
688
+ keyword: Keyword to search for (case-insensitive)
689
+ use_normalized: Use optimized normalized tables (default: True)
690
+
691
+ Returns:
692
+ List of (FileEntry, keywords) tuples where keyword matches
693
+ """
694
+ with self._lock:
695
+ conn = self._get_connection()
696
+
697
+ if use_normalized:
698
+ # Optimized query using normalized tables with indexed lookup
699
+ # Use prefix search (keyword%) for better index utilization
700
+ keyword_pattern = f"{keyword}%"
701
+
702
+ rows = conn.execute(
703
+ """
704
+ SELECT f.id, f.name, f.full_path, f.language, f.mtime, f.line_count,
705
+ GROUP_CONCAT(k.keyword, ',') as keywords
706
+ FROM files f
707
+ JOIN file_keywords fk ON f.id = fk.file_id
708
+ JOIN keywords k ON fk.keyword_id = k.id
709
+ WHERE k.keyword LIKE ? COLLATE NOCASE
710
+ GROUP BY f.id, f.name, f.full_path, f.language, f.mtime, f.line_count
711
+ ORDER BY f.name
712
+ """,
713
+ (keyword_pattern,),
714
+ ).fetchall()
715
+
716
+ results = []
717
+ for row in rows:
718
+ file_entry = FileEntry(
719
+ id=int(row["id"]),
720
+ name=row["name"],
721
+ full_path=Path(row["full_path"]),
722
+ language=row["language"],
723
+ mtime=float(row["mtime"]) if row["mtime"] else 0.0,
724
+ line_count=int(row["line_count"]) if row["line_count"] else 0,
725
+ )
726
+ keywords = row["keywords"].split(',') if row["keywords"] else []
727
+ results.append((file_entry, keywords))
728
+
729
+ return results
730
+
731
+ else:
732
+ # Fallback using normalized tables with contains matching (slower but more flexible)
733
+ keyword_pattern = f"%{keyword}%"
734
+
735
+ rows = conn.execute(
736
+ """
737
+ SELECT f.id, f.name, f.full_path, f.language, f.mtime, f.line_count,
738
+ GROUP_CONCAT(k.keyword, ',') as keywords
739
+ FROM files f
740
+ JOIN file_keywords fk ON f.id = fk.file_id
741
+ JOIN keywords k ON fk.keyword_id = k.id
742
+ WHERE k.keyword LIKE ? COLLATE NOCASE
743
+ GROUP BY f.id, f.name, f.full_path, f.language, f.mtime, f.line_count
744
+ ORDER BY f.name
745
+ """,
746
+ (keyword_pattern,),
747
+ ).fetchall()
748
+
749
+ results = []
750
+ for row in rows:
751
+ file_entry = FileEntry(
752
+ id=int(row["id"]),
753
+ name=row["name"],
754
+ full_path=Path(row["full_path"]),
755
+ language=row["language"],
756
+ mtime=float(row["mtime"]) if row["mtime"] else 0.0,
757
+ line_count=int(row["line_count"]) if row["line_count"] else 0,
758
+ )
759
+ keywords = row["keywords"].split(',') if row["keywords"] else []
760
+ results.append((file_entry, keywords))
761
+
762
+ return results
763
+
764
+ def list_semantic_metadata(
765
+ self,
766
+ offset: int = 0,
767
+ limit: int = 50,
768
+ llm_tool: Optional[str] = None,
769
+ ) -> Tuple[List[Dict[str, Any]], int]:
770
+ """List all semantic metadata with file information.
771
+
772
+ Args:
773
+ offset: Number of records to skip (for pagination)
774
+ limit: Maximum records to return (max 100)
775
+ llm_tool: Optional filter by LLM tool used
776
+
777
+ Returns:
778
+ Tuple of (list of metadata dicts, total count)
779
+ """
780
+ with self._lock:
781
+ conn = self._get_connection()
782
+
783
+ # Query semantic metadata without keywords column
784
+ base_query = """
785
+ SELECT f.id as file_id, f.name as file_name, f.full_path,
786
+ f.language, f.line_count,
787
+ sm.summary, sm.purpose,
788
+ sm.llm_tool, sm.generated_at
789
+ FROM files f
790
+ JOIN semantic_metadata sm ON f.id = sm.file_id
791
+ """
792
+ count_query = """
793
+ SELECT COUNT(*) as total
794
+ FROM files f
795
+ JOIN semantic_metadata sm ON f.id = sm.file_id
796
+ """
797
+
798
+ params: List[Any] = []
799
+ if llm_tool:
800
+ base_query += " WHERE sm.llm_tool = ?"
801
+ count_query += " WHERE sm.llm_tool = ?"
802
+ params.append(llm_tool)
803
+
804
+ base_query += " ORDER BY sm.generated_at DESC LIMIT ? OFFSET ?"
805
+ params.extend([min(limit, 100), offset])
806
+
807
+ count_params = [llm_tool] if llm_tool else []
808
+ total_row = conn.execute(count_query, count_params).fetchone()
809
+ total = int(total_row["total"]) if total_row else 0
810
+
811
+ rows = conn.execute(base_query, params).fetchall()
812
+
813
+ results = []
814
+ for row in rows:
815
+ file_id = int(row["file_id"])
816
+
817
+ # Get keywords from normalized file_keywords table
818
+ keyword_rows = conn.execute(
819
+ """
820
+ SELECT k.keyword
821
+ FROM file_keywords fk
822
+ JOIN keywords k ON fk.keyword_id = k.id
823
+ WHERE fk.file_id = ?
824
+ ORDER BY k.keyword
825
+ """,
826
+ (file_id,),
827
+ ).fetchall()
828
+
829
+ keywords = [kw["keyword"] for kw in keyword_rows]
830
+
831
+ results.append({
832
+ "file_id": file_id,
833
+ "file_name": row["file_name"],
834
+ "full_path": row["full_path"],
835
+ "language": row["language"],
836
+ "line_count": int(row["line_count"]) if row["line_count"] else 0,
837
+ "summary": row["summary"],
838
+ "keywords": keywords,
839
+ "purpose": row["purpose"],
840
+ "llm_tool": row["llm_tool"],
841
+ "generated_at": float(row["generated_at"]) if row["generated_at"] else 0.0,
842
+ })
843
+
844
+ return results, total
845
+
846
+ # === Subdirectory Links ===
847
+
848
+ def register_subdir(
849
+ self,
850
+ name: str,
851
+ index_path: str | Path,
852
+ files_count: int = 0,
853
+ direct_files: int = 0,
854
+ ) -> None:
855
+ """Register or update a subdirectory link.
856
+
857
+ Args:
858
+ name: Subdirectory name
859
+ index_path: Path to subdirectory's _index.db
860
+ files_count: Total files recursively
861
+ direct_files: Deprecated parameter (no longer used)
862
+ """
863
+ with self._lock:
864
+ conn = self._get_connection()
865
+ index_path_str = str(Path(index_path).resolve())
866
+
867
+ import time
868
+ last_updated = time.time()
869
+
870
+ # Note: direct_files parameter is deprecated but kept for backward compatibility
871
+ conn.execute(
872
+ """
873
+ INSERT INTO subdirs(name, index_path, files_count, last_updated)
874
+ VALUES(?, ?, ?, ?)
875
+ ON CONFLICT(name) DO UPDATE SET
876
+ index_path=excluded.index_path,
877
+ files_count=excluded.files_count,
878
+ last_updated=excluded.last_updated
879
+ """,
880
+ (name, index_path_str, files_count, last_updated),
881
+ )
882
+ conn.commit()
883
+
884
+ def unregister_subdir(self, name: str) -> bool:
885
+ """Remove a subdirectory link.
886
+
887
+ Args:
888
+ name: Subdirectory name
889
+
890
+ Returns:
891
+ True if removed, False if not found
892
+ """
893
+ with self._lock:
894
+ conn = self._get_connection()
895
+ row = conn.execute("SELECT id FROM subdirs WHERE name=?", (name,)).fetchone()
896
+ if not row:
897
+ return False
898
+
899
+ conn.execute("DELETE FROM subdirs WHERE name=?", (name,))
900
+ conn.commit()
901
+ return True
902
+
903
+ def get_subdirs(self) -> List[SubdirLink]:
904
+ """Get all subdirectory links.
905
+
906
+ Returns:
907
+ List of SubdirLink objects
908
+ """
909
+ with self._lock:
910
+ conn = self._get_connection()
911
+ rows = conn.execute(
912
+ """
913
+ SELECT id, name, index_path, files_count, last_updated
914
+ FROM subdirs
915
+ ORDER BY name
916
+ """
917
+ ).fetchall()
918
+
919
+ return [
920
+ SubdirLink(
921
+ id=int(row["id"]),
922
+ name=row["name"],
923
+ index_path=Path(row["index_path"]),
924
+ files_count=int(row["files_count"]) if row["files_count"] else 0,
925
+ last_updated=float(row["last_updated"]) if row["last_updated"] else 0.0,
926
+ )
927
+ for row in rows
928
+ ]
929
+
930
+ def get_subdir(self, name: str) -> Optional[SubdirLink]:
931
+ """Get a specific subdirectory link.
932
+
933
+ Args:
934
+ name: Subdirectory name
935
+
936
+ Returns:
937
+ SubdirLink if found, None otherwise
938
+ """
939
+ with self._lock:
940
+ conn = self._get_connection()
941
+ row = conn.execute(
942
+ """
943
+ SELECT id, name, index_path, files_count, last_updated
944
+ FROM subdirs WHERE name=?
945
+ """,
946
+ (name,),
947
+ ).fetchone()
948
+
949
+ if not row:
950
+ return None
951
+
952
+ return SubdirLink(
953
+ id=int(row["id"]),
954
+ name=row["name"],
955
+ index_path=Path(row["index_path"]),
956
+ files_count=int(row["files_count"]) if row["files_count"] else 0,
957
+ last_updated=float(row["last_updated"]) if row["last_updated"] else 0.0,
958
+ )
959
+
960
+ def update_subdir_stats(
961
+ self, name: str, files_count: int, direct_files: Optional[int] = None
962
+ ) -> None:
963
+ """Update subdirectory statistics.
964
+
965
+ Args:
966
+ name: Subdirectory name
967
+ files_count: Total files recursively
968
+ direct_files: Deprecated parameter (no longer used)
969
+ """
970
+ with self._lock:
971
+ conn = self._get_connection()
972
+ import time
973
+ last_updated = time.time()
974
+
975
+ # Note: direct_files parameter is deprecated but kept for backward compatibility
976
+ conn.execute(
977
+ """
978
+ UPDATE subdirs
979
+ SET files_count=?, last_updated=?
980
+ WHERE name=?
981
+ """,
982
+ (files_count, last_updated, name),
983
+ )
984
+ conn.commit()
985
+
986
+ # === Search ===
987
+
988
+ @staticmethod
989
+ def _enhance_fts_query(query: str) -> str:
990
+ """Enhance FTS5 query to support prefix matching for simple queries.
991
+
992
+ For simple single-word or multi-word queries without FTS5 operators,
993
+ automatically adds prefix wildcard (*) to enable partial matching.
994
+
995
+ Examples:
996
+ "loadPack" -> "loadPack*"
997
+ "load package" -> "load* package*"
998
+ "load*" -> "load*" (already has wildcard, unchanged)
999
+ "NOT test" -> "NOT test" (has FTS operator, unchanged)
1000
+
1001
+ Args:
1002
+ query: Original FTS5 query string
1003
+
1004
+ Returns:
1005
+ Enhanced query string with prefix wildcards for simple queries
1006
+ """
1007
+ # Don't modify if query already contains FTS5 operators or wildcards
1008
+ if any(op in query.upper() for op in [' AND ', ' OR ', ' NOT ', ' NEAR ', '*', '"']):
1009
+ return query
1010
+
1011
+ # For simple queries, add prefix wildcard to each word
1012
+ words = query.split()
1013
+ enhanced_words = [f"{word}*" if not word.endswith('*') else word for word in words]
1014
+ return ' '.join(enhanced_words)
1015
+
1016
+ def _find_match_lines(self, content: str, query: str) -> List[int]:
1017
+ """Find line numbers where query terms match.
1018
+
1019
+ Args:
1020
+ content: File content
1021
+ query: Search query (FTS5 format)
1022
+
1023
+ Returns:
1024
+ List of 1-based line numbers containing matches
1025
+ """
1026
+ # Extract search terms from FTS query (remove operators)
1027
+ terms = re.findall(r'["\']([^"\']+)["\']|(\w+)', query)
1028
+ search_terms = [t[0] or t[1] for t in terms if t[0] or t[1]]
1029
+ # Filter out FTS operators
1030
+ fts_operators = {'AND', 'OR', 'NOT', 'NEAR'}
1031
+ search_terms = [t for t in search_terms if t.upper() not in fts_operators]
1032
+
1033
+ if not search_terms:
1034
+ return [1] # Default to first line
1035
+
1036
+ lines = content.split('\n')
1037
+ match_lines = []
1038
+
1039
+ for i, line in enumerate(lines, 1):
1040
+ line_lower = line.lower()
1041
+ for term in search_terms:
1042
+ # Handle wildcard suffix
1043
+ term_clean = term.rstrip('*').lower()
1044
+ if term_clean and term_clean in line_lower:
1045
+ match_lines.append(i)
1046
+ break
1047
+
1048
+ return match_lines if match_lines else [1]
1049
+
1050
+ def _find_containing_symbol(
1051
+ self, conn: sqlite3.Connection, file_id: int, line_num: int
1052
+ ) -> Optional[Tuple[int, int, str, str]]:
1053
+ """Find the symbol that contains the given line number.
1054
+
1055
+ Args:
1056
+ conn: Database connection
1057
+ file_id: File ID in database
1058
+ line_num: 1-based line number
1059
+
1060
+ Returns:
1061
+ Tuple of (start_line, end_line, symbol_name, symbol_kind) or None
1062
+ """
1063
+ row = conn.execute(
1064
+ """
1065
+ SELECT start_line, end_line, name, kind
1066
+ FROM symbols
1067
+ WHERE file_id = ? AND start_line <= ? AND end_line >= ?
1068
+ ORDER BY (end_line - start_line) ASC
1069
+ LIMIT 1
1070
+ """,
1071
+ (file_id, line_num, line_num),
1072
+ ).fetchone()
1073
+
1074
+ if row:
1075
+ return (row["start_line"], row["end_line"], row["name"], row["kind"])
1076
+ return None
1077
+
1078
+ def _extract_code_block(
1079
+ self,
1080
+ content: str,
1081
+ start_line: int,
1082
+ end_line: int,
1083
+ match_line: Optional[int] = None,
1084
+ context_lines: int = 5,
1085
+ ) -> Tuple[str, int, int]:
1086
+ """Extract code block from content.
1087
+
1088
+ If start_line/end_line are provided (from symbol), use them.
1089
+ Otherwise, extract context around match_line.
1090
+
1091
+ Args:
1092
+ content: Full file content
1093
+ start_line: 1-based start line (from symbol or calculated)
1094
+ end_line: 1-based end line (from symbol or calculated)
1095
+ match_line: 1-based line where match occurred (for context extraction)
1096
+ context_lines: Number of lines before/after match when no symbol
1097
+
1098
+ Returns:
1099
+ Tuple of (code_block, actual_start_line, actual_end_line)
1100
+ """
1101
+ lines = content.split('\n')
1102
+ total_lines = len(lines)
1103
+
1104
+ # Clamp to valid range
1105
+ start_line = max(1, start_line)
1106
+ end_line = min(total_lines, end_line)
1107
+
1108
+ # Extract block (convert to 0-based index)
1109
+ block_lines = lines[start_line - 1:end_line]
1110
+ block_content = '\n'.join(block_lines)
1111
+
1112
+ return block_content, start_line, end_line
1113
+
1114
+ def _batch_fetch_symbols(
1115
+ self, conn: sqlite3.Connection, file_ids: List[int]
1116
+ ) -> Dict[int, List[Tuple[int, int, str, str]]]:
1117
+ """Batch fetch all symbols for multiple files in a single query.
1118
+
1119
+ Args:
1120
+ conn: Database connection
1121
+ file_ids: List of file IDs to fetch symbols for
1122
+
1123
+ Returns:
1124
+ Dictionary mapping file_id to list of (start_line, end_line, name, kind) tuples
1125
+ """
1126
+ if not file_ids:
1127
+ return {}
1128
+
1129
+ # Build placeholder string for IN clause
1130
+ placeholders = ','.join('?' for _ in file_ids)
1131
+ rows = conn.execute(
1132
+ f"""
1133
+ SELECT file_id, start_line, end_line, name, kind
1134
+ FROM symbols
1135
+ WHERE file_id IN ({placeholders})
1136
+ ORDER BY file_id, (end_line - start_line) ASC
1137
+ """,
1138
+ file_ids,
1139
+ ).fetchall()
1140
+
1141
+ # Organize symbols by file_id
1142
+ symbols_by_file: Dict[int, List[Tuple[int, int, str, str]]] = {fid: [] for fid in file_ids}
1143
+ for row in rows:
1144
+ symbols_by_file[row["file_id"]].append(
1145
+ (row["start_line"], row["end_line"], row["name"], row["kind"])
1146
+ )
1147
+ return symbols_by_file
1148
+
1149
+ def _find_containing_symbol_from_cache(
1150
+ self, symbols: List[Tuple[int, int, str, str]], line_num: int
1151
+ ) -> Optional[Tuple[int, int, str, str]]:
1152
+ """Find the smallest symbol containing the given line number from cached symbols.
1153
+
1154
+ Args:
1155
+ symbols: List of (start_line, end_line, name, kind) tuples, sorted by size
1156
+ line_num: 1-based line number
1157
+
1158
+ Returns:
1159
+ Tuple of (start_line, end_line, symbol_name, symbol_kind) or None
1160
+ """
1161
+ for start_line, end_line, name, kind in symbols:
1162
+ if start_line <= line_num <= end_line:
1163
+ return (start_line, end_line, name, kind)
1164
+ return None
1165
+
1166
+ def _generate_centered_excerpt(
1167
+ self, content: str, match_line: int, start_line: int, end_line: int, max_chars: int = 200
1168
+ ) -> str:
1169
+ """Generate excerpt centered around the match line.
1170
+
1171
+ Args:
1172
+ content: Full file content
1173
+ match_line: 1-based line where match occurred
1174
+ start_line: 1-based start line of the code block
1175
+ end_line: 1-based end line of the code block
1176
+ max_chars: Maximum characters for excerpt
1177
+
1178
+ Returns:
1179
+ Excerpt string centered around the match
1180
+ """
1181
+ lines = content.split('\n')
1182
+ total_lines = len(lines)
1183
+
1184
+ # Ensure match_line is within bounds
1185
+ match_line = max(1, min(match_line, total_lines))
1186
+
1187
+ # Calculate context window (2 lines before, 2 lines after the match)
1188
+ ctx_start = max(start_line, match_line - 2)
1189
+ ctx_end = min(end_line, match_line + 2)
1190
+
1191
+ # Extract and join lines
1192
+ excerpt_lines = lines[ctx_start - 1:ctx_end]
1193
+ excerpt = '\n'.join(excerpt_lines)
1194
+
1195
+ # Truncate if too long
1196
+ if len(excerpt) > max_chars:
1197
+ excerpt = excerpt[:max_chars] + "..."
1198
+
1199
+ return excerpt
1200
+
1201
+ def _search_internal(
1202
+ self,
1203
+ query: str,
1204
+ fts_table: str,
1205
+ limit: int = 20,
1206
+ return_full_content: bool = False,
1207
+ context_lines: int = 10,
1208
+ ) -> List[SearchResult]:
1209
+ """Internal unified search implementation for all FTS modes.
1210
+
1211
+ Optimizations:
1212
+ - Fast path: Direct FTS query with snippet() for location-only results
1213
+ - Full content path: Batch fetch symbols to eliminate N+1 queries
1214
+ - Centered excerpt generation for better context
1215
+
1216
+ Args:
1217
+ query: FTS5 query string
1218
+ fts_table: FTS table name ('files_fts_exact' or 'files_fts_fuzzy')
1219
+ limit: Maximum results to return
1220
+ return_full_content: If True, include full code block in content field
1221
+ context_lines: Lines of context when no symbol contains the match
1222
+
1223
+ Returns:
1224
+ List of SearchResult objects
1225
+ """
1226
+ with self._lock:
1227
+ conn = self._get_connection()
1228
+
1229
+ # Fast path: location-only results (no content processing)
1230
+ if not return_full_content:
1231
+ try:
1232
+ rows = conn.execute(
1233
+ f"""
1234
+ SELECT rowid, full_path, bm25({fts_table}) AS rank,
1235
+ snippet({fts_table}, 2, '', '', '...', 30) AS excerpt
1236
+ FROM {fts_table}
1237
+ WHERE {fts_table} MATCH ?
1238
+ ORDER BY rank
1239
+ LIMIT ?
1240
+ """,
1241
+ (query, limit),
1242
+ ).fetchall()
1243
+ except sqlite3.DatabaseError as exc:
1244
+ raise StorageError(f"FTS search failed: {exc}") from exc
1245
+
1246
+ results: List[SearchResult] = []
1247
+ for row in rows:
1248
+ rank = float(row["rank"]) if row["rank"] is not None else 0.0
1249
+ score = abs(rank) if rank < 0 else 0.0
1250
+ results.append(
1251
+ SearchResult(
1252
+ path=row["full_path"],
1253
+ score=score,
1254
+ excerpt=row["excerpt"],
1255
+ )
1256
+ )
1257
+ return results
1258
+
1259
+ # Full content path with batch optimization
1260
+ # Step 1: Get file_ids and ranks (lightweight query)
1261
+ try:
1262
+ id_rows = conn.execute(
1263
+ f"""
1264
+ SELECT rowid AS file_id, bm25({fts_table}) AS rank
1265
+ FROM {fts_table}
1266
+ WHERE {fts_table} MATCH ?
1267
+ ORDER BY rank
1268
+ LIMIT ?
1269
+ """,
1270
+ (query, limit),
1271
+ ).fetchall()
1272
+ except sqlite3.DatabaseError as exc:
1273
+ raise StorageError(f"FTS search failed: {exc}") from exc
1274
+
1275
+ if not id_rows:
1276
+ return []
1277
+
1278
+ file_ids = [row["file_id"] for row in id_rows]
1279
+ ranks_by_id = {row["file_id"]: row["rank"] for row in id_rows}
1280
+
1281
+ # Step 2: Batch fetch all symbols for matched files (eliminates N+1)
1282
+ symbols_by_file = self._batch_fetch_symbols(conn, file_ids)
1283
+
1284
+ # Step 3: Process each file on-demand (reduces memory)
1285
+ results: List[SearchResult] = []
1286
+ for file_id in file_ids:
1287
+ # Fetch file content on-demand
1288
+ file_row = conn.execute(
1289
+ "SELECT full_path, content FROM files WHERE id = ?",
1290
+ (file_id,),
1291
+ ).fetchone()
1292
+
1293
+ if not file_row:
1294
+ continue
1295
+
1296
+ file_path = file_row["full_path"]
1297
+ content = file_row["content"] or ""
1298
+ rank = ranks_by_id.get(file_id, 0.0)
1299
+ score = abs(rank) if rank < 0 else 0.0
1300
+
1301
+ # Find matching lines
1302
+ match_lines = self._find_match_lines(content, query)
1303
+ first_match_line = match_lines[0] if match_lines else 1
1304
+
1305
+ # Find symbol from cached symbols (no extra SQL query)
1306
+ file_symbols = symbols_by_file.get(file_id, [])
1307
+ symbol_info = self._find_containing_symbol_from_cache(file_symbols, first_match_line)
1308
+
1309
+ if symbol_info:
1310
+ start_line, end_line, symbol_name, symbol_kind = symbol_info
1311
+ else:
1312
+ # No symbol found, use context around match
1313
+ lines = content.split('\n')
1314
+ total_lines = len(lines)
1315
+ start_line = max(1, first_match_line - context_lines)
1316
+ end_line = min(total_lines, first_match_line + context_lines)
1317
+ symbol_name = None
1318
+ symbol_kind = None
1319
+
1320
+ # Extract code block
1321
+ block_content, start_line, end_line = self._extract_code_block(
1322
+ content, start_line, end_line
1323
+ )
1324
+
1325
+ # Generate centered excerpt (improved quality)
1326
+ excerpt = self._generate_centered_excerpt(
1327
+ content, first_match_line, start_line, end_line
1328
+ )
1329
+
1330
+ results.append(
1331
+ SearchResult(
1332
+ path=file_path,
1333
+ score=score,
1334
+ excerpt=excerpt,
1335
+ content=block_content,
1336
+ start_line=start_line,
1337
+ end_line=end_line,
1338
+ symbol_name=symbol_name,
1339
+ symbol_kind=symbol_kind,
1340
+ )
1341
+ )
1342
+ return results
1343
+
1344
+
1345
+ def search_fts(
1346
+ self,
1347
+ query: str,
1348
+ limit: int = 20,
1349
+ enhance_query: bool = False,
1350
+ return_full_content: bool = False,
1351
+ context_lines: int = 10,
1352
+ ) -> List[SearchResult]:
1353
+ """Full-text search in current directory files.
1354
+
1355
+ Uses files_fts_exact (unicode61 tokenizer) for exact token matching.
1356
+ For fuzzy/substring search, use search_fts_fuzzy() instead.
1357
+
1358
+ Best Practice (from industry analysis of Codanna/Code-Index-MCP):
1359
+ - Default: Respects exact user input without modification
1360
+ - Users can manually add wildcards (e.g., "loadPack*") for prefix matching
1361
+ - Automatic enhancement (enhance_query=True) is NOT recommended as it can
1362
+ violate user intent and bring unwanted noise in results
1363
+
1364
+ Args:
1365
+ query: FTS5 query string
1366
+ limit: Maximum results to return
1367
+ enhance_query: If True, automatically add prefix wildcards for simple queries.
1368
+ Default False to respect exact user input.
1369
+ return_full_content: If True, include full code block in content field.
1370
+ Default False for fast location-only results.
1371
+ context_lines: Lines of context when no symbol contains the match
1372
+
1373
+ Returns:
1374
+ List of SearchResult objects (location-only by default, with content if requested)
1375
+
1376
+ Raises:
1377
+ StorageError: If FTS search fails
1378
+ """
1379
+ final_query = self._enhance_fts_query(query) if enhance_query else query
1380
+ return self._search_internal(
1381
+ query=final_query,
1382
+ fts_table='files_fts_exact',
1383
+ limit=limit,
1384
+ return_full_content=return_full_content,
1385
+ context_lines=context_lines,
1386
+ )
1387
+
1388
+ def search_fts_exact(
1389
+ self,
1390
+ query: str,
1391
+ limit: int = 20,
1392
+ return_full_content: bool = False,
1393
+ context_lines: int = 10,
1394
+ ) -> List[SearchResult]:
1395
+ """Full-text search using exact token matching.
1396
+
1397
+ Args:
1398
+ query: FTS5 query string
1399
+ limit: Maximum results to return
1400
+ return_full_content: If True, include full code block in content field.
1401
+ Default False for fast location-only results.
1402
+ context_lines: Lines of context when no symbol contains the match
1403
+
1404
+ Returns:
1405
+ List of SearchResult objects (location-only by default, with content if requested)
1406
+
1407
+ Raises:
1408
+ StorageError: If FTS search fails
1409
+ """
1410
+ return self._search_internal(
1411
+ query=query,
1412
+ fts_table='files_fts_exact',
1413
+ limit=limit,
1414
+ return_full_content=return_full_content,
1415
+ context_lines=context_lines,
1416
+ )
1417
+
1418
+ def search_fts_fuzzy(
1419
+ self,
1420
+ query: str,
1421
+ limit: int = 20,
1422
+ return_full_content: bool = False,
1423
+ context_lines: int = 10,
1424
+ ) -> List[SearchResult]:
1425
+ """Full-text search using fuzzy/substring matching.
1426
+
1427
+ Args:
1428
+ query: FTS5 query string
1429
+ limit: Maximum results to return
1430
+ return_full_content: If True, include full code block in content field.
1431
+ Default False for fast location-only results.
1432
+ context_lines: Lines of context when no symbol contains the match
1433
+
1434
+ Returns:
1435
+ List of SearchResult objects (location-only by default, with content if requested)
1436
+
1437
+ Raises:
1438
+ StorageError: If FTS search fails
1439
+ """
1440
+ return self._search_internal(
1441
+ query=query,
1442
+ fts_table='files_fts_fuzzy',
1443
+ limit=limit,
1444
+ return_full_content=return_full_content,
1445
+ context_lines=context_lines,
1446
+ )
1447
+
1448
+ def search_files_only(self, query: str, limit: int = 20) -> List[str]:
1449
+ """Fast FTS search returning only file paths (no snippet generation).
1450
+
1451
+ Optimized for when only file paths are needed, skipping expensive
1452
+ snippet() function call.
1453
+
1454
+ Args:
1455
+ query: FTS5 query string
1456
+ limit: Maximum results to return
1457
+
1458
+ Returns:
1459
+ List of file paths as strings
1460
+
1461
+ Raises:
1462
+ StorageError: If FTS search fails
1463
+ """
1464
+ with self._lock:
1465
+ conn = self._get_connection()
1466
+ try:
1467
+ rows = conn.execute(
1468
+ """
1469
+ SELECT full_path
1470
+ FROM files_fts
1471
+ WHERE files_fts MATCH ?
1472
+ ORDER BY bm25(files_fts)
1473
+ LIMIT ?
1474
+ """,
1475
+ (query, limit),
1476
+ ).fetchall()
1477
+ except sqlite3.DatabaseError as exc:
1478
+ raise StorageError(f"FTS search failed: {exc}") from exc
1479
+
1480
+ return [row["full_path"] for row in rows]
1481
+
1482
+ def search_symbols(
1483
+ self, name: str, kind: Optional[str] = None, limit: int = 50, prefix_mode: bool = True
1484
+ ) -> List[Symbol]:
1485
+ """Search symbols by name pattern.
1486
+
1487
+ Args:
1488
+ name: Symbol name pattern
1489
+ kind: Optional symbol kind filter
1490
+ limit: Maximum results to return
1491
+ prefix_mode: If True, use prefix search (faster with index);
1492
+ If False, use substring search (slower)
1493
+
1494
+ Returns:
1495
+ List of Symbol objects
1496
+ """
1497
+ # Prefix search is much faster as it can use index
1498
+ if prefix_mode:
1499
+ pattern = f"{name}%"
1500
+ else:
1501
+ pattern = f"%{name}%"
1502
+
1503
+ with self._lock:
1504
+ conn = self._get_connection()
1505
+ if kind:
1506
+ rows = conn.execute(
1507
+ """
1508
+ SELECT s.name, s.kind, s.start_line, s.end_line, f.full_path
1509
+ FROM symbols s
1510
+ JOIN files f ON s.file_id = f.id
1511
+ WHERE s.name LIKE ? AND s.kind=?
1512
+ ORDER BY s.name
1513
+ LIMIT ?
1514
+ """,
1515
+ (pattern, kind, limit),
1516
+ ).fetchall()
1517
+ else:
1518
+ rows = conn.execute(
1519
+ """
1520
+ SELECT s.name, s.kind, s.start_line, s.end_line, f.full_path
1521
+ FROM symbols s
1522
+ JOIN files f ON s.file_id = f.id
1523
+ WHERE s.name LIKE ?
1524
+ ORDER BY s.name
1525
+ LIMIT ?
1526
+ """,
1527
+ (pattern, limit),
1528
+ ).fetchall()
1529
+
1530
+ return [
1531
+ Symbol(
1532
+ name=row["name"],
1533
+ kind=row["kind"],
1534
+ range=(row["start_line"], row["end_line"]),
1535
+ file=row["full_path"],
1536
+ )
1537
+ for row in rows
1538
+ ]
1539
+
1540
+ # === Statistics ===
1541
+
1542
+ def stats(self) -> Dict[str, Any]:
1543
+ """Get current directory statistics.
1544
+
1545
+ Returns:
1546
+ Dictionary containing:
1547
+ - files: Number of files in this directory
1548
+ - symbols: Number of symbols
1549
+ - subdirs: Number of subdirectories
1550
+ - total_files: Total files including subdirectories
1551
+ - languages: Dictionary of language counts
1552
+ """
1553
+ with self._lock:
1554
+ conn = self._get_connection()
1555
+
1556
+ file_count = conn.execute("SELECT COUNT(*) AS c FROM files").fetchone()["c"]
1557
+ symbol_count = conn.execute("SELECT COUNT(*) AS c FROM symbols").fetchone()["c"]
1558
+ subdir_count = conn.execute("SELECT COUNT(*) AS c FROM subdirs").fetchone()["c"]
1559
+
1560
+ total_files_row = conn.execute(
1561
+ "SELECT COALESCE(SUM(files_count), 0) AS total FROM subdirs"
1562
+ ).fetchone()
1563
+ total_files = int(file_count) + int(total_files_row["total"] if total_files_row else 0)
1564
+
1565
+ lang_rows = conn.execute(
1566
+ "SELECT language, COUNT(*) AS c FROM files GROUP BY language ORDER BY c DESC"
1567
+ ).fetchall()
1568
+ languages = {row["language"]: int(row["c"]) for row in lang_rows}
1569
+
1570
+ return {
1571
+ "files": int(file_count),
1572
+ "symbols": int(symbol_count),
1573
+ "subdirs": int(subdir_count),
1574
+ "total_files": total_files,
1575
+ "languages": languages,
1576
+ }
1577
+
1578
+ # === Internal Methods ===
1579
+
1580
+ def _get_connection(self) -> sqlite3.Connection:
1581
+ """Get or create database connection with proper configuration.
1582
+
1583
+ Returns:
1584
+ sqlite3.Connection with WAL mode and foreign keys enabled
1585
+ """
1586
+ if self._conn is None:
1587
+ self._conn = sqlite3.connect(str(self.db_path), check_same_thread=False)
1588
+ self._conn.row_factory = sqlite3.Row
1589
+ self._conn.execute("PRAGMA journal_mode=WAL")
1590
+ self._conn.execute("PRAGMA synchronous=NORMAL")
1591
+ self._conn.execute("PRAGMA foreign_keys=ON")
1592
+ # Memory-mapped I/O for faster reads (30GB limit)
1593
+ self._conn.execute("PRAGMA mmap_size=30000000000")
1594
+ return self._conn
1595
+
1596
+ def _create_schema(self, conn: sqlite3.Connection) -> None:
1597
+ """Create database schema.
1598
+
1599
+ Args:
1600
+ conn: Database connection
1601
+
1602
+ Raises:
1603
+ StorageError: If schema creation fails
1604
+ """
1605
+ try:
1606
+ # Files table
1607
+ conn.execute(
1608
+ """
1609
+ CREATE TABLE IF NOT EXISTS files (
1610
+ id INTEGER PRIMARY KEY,
1611
+ name TEXT NOT NULL,
1612
+ full_path TEXT UNIQUE NOT NULL,
1613
+ language TEXT,
1614
+ content TEXT,
1615
+ mtime REAL,
1616
+ line_count INTEGER
1617
+ )
1618
+ """
1619
+ )
1620
+
1621
+ # Subdirectories table (v5: removed direct_files)
1622
+ conn.execute(
1623
+ """
1624
+ CREATE TABLE IF NOT EXISTS subdirs (
1625
+ id INTEGER PRIMARY KEY,
1626
+ name TEXT NOT NULL UNIQUE,
1627
+ index_path TEXT NOT NULL,
1628
+ files_count INTEGER DEFAULT 0,
1629
+ last_updated REAL
1630
+ )
1631
+ """
1632
+ )
1633
+
1634
+ # Symbols table with token metadata
1635
+ conn.execute(
1636
+ """
1637
+ CREATE TABLE IF NOT EXISTS symbols (
1638
+ id INTEGER PRIMARY KEY,
1639
+ file_id INTEGER REFERENCES files(id) ON DELETE CASCADE,
1640
+ name TEXT NOT NULL,
1641
+ kind TEXT NOT NULL,
1642
+ start_line INTEGER,
1643
+ end_line INTEGER
1644
+ )
1645
+ """
1646
+ )
1647
+
1648
+ # Dual FTS5 external content tables for exact and fuzzy matching
1649
+ # files_fts_exact: unicode61 tokenizer for exact token matching
1650
+ # files_fts_fuzzy: trigram tokenizer (or extended unicode61) for substring/fuzzy matching
1651
+ from codexlens.storage.sqlite_utils import check_trigram_support
1652
+
1653
+ has_trigram = check_trigram_support(conn)
1654
+ fuzzy_tokenizer = "trigram" if has_trigram else "unicode61 tokenchars '_-.'"
1655
+
1656
+ # Exact FTS table with unicode61 tokenizer
1657
+ # Note: tokenchars includes '.' to properly tokenize qualified names like PortRole.FLOW
1658
+ conn.execute(
1659
+ """
1660
+ CREATE VIRTUAL TABLE IF NOT EXISTS files_fts_exact USING fts5(
1661
+ name, full_path UNINDEXED, content,
1662
+ content='files',
1663
+ content_rowid='id',
1664
+ tokenize="unicode61 tokenchars '_-.'"
1665
+ )
1666
+ """
1667
+ )
1668
+
1669
+ # Fuzzy FTS table with trigram or extended unicode61 tokenizer
1670
+ conn.execute(
1671
+ f"""
1672
+ CREATE VIRTUAL TABLE IF NOT EXISTS files_fts_fuzzy USING fts5(
1673
+ name, full_path UNINDEXED, content,
1674
+ content='files',
1675
+ content_rowid='id',
1676
+ tokenize="{fuzzy_tokenizer}"
1677
+ )
1678
+ """
1679
+ )
1680
+
1681
+ # Semantic metadata table (v5: removed keywords column)
1682
+ conn.execute(
1683
+ """
1684
+ CREATE TABLE IF NOT EXISTS semantic_metadata (
1685
+ id INTEGER PRIMARY KEY,
1686
+ file_id INTEGER UNIQUE REFERENCES files(id) ON DELETE CASCADE,
1687
+ summary TEXT,
1688
+ purpose TEXT,
1689
+ llm_tool TEXT,
1690
+ generated_at REAL
1691
+ )
1692
+ """
1693
+ )
1694
+
1695
+ # Normalized keywords tables for performance
1696
+ conn.execute(
1697
+ """
1698
+ CREATE TABLE IF NOT EXISTS keywords (
1699
+ id INTEGER PRIMARY KEY,
1700
+ keyword TEXT NOT NULL UNIQUE
1701
+ )
1702
+ """
1703
+ )
1704
+
1705
+ conn.execute(
1706
+ """
1707
+ CREATE TABLE IF NOT EXISTS file_keywords (
1708
+ file_id INTEGER NOT NULL,
1709
+ keyword_id INTEGER NOT NULL,
1710
+ PRIMARY KEY (file_id, keyword_id),
1711
+ FOREIGN KEY (file_id) REFERENCES files (id) ON DELETE CASCADE,
1712
+ FOREIGN KEY (keyword_id) REFERENCES keywords (id) ON DELETE CASCADE
1713
+ )
1714
+ """
1715
+ )
1716
+
1717
+ # Code relationships table for graph visualization
1718
+ conn.execute(
1719
+ """
1720
+ CREATE TABLE IF NOT EXISTS code_relationships (
1721
+ id INTEGER PRIMARY KEY,
1722
+ source_symbol_id INTEGER NOT NULL,
1723
+ target_qualified_name TEXT NOT NULL,
1724
+ relationship_type TEXT NOT NULL,
1725
+ source_line INTEGER NOT NULL,
1726
+ target_file TEXT,
1727
+ FOREIGN KEY (source_symbol_id) REFERENCES symbols (id) ON DELETE CASCADE
1728
+ )
1729
+ """
1730
+ )
1731
+
1732
+ # Indexes (v5: removed idx_symbols_type)
1733
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_files_name ON files(name)")
1734
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_files_path ON files(full_path)")
1735
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_subdirs_name ON subdirs(name)")
1736
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)")
1737
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_id)")
1738
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_semantic_file ON semantic_metadata(file_id)")
1739
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_keywords_keyword ON keywords(keyword)")
1740
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_file_keywords_file_id ON file_keywords(file_id)")
1741
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_file_keywords_keyword_id ON file_keywords(keyword_id)")
1742
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_rel_source ON code_relationships(source_symbol_id)")
1743
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_rel_target ON code_relationships(target_qualified_name)")
1744
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_rel_type ON code_relationships(relationship_type)")
1745
+
1746
+ except sqlite3.DatabaseError as exc:
1747
+ raise StorageError(f"Failed to create schema: {exc}") from exc
1748
+
1749
+ def _migrate_v2_add_name_column(self, conn: sqlite3.Connection) -> None:
1750
+ """Migration v2: Add 'name' column to files table.
1751
+
1752
+ Required for FTS5 external content table.
1753
+
1754
+ Args:
1755
+ conn: Database connection
1756
+ """
1757
+ # Check if files table exists and has columns
1758
+ cursor = conn.execute("PRAGMA table_info(files)")
1759
+ files_columns = {row[1] for row in cursor.fetchall()}
1760
+
1761
+ if not files_columns:
1762
+ return # No files table yet, will be created fresh
1763
+
1764
+ # Skip if 'name' column already exists
1765
+ if "name" in files_columns:
1766
+ return
1767
+
1768
+ # Add 'name' column with default value
1769
+ conn.execute("ALTER TABLE files ADD COLUMN name TEXT NOT NULL DEFAULT ''")
1770
+
1771
+ # Populate 'name' column from full_path using pathlib for robustness
1772
+ rows = conn.execute("SELECT id, full_path FROM files WHERE name = ''").fetchall()
1773
+ for row in rows:
1774
+ file_id = row[0]
1775
+ full_path = row[1]
1776
+ # Use pathlib.Path.name for cross-platform compatibility
1777
+ name = Path(full_path).name if full_path else ""
1778
+ conn.execute("UPDATE files SET name = ? WHERE id = ?", (name, file_id))
1779
+
1780
+ def _create_fts_triggers(self, conn: sqlite3.Connection) -> None:
1781
+ """Create FTS5 external content triggers for dual FTS tables.
1782
+
1783
+ Creates synchronized triggers for both files_fts_exact and files_fts_fuzzy tables.
1784
+
1785
+ Args:
1786
+ conn: Database connection
1787
+ """
1788
+ # Insert triggers for files_fts_exact
1789
+ conn.execute(
1790
+ """
1791
+ CREATE TRIGGER IF NOT EXISTS files_exact_ai AFTER INSERT ON files BEGIN
1792
+ INSERT INTO files_fts_exact(rowid, name, full_path, content)
1793
+ VALUES(new.id, new.name, new.full_path, new.content);
1794
+ END
1795
+ """
1796
+ )
1797
+
1798
+ # Delete trigger for files_fts_exact
1799
+ conn.execute(
1800
+ """
1801
+ CREATE TRIGGER IF NOT EXISTS files_exact_ad AFTER DELETE ON files BEGIN
1802
+ INSERT INTO files_fts_exact(files_fts_exact, rowid, name, full_path, content)
1803
+ VALUES('delete', old.id, old.name, old.full_path, old.content);
1804
+ END
1805
+ """
1806
+ )
1807
+
1808
+ # Update trigger for files_fts_exact
1809
+ conn.execute(
1810
+ """
1811
+ CREATE TRIGGER IF NOT EXISTS files_exact_au AFTER UPDATE ON files BEGIN
1812
+ INSERT INTO files_fts_exact(files_fts_exact, rowid, name, full_path, content)
1813
+ VALUES('delete', old.id, old.name, old.full_path, old.content);
1814
+ INSERT INTO files_fts_exact(rowid, name, full_path, content)
1815
+ VALUES(new.id, new.name, new.full_path, new.content);
1816
+ END
1817
+ """
1818
+ )
1819
+
1820
+ # Insert trigger for files_fts_fuzzy
1821
+ conn.execute(
1822
+ """
1823
+ CREATE TRIGGER IF NOT EXISTS files_fuzzy_ai AFTER INSERT ON files BEGIN
1824
+ INSERT INTO files_fts_fuzzy(rowid, name, full_path, content)
1825
+ VALUES(new.id, new.name, new.full_path, new.content);
1826
+ END
1827
+ """
1828
+ )
1829
+
1830
+ # Delete trigger for files_fts_fuzzy
1831
+ conn.execute(
1832
+ """
1833
+ CREATE TRIGGER IF NOT EXISTS files_fuzzy_ad AFTER DELETE ON files BEGIN
1834
+ INSERT INTO files_fts_fuzzy(files_fts_fuzzy, rowid, name, full_path, content)
1835
+ VALUES('delete', old.id, old.name, old.full_path, old.content);
1836
+ END
1837
+ """
1838
+ )
1839
+
1840
+ # Update trigger for files_fts_fuzzy
1841
+ conn.execute(
1842
+ """
1843
+ CREATE TRIGGER IF NOT EXISTS files_fuzzy_au AFTER UPDATE ON files BEGIN
1844
+ INSERT INTO files_fts_fuzzy(files_fts_fuzzy, rowid, name, full_path, content)
1845
+ VALUES('delete', old.id, old.name, old.full_path, old.content);
1846
+ INSERT INTO files_fts_fuzzy(rowid, name, full_path, content)
1847
+ VALUES(new.id, new.name, new.full_path, new.content);
1848
+ END
1849
+ """
1850
+ )