claude-code-workflow 6.2.1 → 6.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +10 -0
- package/.claude/agents/action-planning-agent.md +857 -0
- package/.claude/agents/cli-execution-agent.md +267 -0
- package/.claude/agents/cli-explore-agent.md +182 -0
- package/.claude/agents/cli-lite-planning-agent.md +446 -0
- package/.claude/agents/cli-planning-agent.md +558 -0
- package/.claude/agents/code-developer.md +311 -0
- package/.claude/agents/conceptual-planning-agent.md +308 -0
- package/.claude/agents/context-search-agent.md +581 -0
- package/.claude/agents/doc-generator.md +330 -0
- package/.claude/agents/memory-bridge.md +94 -0
- package/.claude/agents/test-context-search-agent.md +400 -0
- package/.claude/agents/test-fix-agent.md +344 -0
- package/.claude/agents/ui-design-agent.md +593 -0
- package/.claude/agents/universal-executor.md +131 -0
- package/.claude/commands/clean.md +516 -0
- package/.claude/commands/cli/cli-init.md +440 -0
- package/.claude/commands/enhance-prompt.md +93 -0
- package/.claude/commands/memory/code-map-memory.md +687 -0
- package/.claude/commands/memory/compact.md +383 -0
- package/.claude/commands/memory/docs-full-cli.md +471 -0
- package/.claude/commands/memory/docs-related-cli.md +386 -0
- package/.claude/commands/memory/docs.md +615 -0
- package/.claude/commands/memory/load-skill-memory.md +182 -0
- package/.claude/commands/memory/load.md +240 -0
- package/.claude/commands/memory/skill-memory.md +525 -0
- package/.claude/commands/memory/style-skill-memory.md +396 -0
- package/.claude/commands/memory/tech-research-rules.md +310 -0
- package/.claude/commands/memory/update-full.md +332 -0
- package/.claude/commands/memory/update-related.md +332 -0
- package/.claude/commands/memory/workflow-skill-memory.md +517 -0
- package/.claude/commands/task/breakdown.md +204 -0
- package/.claude/commands/task/create.md +152 -0
- package/.claude/commands/task/execute.md +270 -0
- package/.claude/commands/task/replan.md +437 -0
- package/.claude/commands/version.md +254 -0
- package/.claude/commands/workflow/action-plan-verify.md +447 -0
- package/.claude/commands/workflow/brainstorm/api-designer.md +587 -0
- package/.claude/commands/workflow/brainstorm/artifacts.md +453 -0
- package/.claude/commands/workflow/brainstorm/auto-parallel.md +443 -0
- package/.claude/commands/workflow/brainstorm/data-architect.md +220 -0
- package/.claude/commands/workflow/brainstorm/product-manager.md +200 -0
- package/.claude/commands/workflow/brainstorm/product-owner.md +200 -0
- package/.claude/commands/workflow/brainstorm/scrum-master.md +200 -0
- package/.claude/commands/workflow/brainstorm/subject-matter-expert.md +200 -0
- package/.claude/commands/workflow/brainstorm/synthesis.md +398 -0
- package/.claude/commands/workflow/brainstorm/system-architect.md +389 -0
- package/.claude/commands/workflow/brainstorm/ui-designer.md +221 -0
- package/.claude/commands/workflow/brainstorm/ux-expert.md +221 -0
- package/.claude/commands/workflow/debug.md +321 -0
- package/.claude/commands/workflow/execute.md +475 -0
- package/.claude/commands/workflow/init.md +165 -0
- package/.claude/commands/workflow/lite-execute.md +792 -0
- package/.claude/commands/workflow/lite-fix.md +623 -0
- package/.claude/commands/workflow/lite-plan.md +610 -0
- package/.claude/commands/workflow/plan.md +551 -0
- package/.claude/commands/workflow/replan.md +515 -0
- package/.claude/commands/workflow/review-fix.md +606 -0
- package/.claude/commands/workflow/review-module-cycle.md +767 -0
- package/.claude/commands/workflow/review-session-cycle.md +778 -0
- package/.claude/commands/workflow/review.md +297 -0
- package/.claude/commands/workflow/session/complete.md +153 -0
- package/.claude/commands/workflow/session/list.md +96 -0
- package/.claude/commands/workflow/session/resume.md +61 -0
- package/.claude/commands/workflow/session/start.md +200 -0
- package/.claude/commands/workflow/tdd-plan.md +460 -0
- package/.claude/commands/workflow/tdd-verify.md +400 -0
- package/.claude/commands/workflow/test-cycle-execute.md +500 -0
- package/.claude/commands/workflow/test-fix-gen.md +699 -0
- package/.claude/commands/workflow/test-gen.md +529 -0
- package/.claude/commands/workflow/tools/conflict-resolution.md +766 -0
- package/.claude/commands/workflow/tools/context-gather.md +436 -0
- package/.claude/commands/workflow/tools/task-generate-agent.md +490 -0
- package/.claude/commands/workflow/tools/task-generate-tdd.md +526 -0
- package/.claude/commands/workflow/tools/tdd-coverage-analysis.md +309 -0
- package/.claude/commands/workflow/tools/test-concept-enhanced.md +164 -0
- package/.claude/commands/workflow/tools/test-context-gather.md +236 -0
- package/.claude/commands/workflow/tools/test-task-generate.md +257 -0
- package/.claude/commands/workflow/ui-design/animation-extract.md +1150 -0
- package/.claude/commands/workflow/ui-design/codify-style.md +652 -0
- package/.claude/commands/workflow/ui-design/design-sync.md +454 -0
- package/.claude/commands/workflow/ui-design/explore-auto.md +678 -0
- package/.claude/commands/workflow/ui-design/generate.md +504 -0
- package/.claude/commands/workflow/ui-design/imitate-auto.md +745 -0
- package/.claude/commands/workflow/ui-design/import-from-code.md +540 -0
- package/.claude/commands/workflow/ui-design/layout-extract.md +788 -0
- package/.claude/commands/workflow/ui-design/reference-page-generator.md +356 -0
- package/.claude/commands/workflow/ui-design/style-extract.md +773 -0
- package/.claude/skills/command-guide/SKILL.md +388 -0
- package/.claude/skills/command-guide/UPDATE-GUIDELINE.md +592 -0
- package/.claude/skills/command-guide/guides/cli-tools-guide.md +410 -0
- package/.claude/skills/command-guide/guides/examples.md +537 -0
- package/.claude/skills/command-guide/guides/getting-started.md +242 -0
- package/.claude/skills/command-guide/guides/implementation-details.md +1010 -0
- package/.claude/skills/command-guide/guides/index-structure.md +326 -0
- package/.claude/skills/command-guide/guides/troubleshooting.md +92 -0
- package/.claude/skills/command-guide/guides/ui-design-workflow-guide.md +316 -0
- package/.claude/skills/command-guide/guides/workflow-patterns.md +662 -0
- package/.claude/skills/command-guide/index/all-commands.json +772 -0
- package/.claude/skills/command-guide/index/by-category.json +800 -0
- package/.claude/skills/command-guide/index/by-use-case.json +786 -0
- package/.claude/skills/command-guide/index/command-relationships.json +307 -0
- package/.claude/skills/command-guide/index/essential-commands.json +112 -0
- package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +857 -0
- package/.claude/skills/command-guide/reference/agents/cli-execution-agent.md +267 -0
- package/.claude/skills/command-guide/reference/agents/cli-explore-agent.md +182 -0
- package/.claude/skills/command-guide/reference/agents/cli-lite-planning-agent.md +446 -0
- package/.claude/skills/command-guide/reference/agents/cli-planning-agent.md +558 -0
- package/.claude/skills/command-guide/reference/agents/code-developer.md +311 -0
- package/.claude/skills/command-guide/reference/agents/conceptual-planning-agent.md +308 -0
- package/.claude/skills/command-guide/reference/agents/context-search-agent.md +581 -0
- package/.claude/skills/command-guide/reference/agents/doc-generator.md +330 -0
- package/.claude/skills/command-guide/reference/agents/memory-bridge.md +94 -0
- package/.claude/skills/command-guide/reference/agents/test-context-search-agent.md +400 -0
- package/.claude/skills/command-guide/reference/agents/test-fix-agent.md +344 -0
- package/.claude/skills/command-guide/reference/agents/ui-design-agent.md +593 -0
- package/.claude/skills/command-guide/reference/agents/universal-executor.md +131 -0
- package/.claude/skills/command-guide/reference/commands/cli/cli-init.md +440 -0
- package/.claude/skills/command-guide/reference/commands/enhance-prompt.md +93 -0
- package/.claude/skills/command-guide/reference/commands/memory/code-map-memory.md +687 -0
- package/.claude/skills/command-guide/reference/commands/memory/docs-full-cli.md +471 -0
- package/.claude/skills/command-guide/reference/commands/memory/docs-related-cli.md +386 -0
- package/.claude/skills/command-guide/reference/commands/memory/docs.md +616 -0
- package/.claude/skills/command-guide/reference/commands/memory/load-skill-memory.md +182 -0
- package/.claude/skills/command-guide/reference/commands/memory/load.md +240 -0
- package/.claude/skills/command-guide/reference/commands/memory/skill-memory.md +525 -0
- package/.claude/skills/command-guide/reference/commands/memory/style-skill-memory.md +396 -0
- package/.claude/skills/command-guide/reference/commands/memory/tech-research.md +314 -0
- package/.claude/skills/command-guide/reference/commands/memory/update-full.md +332 -0
- package/.claude/skills/command-guide/reference/commands/memory/update-related.md +332 -0
- package/.claude/skills/command-guide/reference/commands/memory/workflow-skill-memory.md +517 -0
- package/.claude/skills/command-guide/reference/commands/task/breakdown.md +204 -0
- package/.claude/skills/command-guide/reference/commands/task/create.md +152 -0
- package/.claude/skills/command-guide/reference/commands/task/execute.md +270 -0
- package/.claude/skills/command-guide/reference/commands/task/replan.md +437 -0
- package/.claude/skills/command-guide/reference/commands/version.md +254 -0
- package/.claude/skills/command-guide/reference/commands/workflow/action-plan-verify.md +447 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/api-designer.md +585 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/artifacts.md +452 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/auto-parallel.md +443 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/data-architect.md +220 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/product-manager.md +200 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/product-owner.md +200 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/scrum-master.md +200 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/subject-matter-expert.md +200 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/synthesis.md +398 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/system-architect.md +387 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/ui-designer.md +221 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/ux-expert.md +221 -0
- package/.claude/skills/command-guide/reference/commands/workflow/execute.md +465 -0
- package/.claude/skills/command-guide/reference/commands/workflow/init.md +164 -0
- package/.claude/skills/command-guide/reference/commands/workflow/lite-execute.md +748 -0
- package/.claude/skills/command-guide/reference/commands/workflow/lite-fix.md +664 -0
- package/.claude/skills/command-guide/reference/commands/workflow/lite-plan.md +645 -0
- package/.claude/skills/command-guide/reference/commands/workflow/plan.md +551 -0
- package/.claude/skills/command-guide/reference/commands/workflow/replan.md +515 -0
- package/.claude/skills/command-guide/reference/commands/workflow/review-fix.md +606 -0
- package/.claude/skills/command-guide/reference/commands/workflow/review-module-cycle.md +765 -0
- package/.claude/skills/command-guide/reference/commands/workflow/review-session-cycle.md +776 -0
- package/.claude/skills/command-guide/reference/commands/workflow/review.md +298 -0
- package/.claude/skills/command-guide/reference/commands/workflow/session/complete.md +547 -0
- package/.claude/skills/command-guide/reference/commands/workflow/session/list.md +114 -0
- package/.claude/skills/command-guide/reference/commands/workflow/session/resume.md +77 -0
- package/.claude/skills/command-guide/reference/commands/workflow/session/start.md +257 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tdd-plan.md +460 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tdd-verify.md +400 -0
- package/.claude/skills/command-guide/reference/commands/workflow/test-cycle-execute.md +498 -0
- package/.claude/skills/command-guide/reference/commands/workflow/test-fix-gen.md +699 -0
- package/.claude/skills/command-guide/reference/commands/workflow/test-gen.md +529 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/conflict-resolution.md +766 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +434 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-agent.md +487 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +525 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/tdd-coverage-analysis.md +309 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/test-concept-enhanced.md +163 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/test-context-gather.md +235 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/test-task-generate.md +256 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/animation-extract.md +1150 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/codify-style.md +652 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/design-sync.md +454 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/explore-auto.md +678 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/generate.md +504 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/imitate-auto.md +745 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/import-from-code.md +537 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/layout-extract.md +788 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/reference-page-generator.md +356 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/style-extract.md +773 -0
- package/.claude/skills/command-guide/scripts/analyze_commands.py +502 -0
- package/.claude/skills/command-guide/scripts/update-index.sh +130 -0
- package/.claude/skills/command-guide/templates/issue-bug.md +104 -0
- package/.claude/skills/command-guide/templates/issue-diagnosis.md +275 -0
- package/.claude/skills/command-guide/templates/issue-feature.md +97 -0
- package/.claude/skills/command-guide/templates/issue-question.md +141 -0
- package/.claude/skills/prompt-enhancer/SKILL.md +124 -0
- package/.claude/workflows/_template-compare-matrix.html +692 -0
- package/.claude/workflows/chinese-response.md +38 -0
- package/.claude/workflows/cli-templates/fix-plan-template.json +75 -0
- package/.claude/workflows/cli-templates/fix-progress-template.json +48 -0
- package/.claude/workflows/cli-templates/memory/style-skill-memory/skill-md-template.md +299 -0
- package/.claude/workflows/cli-templates/planning-roles/data-architect.md +120 -0
- package/.claude/workflows/cli-templates/planning-roles/product-manager.md +119 -0
- package/.claude/workflows/cli-templates/planning-roles/product-owner.md +261 -0
- package/.claude/workflows/cli-templates/planning-roles/scrum-master.md +186 -0
- package/.claude/workflows/cli-templates/planning-roles/subject-matter-expert.md +281 -0
- package/.claude/workflows/cli-templates/planning-roles/synthesis-role.md +414 -0
- package/.claude/workflows/cli-templates/planning-roles/system-architect.md +106 -0
- package/.claude/workflows/cli-templates/planning-roles/test-strategist.md +124 -0
- package/.claude/workflows/cli-templates/planning-roles/ui-designer.md +379 -0
- package/.claude/workflows/cli-templates/planning-roles/ux-expert.md +240 -0
- package/.claude/workflows/cli-templates/prompts/analysis/01-diagnose-bug-root-cause.txt +127 -0
- package/.claude/workflows/cli-templates/prompts/analysis/01-trace-code-execution.txt +115 -0
- package/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt +37 -0
- package/.claude/workflows/cli-templates/prompts/analysis/02-analyze-technical-document.txt +33 -0
- package/.claude/workflows/cli-templates/prompts/analysis/02-review-architecture.txt +29 -0
- package/.claude/workflows/cli-templates/prompts/analysis/02-review-code-quality.txt +28 -0
- package/.claude/workflows/cli-templates/prompts/analysis/03-analyze-performance.txt +29 -0
- package/.claude/workflows/cli-templates/prompts/analysis/03-assess-security-risks.txt +29 -0
- package/.claude/workflows/cli-templates/prompts/analysis/03-review-quality-standards.txt +29 -0
- package/.claude/workflows/cli-templates/prompts/development/02-generate-tests.txt +70 -0
- package/.claude/workflows/cli-templates/prompts/development/02-implement-component-ui.txt +55 -0
- package/.claude/workflows/cli-templates/prompts/development/02-implement-feature.txt +58 -0
- package/.claude/workflows/cli-templates/prompts/development/02-refactor-codebase.txt +55 -0
- package/.claude/workflows/cli-templates/prompts/development/03-debug-runtime-issues.txt +55 -0
- package/.claude/workflows/cli-templates/prompts/documentation/api.txt +15 -0
- package/.claude/workflows/cli-templates/prompts/documentation/folder-navigation.txt +27 -0
- package/.claude/workflows/cli-templates/prompts/documentation/module-readme.txt +49 -0
- package/.claude/workflows/cli-templates/prompts/documentation/project-architecture.txt +41 -0
- package/.claude/workflows/cli-templates/prompts/documentation/project-examples.txt +35 -0
- package/.claude/workflows/cli-templates/prompts/documentation/project-readme.txt +35 -0
- package/.claude/workflows/cli-templates/prompts/memory/02-document-module-structure.txt +165 -0
- package/.claude/workflows/cli-templates/prompts/planning/01-plan-architecture-design.txt +109 -0
- package/.claude/workflows/cli-templates/prompts/planning/02-breakdown-task-steps.txt +30 -0
- package/.claude/workflows/cli-templates/prompts/planning/02-design-component-spec.txt +28 -0
- package/.claude/workflows/cli-templates/prompts/planning/03-evaluate-concept-feasibility.txt +127 -0
- package/.claude/workflows/cli-templates/prompts/planning/03-plan-migration-strategy.txt +30 -0
- package/.claude/workflows/cli-templates/prompts/rules/rule-api.txt +122 -0
- package/.claude/workflows/cli-templates/prompts/rules/rule-components.txt +122 -0
- package/.claude/workflows/cli-templates/prompts/rules/rule-config.txt +89 -0
- package/.claude/workflows/cli-templates/prompts/rules/rule-core.txt +60 -0
- package/.claude/workflows/cli-templates/prompts/rules/rule-patterns.txt +70 -0
- package/.claude/workflows/cli-templates/prompts/rules/rule-testing.txt +81 -0
- package/.claude/workflows/cli-templates/prompts/rules/tech-rules-agent-prompt.txt +89 -0
- package/.claude/workflows/cli-templates/prompts/tech/tech-module-format.txt +359 -0
- package/.claude/workflows/cli-templates/prompts/tech/tech-skill-index.txt +185 -0
- package/.claude/workflows/cli-templates/prompts/test/test-concept-analysis.txt +179 -0
- package/.claude/workflows/cli-templates/prompts/universal/00-universal-creative-style.txt +95 -0
- package/.claude/workflows/cli-templates/prompts/universal/00-universal-rigorous-style.txt +92 -0
- package/.claude/workflows/cli-templates/prompts/verification/codex-technical.txt +28 -0
- package/.claude/workflows/cli-templates/prompts/verification/cross-validation.txt +28 -0
- package/.claude/workflows/cli-templates/prompts/verification/gemini-strategic.txt +27 -0
- package/.claude/workflows/cli-templates/prompts/workflow/analysis-results-structure.txt +224 -0
- package/.claude/workflows/cli-templates/prompts/workflow/codex-feasibility-validation.txt +176 -0
- package/.claude/workflows/cli-templates/prompts/workflow/gemini-solution-design.txt +131 -0
- package/.claude/workflows/cli-templates/prompts/workflow/impl-plan-template.txt +286 -0
- package/.claude/workflows/cli-templates/prompts/workflow/skill-aggregation.txt +172 -0
- package/.claude/workflows/cli-templates/prompts/workflow/skill-conflict-patterns.txt +94 -0
- package/.claude/workflows/cli-templates/prompts/workflow/skill-index.txt +224 -0
- package/.claude/workflows/cli-templates/prompts/workflow/skill-lessons-learned.txt +94 -0
- package/.claude/workflows/cli-templates/prompts/workflow/skill-sessions-timeline.txt +53 -0
- package/.claude/workflows/cli-templates/prompts/workflow/task-json-agent-mode.txt +123 -0
- package/.claude/workflows/cli-templates/prompts/workflow/task-json-cli-mode.txt +182 -0
- package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +112 -0
- package/.claude/workflows/cli-templates/protocols/write-protocol.md +201 -0
- package/.claude/workflows/cli-templates/schemas/conflict-resolution-schema.json +137 -0
- package/.claude/workflows/cli-templates/schemas/debug-log-json-schema.json +127 -0
- package/.claude/workflows/cli-templates/schemas/diagnosis-json-schema.json +234 -0
- package/.claude/workflows/cli-templates/schemas/explore-json-schema.json +124 -0
- package/.claude/workflows/cli-templates/schemas/fix-plan-json-schema.json +298 -0
- package/.claude/workflows/cli-templates/schemas/plan-json-schema.json +244 -0
- package/.claude/workflows/cli-templates/schemas/project-json-schema.json +221 -0
- package/.claude/workflows/cli-templates/schemas/review-deep-dive-results-schema.json +82 -0
- package/.claude/workflows/cli-templates/schemas/review-dimension-results-schema.json +51 -0
- package/.claude/workflows/cli-templates/tech-stacks/go-dev.md +91 -0
- package/.claude/workflows/cli-templates/tech-stacks/java-dev.md +107 -0
- package/.claude/workflows/cli-templates/tech-stacks/javascript-dev.md +58 -0
- package/.claude/workflows/cli-templates/tech-stacks/python-dev.md +79 -0
- package/.claude/workflows/cli-templates/tech-stacks/react-dev.md +103 -0
- package/.claude/workflows/cli-templates/tech-stacks/typescript-dev.md +83 -0
- package/.claude/workflows/cli-templates/ui-design/systems/animation-tokens.json +247 -0
- package/.claude/workflows/cli-templates/ui-design/systems/design-tokens.json +342 -0
- package/.claude/workflows/cli-templates/ui-design/systems/layout-templates.json +145 -0
- package/.claude/workflows/cli-tools-usage.md +526 -0
- package/.claude/workflows/coding-philosophy.md +70 -0
- package/.claude/workflows/context-tools.md +84 -0
- package/.claude/workflows/file-modification.md +64 -0
- package/.claude/workflows/review-directory-specification.md +336 -0
- package/.claude/workflows/task-core.md +214 -0
- package/.claude/workflows/tool-strategy.md +216 -0
- package/.claude/workflows/windows-platform.md +16 -0
- package/.claude/workflows/workflow-architecture.md +942 -0
- package/.codex/AGENTS.md +63 -0
- package/.codex/prompts/debug.md +318 -0
- package/.codex/prompts/execute.md +273 -0
- package/.codex/prompts/lite-execute.md +164 -0
- package/.codex/prompts/lite-plan.md +469 -0
- package/.codex/prompts.zip +0 -0
- package/.gemini/GEMINI.md +25 -0
- package/.qwen/QWEN.md +25 -0
- package/LICENSE +21 -0
- package/README.md +294 -145
- package/ccw/README.md +145 -0
- package/ccw/package.json +65 -0
- package/codex-lens/pyproject.toml +48 -0
- package/codex-lens/src/codexlens/.workflow/.cli-history/history.db +0 -0
- package/codex-lens/src/codexlens/__init__.py +28 -0
- package/codex-lens/src/codexlens/__main__.py +14 -0
- package/codex-lens/src/codexlens/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/__pycache__/__main__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/__pycache__/entities.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/__pycache__/errors.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__init__.py +27 -0
- package/codex-lens/src/codexlens/cli/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/commands.py +1931 -0
- package/codex-lens/src/codexlens/cli/embedding_manager.py +620 -0
- package/codex-lens/src/codexlens/cli/model_manager.py +289 -0
- package/codex-lens/src/codexlens/cli/output.py +124 -0
- package/codex-lens/src/codexlens/config.py +201 -0
- package/codex-lens/src/codexlens/entities.py +121 -0
- package/codex-lens/src/codexlens/errors.py +55 -0
- package/codex-lens/src/codexlens/indexing/README.md +77 -0
- package/codex-lens/src/codexlens/indexing/__init__.py +4 -0
- package/codex-lens/src/codexlens/indexing/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/indexing/__pycache__/symbol_extractor.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/indexing/symbol_extractor.py +243 -0
- package/codex-lens/src/codexlens/parsers/__init__.py +8 -0
- package/codex-lens/src/codexlens/parsers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/parsers/__pycache__/encoding.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/parsers/__pycache__/factory.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/parsers/__pycache__/tokenizer.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/parsers/__pycache__/treesitter_parser.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/parsers/encoding.py +202 -0
- package/codex-lens/src/codexlens/parsers/factory.py +256 -0
- package/codex-lens/src/codexlens/parsers/tokenizer.py +98 -0
- package/codex-lens/src/codexlens/parsers/treesitter_parser.py +335 -0
- package/codex-lens/src/codexlens/search/__init__.py +15 -0
- package/codex-lens/src/codexlens/search/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/enrichment.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/query_parser.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/chain_search.py +647 -0
- package/codex-lens/src/codexlens/search/enrichment.py +150 -0
- package/codex-lens/src/codexlens/search/hybrid_search.py +313 -0
- package/codex-lens/src/codexlens/search/query_parser.py +242 -0
- package/codex-lens/src/codexlens/search/ranking.py +274 -0
- package/codex-lens/src/codexlens/semantic/__init__.py +39 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/ann_index.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/code_extractor.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/graph_analyzer.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/llm_enhancer.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/ann_index.py +414 -0
- package/codex-lens/src/codexlens/semantic/chunker.py +448 -0
- package/codex-lens/src/codexlens/semantic/code_extractor.py +274 -0
- package/codex-lens/src/codexlens/semantic/embedder.py +185 -0
- package/codex-lens/src/codexlens/semantic/vector_store.py +955 -0
- package/codex-lens/src/codexlens/storage/__init__.py +29 -0
- package/codex-lens/src/codexlens/storage/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/dir_index.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/file_cache.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/index_tree.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/migration_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/registry.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/sqlite_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/sqlite_utils.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/dir_index.py +1850 -0
- package/codex-lens/src/codexlens/storage/file_cache.py +32 -0
- package/codex-lens/src/codexlens/storage/index_tree.py +776 -0
- package/codex-lens/src/codexlens/storage/migration_manager.py +154 -0
- package/codex-lens/src/codexlens/storage/migrations/__init__.py +1 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_001_normalize_keywords.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_002_add_token_metadata.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_003_code_relationships.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_005_cleanup_unused_fields.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/migration_001_normalize_keywords.py +123 -0
- package/codex-lens/src/codexlens/storage/migrations/migration_002_add_token_metadata.py +48 -0
- package/codex-lens/src/codexlens/storage/migrations/migration_004_dual_fts.py +232 -0
- package/codex-lens/src/codexlens/storage/migrations/migration_005_cleanup_unused_fields.py +196 -0
- package/codex-lens/src/codexlens/storage/path_mapper.py +274 -0
- package/codex-lens/src/codexlens/storage/registry.py +670 -0
- package/codex-lens/src/codexlens/storage/sqlite_store.py +576 -0
- package/codex-lens/src/codexlens/storage/sqlite_utils.py +64 -0
- package/package.json +37 -32
- package/dist/cli.d.ts +0 -2
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -219
- package/dist/cli.js.map +0 -1
- package/dist/commands/cli.d.ts +0 -32
- package/dist/commands/cli.d.ts.map +0 -1
- package/dist/commands/cli.js +0 -619
- package/dist/commands/cli.js.map +0 -1
- package/dist/commands/core-memory.d.ts +0 -32
- package/dist/commands/core-memory.d.ts.map +0 -1
- package/dist/commands/core-memory.js +0 -640
- package/dist/commands/core-memory.js.map +0 -1
- package/dist/commands/hook.d.ts +0 -16
- package/dist/commands/hook.d.ts.map +0 -1
- package/dist/commands/hook.js +0 -276
- package/dist/commands/hook.js.map +0 -1
- package/dist/commands/install.d.ts +0 -12
- package/dist/commands/install.d.ts.map +0 -1
- package/dist/commands/install.js +0 -443
- package/dist/commands/install.js.map +0 -1
- package/dist/commands/list.d.ts +0 -5
- package/dist/commands/list.d.ts.map +0 -1
- package/dist/commands/list.js +0 -32
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/memory.d.ts +0 -57
- package/dist/commands/memory.d.ts.map +0 -1
- package/dist/commands/memory.js +0 -890
- package/dist/commands/memory.js.map +0 -1
- package/dist/commands/serve.d.ts +0 -12
- package/dist/commands/serve.d.ts.map +0 -1
- package/dist/commands/serve.js +0 -63
- package/dist/commands/serve.js.map +0 -1
- package/dist/commands/session-path-resolver.d.ts +0 -45
- package/dist/commands/session-path-resolver.d.ts.map +0 -1
- package/dist/commands/session-path-resolver.js +0 -302
- package/dist/commands/session-path-resolver.js.map +0 -1
- package/dist/commands/session.d.ts +0 -12
- package/dist/commands/session.d.ts.map +0 -1
- package/dist/commands/session.js +0 -954
- package/dist/commands/session.js.map +0 -1
- package/dist/commands/stop.d.ts +0 -11
- package/dist/commands/stop.d.ts.map +0 -1
- package/dist/commands/stop.js +0 -96
- package/dist/commands/stop.js.map +0 -1
- package/dist/commands/tool.d.ts +0 -29
- package/dist/commands/tool.d.ts.map +0 -1
- package/dist/commands/tool.js +0 -173
- package/dist/commands/tool.js.map +0 -1
- package/dist/commands/uninstall.d.ts +0 -9
- package/dist/commands/uninstall.d.ts.map +0 -1
- package/dist/commands/uninstall.js +0 -239
- package/dist/commands/uninstall.js.map +0 -1
- package/dist/commands/upgrade.d.ts +0 -10
- package/dist/commands/upgrade.d.ts.map +0 -1
- package/dist/commands/upgrade.js +0 -288
- package/dist/commands/upgrade.js.map +0 -1
- package/dist/commands/view.d.ts +0 -14
- package/dist/commands/view.d.ts.map +0 -1
- package/dist/commands/view.js +0 -100
- package/dist/commands/view.js.map +0 -1
- package/dist/config/storage-paths.d.ts +0 -184
- package/dist/config/storage-paths.d.ts.map +0 -1
- package/dist/config/storage-paths.js +0 -536
- package/dist/config/storage-paths.js.map +0 -1
- package/dist/core/cache-manager.d.ts +0 -80
- package/dist/core/cache-manager.d.ts.map +0 -1
- package/dist/core/cache-manager.js +0 -260
- package/dist/core/cache-manager.js.map +0 -1
- package/dist/core/claude-freshness.d.ts +0 -53
- package/dist/core/claude-freshness.d.ts.map +0 -1
- package/dist/core/claude-freshness.js +0 -232
- package/dist/core/claude-freshness.js.map +0 -1
- package/dist/core/core-memory-store.d.ts +0 -320
- package/dist/core/core-memory-store.d.ts.map +0 -1
- package/dist/core/core-memory-store.js +0 -1177
- package/dist/core/core-memory-store.js.map +0 -1
- package/dist/core/dashboard-generator-patch.d.ts +0 -2
- package/dist/core/dashboard-generator-patch.d.ts.map +0 -1
- package/dist/core/dashboard-generator-patch.js +0 -48
- package/dist/core/dashboard-generator-patch.js.map +0 -1
- package/dist/core/dashboard-generator.d.ts +0 -8
- package/dist/core/dashboard-generator.d.ts.map +0 -1
- package/dist/core/dashboard-generator.js +0 -695
- package/dist/core/dashboard-generator.js.map +0 -1
- package/dist/core/data-aggregator.d.ts +0 -145
- package/dist/core/data-aggregator.d.ts.map +0 -1
- package/dist/core/data-aggregator.js +0 -416
- package/dist/core/data-aggregator.js.map +0 -1
- package/dist/core/history-importer.d.ts +0 -102
- package/dist/core/history-importer.d.ts.map +0 -1
- package/dist/core/history-importer.js +0 -493
- package/dist/core/history-importer.js.map +0 -1
- package/dist/core/lite-scanner-complete.d.ts +0 -81
- package/dist/core/lite-scanner-complete.d.ts.map +0 -1
- package/dist/core/lite-scanner-complete.js +0 -368
- package/dist/core/lite-scanner-complete.js.map +0 -1
- package/dist/core/lite-scanner.d.ts +0 -81
- package/dist/core/lite-scanner.d.ts.map +0 -1
- package/dist/core/lite-scanner.js +0 -368
- package/dist/core/lite-scanner.js.map +0 -1
- package/dist/core/manifest.d.ts +0 -88
- package/dist/core/manifest.d.ts.map +0 -1
- package/dist/core/manifest.js +0 -214
- package/dist/core/manifest.js.map +0 -1
- package/dist/core/memory-embedder-bridge.d.ts +0 -83
- package/dist/core/memory-embedder-bridge.d.ts.map +0 -1
- package/dist/core/memory-embedder-bridge.js +0 -181
- package/dist/core/memory-embedder-bridge.js.map +0 -1
- package/dist/core/memory-store.d.ts +0 -249
- package/dist/core/memory-store.d.ts.map +0 -1
- package/dist/core/memory-store.js +0 -781
- package/dist/core/memory-store.js.map +0 -1
- package/dist/core/routes/ccw-routes.d.ts +0 -20
- package/dist/core/routes/ccw-routes.d.ts.map +0 -1
- package/dist/core/routes/ccw-routes.js +0 -70
- package/dist/core/routes/ccw-routes.js.map +0 -1
- package/dist/core/routes/claude-routes.d.ts +0 -19
- package/dist/core/routes/claude-routes.d.ts.map +0 -1
- package/dist/core/routes/claude-routes.js +0 -1017
- package/dist/core/routes/claude-routes.js.map +0 -1
- package/dist/core/routes/cli-routes.d.ts +0 -20
- package/dist/core/routes/cli-routes.d.ts.map +0 -1
- package/dist/core/routes/cli-routes.js +0 -468
- package/dist/core/routes/cli-routes.js.map +0 -1
- package/dist/core/routes/codexlens-routes.d.ts +0 -20
- package/dist/core/routes/codexlens-routes.d.ts.map +0 -1
- package/dist/core/routes/codexlens-routes.js +0 -754
- package/dist/core/routes/codexlens-routes.js.map +0 -1
- package/dist/core/routes/core-memory-routes.d.ts +0 -21
- package/dist/core/routes/core-memory-routes.d.ts.map +0 -1
- package/dist/core/routes/core-memory-routes.js +0 -520
- package/dist/core/routes/core-memory-routes.js.map +0 -1
- package/dist/core/routes/files-routes.d.ts +0 -20
- package/dist/core/routes/files-routes.d.ts.map +0 -1
- package/dist/core/routes/files-routes.js +0 -374
- package/dist/core/routes/files-routes.js.map +0 -1
- package/dist/core/routes/graph-routes.d.ts +0 -20
- package/dist/core/routes/graph-routes.d.ts.map +0 -1
- package/dist/core/routes/graph-routes.js +0 -517
- package/dist/core/routes/graph-routes.js.map +0 -1
- package/dist/core/routes/help-routes.d.ts +0 -20
- package/dist/core/routes/help-routes.d.ts.map +0 -1
- package/dist/core/routes/help-routes.js +0 -250
- package/dist/core/routes/help-routes.js.map +0 -1
- package/dist/core/routes/hooks-routes.d.ts +0 -21
- package/dist/core/routes/hooks-routes.d.ts.map +0 -1
- package/dist/core/routes/hooks-routes.js +0 -346
- package/dist/core/routes/hooks-routes.js.map +0 -1
- package/dist/core/routes/mcp-routes.d.ts +0 -20
- package/dist/core/routes/mcp-routes.d.ts.map +0 -1
- package/dist/core/routes/mcp-routes.js +0 -1129
- package/dist/core/routes/mcp-routes.js.map +0 -1
- package/dist/core/routes/mcp-templates-db.d.ts +0 -54
- package/dist/core/routes/mcp-templates-db.d.ts.map +0 -1
- package/dist/core/routes/mcp-templates-db.js +0 -226
- package/dist/core/routes/mcp-templates-db.js.map +0 -1
- package/dist/core/routes/memory-routes.d.ts +0 -21
- package/dist/core/routes/memory-routes.d.ts.map +0 -1
- package/dist/core/routes/memory-routes.js +0 -1095
- package/dist/core/routes/memory-routes.js.map +0 -1
- package/dist/core/routes/rules-routes.d.ts +0 -20
- package/dist/core/routes/rules-routes.d.ts.map +0 -1
- package/dist/core/routes/rules-routes.js +0 -442
- package/dist/core/routes/rules-routes.js.map +0 -1
- package/dist/core/routes/session-routes.d.ts +0 -20
- package/dist/core/routes/session-routes.d.ts.map +0 -1
- package/dist/core/routes/session-routes.js +0 -423
- package/dist/core/routes/session-routes.js.map +0 -1
- package/dist/core/routes/skills-routes.d.ts +0 -20
- package/dist/core/routes/skills-routes.d.ts.map +0 -1
- package/dist/core/routes/skills-routes.js +0 -533
- package/dist/core/routes/skills-routes.js.map +0 -1
- package/dist/core/routes/status-routes.d.ts +0 -20
- package/dist/core/routes/status-routes.d.ts.map +0 -1
- package/dist/core/routes/status-routes.js +0 -38
- package/dist/core/routes/status-routes.js.map +0 -1
- package/dist/core/routes/system-routes.d.ts +0 -22
- package/dist/core/routes/system-routes.d.ts.map +0 -1
- package/dist/core/routes/system-routes.js +0 -354
- package/dist/core/routes/system-routes.js.map +0 -1
- package/dist/core/server.d.ts +0 -17
- package/dist/core/server.d.ts.map +0 -1
- package/dist/core/server.js +0 -386
- package/dist/core/server.js.map +0 -1
- package/dist/core/session-clustering-service.d.ts +0 -153
- package/dist/core/session-clustering-service.d.ts.map +0 -1
- package/dist/core/session-clustering-service.js +0 -1065
- package/dist/core/session-clustering-service.js.map +0 -1
- package/dist/core/session-scanner.d.ts +0 -32
- package/dist/core/session-scanner.d.ts.map +0 -1
- package/dist/core/session-scanner.js +0 -253
- package/dist/core/session-scanner.js.map +0 -1
- package/dist/core/websocket.d.ts +0 -23
- package/dist/core/websocket.d.ts.map +0 -1
- package/dist/core/websocket.js +0 -168
- package/dist/core/websocket.js.map +0 -1
- package/dist/index.d.ts +0 -10
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -10
- package/dist/index.js.map +0 -1
- package/dist/mcp-server/index.d.ts +0 -7
- package/dist/mcp-server/index.d.ts.map +0 -1
- package/dist/mcp-server/index.js +0 -157
- package/dist/mcp-server/index.js.map +0 -1
- package/dist/tools/classify-folders.d.ts +0 -26
- package/dist/tools/classify-folders.d.ts.map +0 -1
- package/dist/tools/classify-folders.js +0 -201
- package/dist/tools/classify-folders.js.map +0 -1
- package/dist/tools/cli-config-manager.d.ts +0 -62
- package/dist/tools/cli-config-manager.d.ts.map +0 -1
- package/dist/tools/cli-config-manager.js +0 -221
- package/dist/tools/cli-config-manager.js.map +0 -1
- package/dist/tools/cli-executor.d.ts +0 -373
- package/dist/tools/cli-executor.d.ts.map +0 -1
- package/dist/tools/cli-executor.js +0 -1625
- package/dist/tools/cli-executor.js.map +0 -1
- package/dist/tools/cli-history-store.d.ts +0 -330
- package/dist/tools/cli-history-store.d.ts.map +0 -1
- package/dist/tools/cli-history-store.js +0 -916
- package/dist/tools/cli-history-store.js.map +0 -1
- package/dist/tools/codex-lens.d.ts +0 -118
- package/dist/tools/codex-lens.d.ts.map +0 -1
- package/dist/tools/codex-lens.js +0 -962
- package/dist/tools/codex-lens.js.map +0 -1
- package/dist/tools/convert-tokens-to-css.d.ts +0 -14
- package/dist/tools/convert-tokens-to-css.d.ts.map +0 -1
- package/dist/tools/convert-tokens-to-css.js +0 -244
- package/dist/tools/convert-tokens-to-css.js.map +0 -1
- package/dist/tools/core-memory.d.ts +0 -66
- package/dist/tools/core-memory.d.ts.map +0 -1
- package/dist/tools/core-memory.js +0 -324
- package/dist/tools/core-memory.js.map +0 -1
- package/dist/tools/detect-changed-modules.d.ts +0 -24
- package/dist/tools/detect-changed-modules.d.ts.map +0 -1
- package/dist/tools/detect-changed-modules.js +0 -277
- package/dist/tools/detect-changed-modules.js.map +0 -1
- package/dist/tools/discover-design-files.d.ts +0 -36
- package/dist/tools/discover-design-files.d.ts.map +0 -1
- package/dist/tools/discover-design-files.js +0 -147
- package/dist/tools/discover-design-files.js.map +0 -1
- package/dist/tools/edit-file.d.ts +0 -28
- package/dist/tools/edit-file.d.ts.map +0 -1
- package/dist/tools/edit-file.js +0 -479
- package/dist/tools/edit-file.js.map +0 -1
- package/dist/tools/generate-module-docs.d.ts +0 -22
- package/dist/tools/generate-module-docs.d.ts.map +0 -1
- package/dist/tools/generate-module-docs.js +0 -379
- package/dist/tools/generate-module-docs.js.map +0 -1
- package/dist/tools/get-modules-by-depth.d.ts +0 -15
- package/dist/tools/get-modules-by-depth.d.ts.map +0 -1
- package/dist/tools/get-modules-by-depth.js +0 -296
- package/dist/tools/get-modules-by-depth.js.map +0 -1
- package/dist/tools/index.d.ts +0 -55
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -304
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/native-session-discovery.d.ts +0 -97
- package/dist/tools/native-session-discovery.d.ts.map +0 -1
- package/dist/tools/native-session-discovery.js +0 -700
- package/dist/tools/native-session-discovery.js.map +0 -1
- package/dist/tools/notifier.d.ts +0 -50
- package/dist/tools/notifier.d.ts.map +0 -1
- package/dist/tools/notifier.js +0 -90
- package/dist/tools/notifier.js.map +0 -1
- package/dist/tools/read-file.d.ts +0 -32
- package/dist/tools/read-file.d.ts.map +0 -1
- package/dist/tools/read-file.js +0 -329
- package/dist/tools/read-file.js.map +0 -1
- package/dist/tools/resume-strategy.d.ts +0 -48
- package/dist/tools/resume-strategy.d.ts.map +0 -1
- package/dist/tools/resume-strategy.js +0 -248
- package/dist/tools/resume-strategy.js.map +0 -1
- package/dist/tools/session-content-parser.d.ts +0 -58
- package/dist/tools/session-content-parser.d.ts.map +0 -1
- package/dist/tools/session-content-parser.js +0 -420
- package/dist/tools/session-content-parser.js.map +0 -1
- package/dist/tools/session-manager.d.ts +0 -9
- package/dist/tools/session-manager.d.ts.map +0 -1
- package/dist/tools/session-manager.js +0 -834
- package/dist/tools/session-manager.js.map +0 -1
- package/dist/tools/smart-context.d.ts +0 -35
- package/dist/tools/smart-context.d.ts.map +0 -1
- package/dist/tools/smart-context.js +0 -182
- package/dist/tools/smart-context.js.map +0 -1
- package/dist/tools/smart-search.d.ts +0 -105
- package/dist/tools/smart-search.d.ts.map +0 -1
- package/dist/tools/smart-search.js +0 -1753
- package/dist/tools/smart-search.js.map +0 -1
- package/dist/tools/storage-manager.d.ts +0 -114
- package/dist/tools/storage-manager.d.ts.map +0 -1
- package/dist/tools/storage-manager.js +0 -392
- package/dist/tools/storage-manager.js.map +0 -1
- package/dist/tools/ui-generate-preview.d.ts +0 -39
- package/dist/tools/ui-generate-preview.d.ts.map +0 -1
- package/dist/tools/ui-generate-preview.js +0 -300
- package/dist/tools/ui-generate-preview.js.map +0 -1
- package/dist/tools/ui-instantiate-prototypes.d.ts +0 -75
- package/dist/tools/ui-instantiate-prototypes.d.ts.map +0 -1
- package/dist/tools/ui-instantiate-prototypes.js +0 -256
- package/dist/tools/ui-instantiate-prototypes.js.map +0 -1
- package/dist/tools/update-module-claude.d.ts +0 -80
- package/dist/tools/update-module-claude.d.ts.map +0 -1
- package/dist/tools/update-module-claude.js +0 -351
- package/dist/tools/update-module-claude.js.map +0 -1
- package/dist/tools/write-file.d.ts +0 -19
- package/dist/tools/write-file.d.ts.map +0 -1
- package/dist/tools/write-file.js +0 -193
- package/dist/tools/write-file.js.map +0 -1
- package/dist/types/config.d.ts +0 -11
- package/dist/types/config.d.ts.map +0 -1
- package/dist/types/config.js +0 -2
- package/dist/types/config.js.map +0 -1
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -4
- package/dist/types/index.js.map +0 -1
- package/dist/types/session.d.ts +0 -20
- package/dist/types/session.d.ts.map +0 -1
- package/dist/types/session.js +0 -2
- package/dist/types/session.js.map +0 -1
- package/dist/types/tool.d.ts +0 -36
- package/dist/types/tool.d.ts.map +0 -1
- package/dist/types/tool.js +0 -11
- package/dist/types/tool.js.map +0 -1
- package/dist/utils/browser-launcher.d.ts +0 -13
- package/dist/utils/browser-launcher.d.ts.map +0 -1
- package/dist/utils/browser-launcher.js +0 -60
- package/dist/utils/browser-launcher.js.map +0 -1
- package/dist/utils/file-utils.d.ts +0 -25
- package/dist/utils/file-utils.d.ts.map +0 -1
- package/dist/utils/file-utils.js +0 -48
- package/dist/utils/file-utils.js.map +0 -1
- package/dist/utils/path-resolver.d.ts +0 -80
- package/dist/utils/path-resolver.d.ts.map +0 -1
- package/dist/utils/path-resolver.js +0 -260
- package/dist/utils/path-resolver.js.map +0 -1
- package/dist/utils/path-validator.d.ts +0 -49
- package/dist/utils/path-validator.d.ts.map +0 -1
- package/dist/utils/path-validator.js +0 -123
- package/dist/utils/path-validator.js.map +0 -1
- package/dist/utils/ui.d.ts +0 -62
- package/dist/utils/ui.d.ts.map +0 -1
- package/dist/utils/ui.js +0 -129
- package/dist/utils/ui.js.map +0 -1
- /package/{bin → ccw/bin}/ccw-mcp.js +0 -0
- /package/{bin → ccw/bin}/ccw.js +0 -0
- /package/{src → ccw/src}/.workflow/.cli-history/history.db +0 -0
- /package/{src → ccw/src}/.workflow/.cli-history/history.db-shm +0 -0
- /package/{src → ccw/src}/.workflow/.cli-history/history.db-wal +0 -0
- /package/{src → ccw/src}/cli.ts +0 -0
- /package/{src → ccw/src}/commands/cli.ts +0 -0
- /package/{src → ccw/src}/commands/core-memory.ts +0 -0
- /package/{src → ccw/src}/commands/hook.ts +0 -0
- /package/{src → ccw/src}/commands/install.ts +0 -0
- /package/{src → ccw/src}/commands/list.ts +0 -0
- /package/{src → ccw/src}/commands/memory.ts +0 -0
- /package/{src → ccw/src}/commands/serve.ts +0 -0
- /package/{src → ccw/src}/commands/session-path-resolver.ts +0 -0
- /package/{src → ccw/src}/commands/session.ts +0 -0
- /package/{src → ccw/src}/commands/stop.ts +0 -0
- /package/{src → ccw/src}/commands/tool.ts +0 -0
- /package/{src → ccw/src}/commands/uninstall.ts +0 -0
- /package/{src → ccw/src}/commands/upgrade.ts +0 -0
- /package/{src → ccw/src}/commands/view.ts +0 -0
- /package/{src → ccw/src}/config/storage-paths.ts +0 -0
- /package/{src → ccw/src}/core/cache-manager.ts +0 -0
- /package/{src → ccw/src}/core/claude-freshness.ts +0 -0
- /package/{src → ccw/src}/core/core-memory-store.ts +0 -0
- /package/{src → ccw/src}/core/dashboard-generator-patch.ts +0 -0
- /package/{src → ccw/src}/core/dashboard-generator.ts +0 -0
- /package/{src → ccw/src}/core/data-aggregator.ts +0 -0
- /package/{src → ccw/src}/core/history-importer.ts +0 -0
- /package/{src → ccw/src}/core/lite-scanner-complete.ts +0 -0
- /package/{src → ccw/src}/core/lite-scanner.ts +0 -0
- /package/{src → ccw/src}/core/manifest.ts +0 -0
- /package/{src → ccw/src}/core/memory-embedder-bridge.ts +0 -0
- /package/{src → ccw/src}/core/memory-store.ts +0 -0
- /package/{src → ccw/src}/core/routes/ccw-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/claude-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/cli-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/codexlens-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/core-memory-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/files-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/graph-routes.md +0 -0
- /package/{src → ccw/src}/core/routes/graph-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/help-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/hooks-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/mcp-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/mcp-routes.ts.backup +0 -0
- /package/{src → ccw/src}/core/routes/mcp-templates-db.ts +0 -0
- /package/{src → ccw/src}/core/routes/memory-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/rules-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/session-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/skills-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/status-routes.ts +0 -0
- /package/{src → ccw/src}/core/routes/system-routes.ts +0 -0
- /package/{src → ccw/src}/core/server.ts +0 -0
- /package/{src → ccw/src}/core/session-clustering-service.ts +0 -0
- /package/{src → ccw/src}/core/session-scanner.ts +0 -0
- /package/{src → ccw/src}/core/websocket.ts +0 -0
- /package/{src → ccw/src}/index.ts +0 -0
- /package/{src → ccw/src}/mcp-server/index.ts +0 -0
- /package/{src → ccw/src}/templates/assets/css/github-dark.min.css +0 -0
- /package/{src → ccw/src}/templates/assets/css/github.min.css +0 -0
- /package/{src → ccw/src}/templates/assets/js/cytoscape.min.js +0 -0
- /package/{src → ccw/src}/templates/assets/js/d3.min.js +0 -0
- /package/{src → ccw/src}/templates/assets/js/highlight.min.js +0 -0
- /package/{src → ccw/src}/templates/assets/js/lucide.min.js +0 -0
- /package/{src → ccw/src}/templates/assets/js/marked.min.js +0 -0
- /package/{src → ccw/src}/templates/assets/js/tailwind.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/01-base.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/02-session.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/03-tasks.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/04-lite-tasks.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/05-context.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/06-cards.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/07-managers.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/08-review.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/09-explorer.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/10-cli-status.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/11-cli-history.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/12-cli-legacy.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/13-cli-ccw.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/14-cli-modals.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/15-cli-endpoints.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/16-cli-session.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/17-cli-conversation.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/18-cli-settings.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/19-cli-native-session.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/20-cli-taskqueue.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/21-cli-toolmgmt.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/22-cli-semantic.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/23-memory.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/24-prompt-history.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/25-skills-rules.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/26-claude-manager.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/27-graph-explorer.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/28-mcp-manager.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/29-help.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-css/30-core-memory.css +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/api.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/_conflict_tab.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/_exp_helpers.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/_review_tab.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/carousel.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/cli-history.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/cli-status.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/flowchart.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/global-notifications.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/hook-manager.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/index-manager.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/mcp-manager.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/modals.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/navigation.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/notifications.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/sidebar.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/storage-manager.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/tabs-context.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/tabs-other.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/task-drawer-core.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/task-drawer-renderers.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/task-queue-sidebar.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/theme.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/components/version-check.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/help-i18n.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/i18n.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/main.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/state.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/utils.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/claude-manager.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/cli-manager.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/codexlens-manager.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/core-memory-clusters.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/core-memory.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/explorer.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/fix-session.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/graph-explorer.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/help.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/history.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/home.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/hook-manager.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/lite-tasks.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/mcp-manager.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/mcp-manager.js.backup +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/mcp-manager.js.new +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/memory.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/project-overview.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/prompt-history.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/review-session.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/rules-manager.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/session-detail.js +0 -0
- /package/{src → ccw/src}/templates/dashboard-js/views/skills-manager.js +0 -0
- /package/{src → ccw/src}/templates/dashboard.html +0 -0
- /package/{src → ccw/src}/templates/hooks-config-example.json +0 -0
- /package/{src → ccw/src}/templates/review-cycle-dashboard.html +0 -0
- /package/{src → ccw/src}/templates/workflow-dashboard.html +0 -0
- /package/{src → ccw/src}/tools/classify-folders.ts +0 -0
- /package/{src → ccw/src}/tools/cli-config-manager.ts +0 -0
- /package/{src → ccw/src}/tools/cli-executor.ts +0 -0
- /package/{src → ccw/src}/tools/cli-history-store.ts +0 -0
- /package/{src → ccw/src}/tools/codex-lens.ts +0 -0
- /package/{src → ccw/src}/tools/convert-tokens-to-css.ts +0 -0
- /package/{src → ccw/src}/tools/core-memory.ts +0 -0
- /package/{src → ccw/src}/tools/detect-changed-modules.ts +0 -0
- /package/{src → ccw/src}/tools/discover-design-files.ts +0 -0
- /package/{src → ccw/src}/tools/edit-file.ts +0 -0
- /package/{src → ccw/src}/tools/generate-module-docs.ts +0 -0
- /package/{src → ccw/src}/tools/get-modules-by-depth.ts +0 -0
- /package/{src → ccw/src}/tools/index.ts +0 -0
- /package/{src → ccw/src}/tools/native-session-discovery.ts +0 -0
- /package/{src → ccw/src}/tools/notifier.ts +0 -0
- /package/{src → ccw/src}/tools/read-file.ts +0 -0
- /package/{src → ccw/src}/tools/resume-strategy.ts +0 -0
- /package/{src → ccw/src}/tools/session-content-parser.ts +0 -0
- /package/{src → ccw/src}/tools/session-manager.ts +0 -0
- /package/{src → ccw/src}/tools/smart-context.ts +0 -0
- /package/{src → ccw/src}/tools/smart-search.ts +0 -0
- /package/{src → ccw/src}/tools/smart-search.ts.backup +0 -0
- /package/{src → ccw/src}/tools/storage-manager.ts +0 -0
- /package/{src → ccw/src}/tools/ui-generate-preview.js +0 -0
- /package/{src → ccw/src}/tools/ui-instantiate-prototypes.js +0 -0
- /package/{src → ccw/src}/tools/update-module-claude.js +0 -0
- /package/{src → ccw/src}/tools/write-file.ts +0 -0
- /package/{src → ccw/src}/types/config.ts +0 -0
- /package/{src → ccw/src}/types/index.ts +0 -0
- /package/{src → ccw/src}/types/session.ts +0 -0
- /package/{src → ccw/src}/types/tool.ts +0 -0
- /package/{src → ccw/src}/utils/browser-launcher.ts +0 -0
- /package/{src → ccw/src}/utils/file-utils.ts +0 -0
- /package/{src → ccw/src}/utils/path-resolver.ts +0 -0
- /package/{src → ccw/src}/utils/path-validator.ts +0 -0
- /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
|
+
)
|