spectra-cli 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -5
- package/dist/.spectra-build-meta.json +9 -0
- package/dist/adapters/language-adapter.d.ts +7 -12
- package/dist/adapters/language-adapter.d.ts.map +1 -1
- package/dist/adapters/python-adapter.d.ts.map +1 -1
- package/dist/adapters/python-adapter.js +11 -2
- package/dist/adapters/python-adapter.js.map +1 -1
- package/dist/batch/batch-orchestrator.d.ts +44 -1
- package/dist/batch/batch-orchestrator.d.ts.map +1 -1
- package/dist/batch/batch-orchestrator.js +312 -76
- package/dist/batch/batch-orchestrator.js.map +1 -1
- package/dist/batch/delta-regenerator.d.ts.map +1 -1
- package/dist/batch/delta-regenerator.js +21 -28
- package/dist/batch/delta-regenerator.js.map +1 -1
- package/dist/batch/module-grouper.d.ts +7 -0
- package/dist/batch/module-grouper.d.ts.map +1 -1
- package/dist/batch/module-grouper.js +2 -0
- package/dist/batch/module-grouper.js.map +1 -1
- package/dist/batch/regen-plan.d.ts +78 -0
- package/dist/batch/regen-plan.d.ts.map +1 -0
- package/dist/batch/regen-plan.js +93 -0
- package/dist/batch/regen-plan.js.map +1 -0
- package/dist/cli/commands/batch.d.ts.map +1 -1
- package/dist/cli/commands/batch.js +36 -8
- package/dist/cli/commands/batch.js.map +1 -1
- package/dist/cli/commands/scaffold-kb.d.ts +9 -0
- package/dist/cli/commands/scaffold-kb.d.ts.map +1 -0
- package/dist/cli/commands/scaffold-kb.js +179 -0
- package/dist/cli/commands/scaffold-kb.js.map +1 -0
- package/dist/cli/index.js +24 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/utils/error-handler.d.ts +0 -6
- package/dist/cli/utils/error-handler.d.ts.map +1 -1
- package/dist/cli/utils/error-handler.js +0 -13
- package/dist/cli/utils/error-handler.js.map +1 -1
- package/dist/cli/utils/parse-args.d.ts +43 -3
- package/dist/cli/utils/parse-args.d.ts.map +1 -1
- package/dist/cli/utils/parse-args.js +98 -12
- package/dist/cli/utils/parse-args.js.map +1 -1
- package/dist/cli/version-meta.d.ts +16 -0
- package/dist/cli/version-meta.d.ts.map +1 -0
- package/dist/cli/version-meta.js +28 -0
- package/dist/cli/version-meta.js.map +1 -0
- package/dist/core/ast-analyzer.d.ts.map +1 -1
- package/dist/core/ast-analyzer.js +7 -11
- package/dist/core/ast-analyzer.js.map +1 -1
- package/dist/core/import-resolver.d.ts +70 -67
- package/dist/core/import-resolver.d.ts.map +1 -1
- package/dist/core/import-resolver.js +341 -200
- package/dist/core/import-resolver.js.map +1 -1
- package/dist/core/single-spec-orchestrator.d.ts +21 -0
- package/dist/core/single-spec-orchestrator.d.ts.map +1 -1
- package/dist/core/single-spec-orchestrator.js +41 -14
- package/dist/core/single-spec-orchestrator.js.map +1 -1
- package/dist/core/skeleton-hash.d.ts +33 -0
- package/dist/core/skeleton-hash.d.ts.map +1 -0
- package/dist/core/skeleton-hash.js +76 -0
- package/dist/core/skeleton-hash.js.map +1 -0
- package/dist/core/tree-sitter-analyzer.d.ts +3 -2
- package/dist/core/tree-sitter-analyzer.d.ts.map +1 -1
- package/dist/core/tree-sitter-analyzer.js +5 -6
- package/dist/core/tree-sitter-analyzer.js.map +1 -1
- package/dist/core/tree-sitter-fallback.d.ts +7 -2
- package/dist/core/tree-sitter-fallback.d.ts.map +1 -1
- package/dist/core/tree-sitter-fallback.js +18 -15
- package/dist/core/tree-sitter-fallback.js.map +1 -1
- package/dist/extraction/image-extractor.d.ts.map +1 -1
- package/dist/extraction/image-extractor.js +3 -1
- package/dist/extraction/image-extractor.js.map +1 -1
- package/dist/extraction/markdown-extractor.d.ts.map +1 -1
- package/dist/extraction/markdown-extractor.js +3 -1
- package/dist/extraction/markdown-extractor.js.map +1 -1
- package/dist/generator/frontmatter.d.ts +5 -0
- package/dist/generator/frontmatter.d.ts.map +1 -1
- package/dist/generator/frontmatter.js +4 -0
- package/dist/generator/frontmatter.js.map +1 -1
- package/dist/kb-mcp/index.d.ts +14 -0
- package/dist/kb-mcp/index.d.ts.map +1 -0
- package/dist/kb-mcp/index.js +24 -0
- package/dist/kb-mcp/index.js.map +1 -0
- package/dist/kb-mcp/lib/kb-error.d.ts +21 -0
- package/dist/kb-mcp/lib/kb-error.d.ts.map +1 -0
- package/dist/kb-mcp/lib/kb-error.js +28 -0
- package/dist/kb-mcp/lib/kb-error.js.map +1 -0
- package/dist/kb-mcp/lib/kb-locator.d.ts +58 -0
- package/dist/kb-mcp/lib/kb-locator.d.ts.map +1 -0
- package/dist/kb-mcp/lib/kb-locator.js +85 -0
- package/dist/kb-mcp/lib/kb-locator.js.map +1 -0
- package/dist/kb-mcp/lib/result-merger.d.ts +35 -0
- package/dist/kb-mcp/lib/result-merger.d.ts.map +1 -0
- package/dist/kb-mcp/lib/result-merger.js +64 -0
- package/dist/kb-mcp/lib/result-merger.js.map +1 -0
- package/dist/kb-mcp/server.d.ts +12 -0
- package/dist/kb-mcp/server.d.ts.map +1 -0
- package/dist/kb-mcp/server.js +29 -0
- package/dist/kb-mcp/server.js.map +1 -0
- package/dist/kb-mcp/tools/kb-api-lookup.d.ts +28 -0
- package/dist/kb-mcp/tools/kb-api-lookup.d.ts.map +1 -0
- package/dist/kb-mcp/tools/kb-api-lookup.js +237 -0
- package/dist/kb-mcp/tools/kb-api-lookup.js.map +1 -0
- package/dist/kb-mcp/tools/kb-doc-lookup.d.ts +20 -0
- package/dist/kb-mcp/tools/kb-doc-lookup.d.ts.map +1 -0
- package/dist/kb-mcp/tools/kb-doc-lookup.js +92 -0
- package/dist/kb-mcp/tools/kb-doc-lookup.js.map +1 -0
- package/dist/kb-mcp/tools/kb-search.d.ts +23 -0
- package/dist/kb-mcp/tools/kb-search.d.ts.map +1 -0
- package/dist/kb-mcp/tools/kb-search.js +126 -0
- package/dist/kb-mcp/tools/kb-search.js.map +1 -0
- package/dist/knowledge-graph/import-resolver.d.ts +11 -86
- package/dist/knowledge-graph/import-resolver.d.ts.map +1 -1
- package/dist/knowledge-graph/import-resolver.js +10 -333
- package/dist/knowledge-graph/import-resolver.js.map +1 -1
- package/dist/knowledge-graph/incremental.d.ts +25 -4
- package/dist/knowledge-graph/incremental.d.ts.map +1 -1
- package/dist/knowledge-graph/incremental.js +37 -15
- package/dist/knowledge-graph/incremental.js.map +1 -1
- package/dist/knowledge-graph/index.d.ts +11 -0
- package/dist/knowledge-graph/index.d.ts.map +1 -1
- package/dist/knowledge-graph/index.js +43 -1
- package/dist/knowledge-graph/index.js.map +1 -1
- package/dist/knowledge-graph/module-derivation.d.ts +14 -0
- package/dist/knowledge-graph/module-derivation.d.ts.map +1 -1
- package/dist/knowledge-graph/module-derivation.js +44 -44
- package/dist/knowledge-graph/module-derivation.js.map +1 -1
- package/dist/knowledge-graph/persistence.d.ts +56 -16
- package/dist/knowledge-graph/persistence.d.ts.map +1 -1
- package/dist/knowledge-graph/persistence.js +86 -32
- package/dist/knowledge-graph/persistence.js.map +1 -1
- package/dist/knowledge-graph/query-helpers.d.ts +39 -12
- package/dist/knowledge-graph/query-helpers.d.ts.map +1 -1
- package/dist/knowledge-graph/query-helpers.js +199 -34
- package/dist/knowledge-graph/query-helpers.js.map +1 -1
- package/dist/knowledge-graph/relativize.d.ts +51 -0
- package/dist/knowledge-graph/relativize.d.ts.map +1 -0
- package/dist/knowledge-graph/relativize.js +104 -0
- package/dist/knowledge-graph/relativize.js.map +1 -0
- package/dist/mcp/agent-context-tools.d.ts +3 -54
- package/dist/mcp/agent-context-tools.d.ts.map +1 -1
- package/dist/mcp/agent-context-tools.js +141 -189
- package/dist/mcp/agent-context-tools.js.map +1 -1
- package/dist/mcp/file-nav-tools.d.ts +42 -0
- package/dist/mcp/file-nav-tools.d.ts.map +1 -0
- package/dist/mcp/file-nav-tools.js +318 -0
- package/dist/mcp/file-nav-tools.js.map +1 -0
- package/dist/mcp/graph-tools.d.ts +2 -0
- package/dist/mcp/graph-tools.d.ts.map +1 -1
- package/dist/mcp/graph-tools.js +134 -123
- package/dist/mcp/graph-tools.js.map +1 -1
- package/dist/mcp/lib/file-nav-helpers.d.ts +128 -0
- package/dist/mcp/lib/file-nav-helpers.d.ts.map +1 -0
- package/dist/mcp/lib/file-nav-helpers.js +305 -0
- package/dist/mcp/lib/file-nav-helpers.js.map +1 -0
- package/dist/mcp/lib/telemetry.d.ts +69 -0
- package/dist/mcp/lib/telemetry.d.ts.map +1 -0
- package/dist/mcp/lib/telemetry.js +113 -0
- package/dist/mcp/lib/telemetry.js.map +1 -0
- package/dist/mcp/lib/tool-response.d.ts +46 -0
- package/dist/mcp/lib/tool-response.d.ts.map +1 -0
- package/dist/mcp/lib/tool-response.js +83 -0
- package/dist/mcp/lib/tool-response.js.map +1 -0
- package/dist/mcp/server.d.ts +13 -2
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +209 -150
- package/dist/mcp/server.js.map +1 -1
- package/dist/models/code-skeleton.d.ts +4 -2
- package/dist/models/code-skeleton.d.ts.map +1 -1
- package/dist/models/code-skeleton.js +2 -1
- package/dist/models/code-skeleton.js.map +1 -1
- package/dist/models/module-spec.d.ts +18 -0
- package/dist/models/module-spec.d.ts.map +1 -1
- package/dist/models/module-spec.js +6 -0
- package/dist/models/module-spec.js.map +1 -1
- package/dist/panoramic/builders/doc-graph-builder.d.ts +13 -0
- package/dist/panoramic/builders/doc-graph-builder.d.ts.map +1 -1
- package/dist/panoramic/builders/doc-graph-builder.js +20 -0
- package/dist/panoramic/builders/doc-graph-builder.js.map +1 -1
- package/dist/panoramic/graph/graph-builder.d.ts +72 -2
- package/dist/panoramic/graph/graph-builder.d.ts.map +1 -1
- package/dist/panoramic/graph/graph-builder.js +257 -51
- package/dist/panoramic/graph/graph-builder.js.map +1 -1
- package/dist/panoramic/graph/graph-query.d.ts +16 -0
- package/dist/panoramic/graph/graph-query.d.ts.map +1 -1
- package/dist/panoramic/graph/graph-query.js +49 -1
- package/dist/panoramic/graph/graph-query.js.map +1 -1
- package/dist/panoramic/graph/index.d.ts +2 -1
- package/dist/panoramic/graph/index.d.ts.map +1 -1
- package/dist/panoramic/graph/index.js +1 -1
- package/dist/panoramic/graph/index.js.map +1 -1
- package/dist/panoramic/pipelines/adr-evidence-verifier.d.ts.map +1 -1
- package/dist/panoramic/pipelines/adr-evidence-verifier.js +1 -29
- package/dist/panoramic/pipelines/adr-evidence-verifier.js.map +1 -1
- package/dist/panoramic/query.d.ts +1 -0
- package/dist/panoramic/query.d.ts.map +1 -1
- package/dist/panoramic/query.js +5 -0
- package/dist/panoramic/query.js.map +1 -1
- package/dist/scaffold-kb/api-entities-serializer.d.ts +12 -0
- package/dist/scaffold-kb/api-entities-serializer.d.ts.map +1 -0
- package/dist/scaffold-kb/api-entities-serializer.js +144 -0
- package/dist/scaffold-kb/api-entities-serializer.js.map +1 -0
- package/dist/scaffold-kb/arbitration.d.ts +44 -0
- package/dist/scaffold-kb/arbitration.d.ts.map +1 -0
- package/dist/scaffold-kb/arbitration.js +144 -0
- package/dist/scaffold-kb/arbitration.js.map +1 -0
- package/dist/scaffold-kb/chunk-splitter.d.ts +20 -0
- package/dist/scaffold-kb/chunk-splitter.d.ts.map +1 -0
- package/dist/scaffold-kb/chunk-splitter.js +309 -0
- package/dist/scaffold-kb/chunk-splitter.js.map +1 -0
- package/dist/scaffold-kb/doc-graph-builder.d.ts +26 -0
- package/dist/scaffold-kb/doc-graph-builder.d.ts.map +1 -0
- package/dist/scaffold-kb/doc-graph-builder.js +72 -0
- package/dist/scaffold-kb/doc-graph-builder.js.map +1 -0
- package/dist/scaffold-kb/entity-extractor.d.ts +40 -0
- package/dist/scaffold-kb/entity-extractor.d.ts.map +1 -0
- package/dist/scaffold-kb/entity-extractor.js +0 -0
- package/dist/scaffold-kb/entity-extractor.js.map +1 -0
- package/dist/scaffold-kb/entity-heuristic.d.ts +14 -0
- package/dist/scaffold-kb/entity-heuristic.d.ts.map +1 -0
- package/dist/scaffold-kb/entity-heuristic.js +107 -0
- package/dist/scaffold-kb/entity-heuristic.js.map +1 -0
- package/dist/scaffold-kb/entity-matcher.d.ts +20 -0
- package/dist/scaffold-kb/entity-matcher.d.ts.map +1 -0
- package/dist/scaffold-kb/entity-matcher.js +53 -0
- package/dist/scaffold-kb/entity-matcher.js.map +1 -0
- package/dist/scaffold-kb/entity-util.d.ts +11 -0
- package/dist/scaffold-kb/entity-util.d.ts.map +1 -0
- package/dist/scaffold-kb/entity-util.js +23 -0
- package/dist/scaffold-kb/entity-util.js.map +1 -0
- package/dist/scaffold-kb/evidence-envelope.d.ts +17 -0
- package/dist/scaffold-kb/evidence-envelope.d.ts.map +1 -0
- package/dist/scaffold-kb/evidence-envelope.js +32 -0
- package/dist/scaffold-kb/evidence-envelope.js.map +1 -0
- package/dist/scaffold-kb/index.d.ts +20 -0
- package/dist/scaffold-kb/index.d.ts.map +1 -0
- package/dist/scaffold-kb/index.js +102 -0
- package/dist/scaffold-kb/index.js.map +1 -0
- package/dist/scaffold-kb/ingest/ingest-core.d.ts +47 -0
- package/dist/scaffold-kb/ingest/ingest-core.d.ts.map +1 -0
- package/dist/scaffold-kb/ingest/ingest-core.js +231 -0
- package/dist/scaffold-kb/ingest/ingest-core.js.map +1 -0
- package/dist/scaffold-kb/ingest/office-parser.d.ts +33 -0
- package/dist/scaffold-kb/ingest/office-parser.d.ts.map +1 -0
- package/dist/scaffold-kb/ingest/office-parser.js +187 -0
- package/dist/scaffold-kb/ingest/office-parser.js.map +1 -0
- package/dist/scaffold-kb/ingest/url-fetcher.d.ts +43 -0
- package/dist/scaffold-kb/ingest/url-fetcher.d.ts.map +1 -0
- package/dist/scaffold-kb/ingest/url-fetcher.js +245 -0
- package/dist/scaffold-kb/ingest/url-fetcher.js.map +1 -0
- package/dist/scaffold-kb/ingester.d.ts +38 -0
- package/dist/scaffold-kb/ingester.d.ts.map +1 -0
- package/dist/scaffold-kb/ingester.js +261 -0
- package/dist/scaffold-kb/ingester.js.map +1 -0
- package/dist/scaffold-kb/injection-format.d.ts +25 -0
- package/dist/scaffold-kb/injection-format.d.ts.map +1 -0
- package/dist/scaffold-kb/injection-format.js +55 -0
- package/dist/scaffold-kb/injection-format.js.map +1 -0
- package/dist/scaffold-kb/kb-writer.d.ts +13 -0
- package/dist/scaffold-kb/kb-writer.d.ts.map +1 -0
- package/dist/scaffold-kb/kb-writer.js +69 -0
- package/dist/scaffold-kb/kb-writer.js.map +1 -0
- package/dist/scaffold-kb/keyword-extract.d.ts +16 -0
- package/dist/scaffold-kb/keyword-extract.d.ts.map +1 -0
- package/dist/scaffold-kb/keyword-extract.js +49 -0
- package/dist/scaffold-kb/keyword-extract.js.map +1 -0
- package/dist/scaffold-kb/query-sanitizer.d.ts +28 -0
- package/dist/scaffold-kb/query-sanitizer.d.ts.map +1 -0
- package/dist/scaffold-kb/query-sanitizer.js +38 -0
- package/dist/scaffold-kb/query-sanitizer.js.map +1 -0
- package/dist/scaffold-kb/recall-eval.d.ts +48 -0
- package/dist/scaffold-kb/recall-eval.d.ts.map +1 -0
- package/dist/scaffold-kb/recall-eval.js +55 -0
- package/dist/scaffold-kb/recall-eval.js.map +1 -0
- package/dist/scaffold-kb/schema-compat.d.ts +22 -0
- package/dist/scaffold-kb/schema-compat.d.ts.map +1 -0
- package/dist/scaffold-kb/schema-compat.js +32 -0
- package/dist/scaffold-kb/schema-compat.js.map +1 -0
- package/dist/scaffold-kb/search-core.d.ts +42 -0
- package/dist/scaffold-kb/search-core.d.ts.map +1 -0
- package/dist/scaffold-kb/search-core.js +89 -0
- package/dist/scaffold-kb/search-core.js.map +1 -0
- package/dist/scaffold-kb/sqlite-engine.d.ts +58 -0
- package/dist/scaffold-kb/sqlite-engine.d.ts.map +1 -0
- package/dist/scaffold-kb/sqlite-engine.js +61 -0
- package/dist/scaffold-kb/sqlite-engine.js.map +1 -0
- package/dist/scaffold-kb/sqlite-writer.d.ts +18 -0
- package/dist/scaffold-kb/sqlite-writer.d.ts.map +1 -0
- package/dist/scaffold-kb/sqlite-writer.js +76 -0
- package/dist/scaffold-kb/sqlite-writer.js.map +1 -0
- package/dist/scaffold-kb/tokenizer.d.ts +26 -0
- package/dist/scaffold-kb/tokenizer.d.ts.map +1 -0
- package/dist/scaffold-kb/tokenizer.js +97 -0
- package/dist/scaffold-kb/tokenizer.js.map +1 -0
- package/dist/scaffold-kb/types.d.ts +157 -0
- package/dist/scaffold-kb/types.d.ts.map +1 -0
- package/dist/scaffold-kb/types.js +5 -0
- package/dist/scaffold-kb/types.js.map +1 -0
- package/dist/utils/file-scanner.d.ts +15 -0
- package/dist/utils/file-scanner.d.ts.map +1 -1
- package/dist/utils/file-scanner.js +17 -0
- package/dist/utils/file-scanner.js.map +1 -1
- package/dist/utils/string-distance.d.ts +20 -0
- package/dist/utils/string-distance.d.ts.map +1 -0
- package/dist/utils/string-distance.js +38 -0
- package/dist/utils/string-distance.js.map +1 -0
- package/package.json +11 -2
- package/plugins/demo-kb-en/.claude-plugin/plugin.json +11 -0
- package/plugins/demo-kb-en/.mcp.json +8 -0
- package/plugins/demo-kb-en/FIXTURE.json +23 -0
- package/plugins/demo-kb-en/FIXTURE.md +53 -0
- package/plugins/demo-kb-en/ingest-samples/meeting-notes.md +13 -0
- package/plugins/demo-kb-en/kb/api-entities.json +3445 -0
- package/plugins/demo-kb-en/kb/chunks.sqlite +0 -0
- package/plugins/demo-kb-en/kb/doc-graph.json +113 -0
- package/plugins/demo-kb-zh/.claude-plugin/plugin.json +11 -0
- package/plugins/demo-kb-zh/.mcp.json +8 -0
- package/plugins/demo-kb-zh/FIXTURE.json +23 -0
- package/plugins/demo-kb-zh/FIXTURE.md +63 -0
- package/plugins/demo-kb-zh/ingest-samples/meeting-notes.md +13 -0
- package/plugins/demo-kb-zh/kb/api-entities.json +990 -0
- package/plugins/demo-kb-zh/kb/chunks.sqlite +0 -0
- package/plugins/demo-kb-zh/kb/doc-graph.json +113 -0
- package/plugins/spec-driver/.claude-plugin/plugin.json +1 -1
- package/plugins/spec-driver/README.md +33 -1
- package/plugins/spec-driver/agents/verify.md +88 -0
- package/plugins/spec-driver/config/orchestration.yaml +39 -31
- package/plugins/spec-driver/contracts/orchestration-overrides-contract.yaml +27 -0
- package/plugins/spec-driver/contracts/orchestration-schema.mjs +272 -227
- package/plugins/spec-driver/contracts/wrapper-source-of-truth.yaml +3 -0
- package/plugins/spec-driver/hooks/hooks.json +9 -0
- package/plugins/spec-driver/hooks/stop-fix-compliance-check.sh +36 -0
- package/plugins/spec-driver/lib/delegation-contract.mjs +84 -0
- package/plugins/spec-driver/lib/orchestration-resolver.mjs +49 -0
- package/plugins/spec-driver/lib/orchestrator.mjs +29 -1
- package/plugins/spec-driver/scripts/codex-skills.sh +28 -28
- package/plugins/spec-driver/scripts/dev/spike-fix-compliance-e2e.mjs +130 -0
- package/plugins/spec-driver/scripts/fix-compliance-judge.mjs +415 -0
- package/plugins/spec-driver/scripts/generate-adoption-insights.mjs +48 -1
- package/plugins/spec-driver/scripts/goal-loop-cli.mjs +307 -0
- package/plugins/spec-driver/scripts/init-project.sh +56 -0
- package/plugins/spec-driver/scripts/kb-prequery.mjs +125 -0
- package/plugins/spec-driver/scripts/lib/config-schema.mjs +200 -94
- package/plugins/spec-driver/scripts/lib/ensure-gitignore.sh +277 -0
- package/plugins/spec-driver/scripts/lib/extract-wrapper-body.mjs +136 -0
- package/plugins/spec-driver/scripts/lib/fix-compliance-core.mjs +434 -0
- package/plugins/spec-driver/scripts/lib/fix-compliance-io.mjs +335 -0
- package/plugins/spec-driver/scripts/lib/goal-loop-core.mjs +783 -0
- package/plugins/spec-driver/scripts/lib/init-project-output.sh +28 -0
- package/plugins/spec-driver/scripts/lib/load-zod.mjs +62 -0
- package/plugins/spec-driver/scripts/lib/project-profile-resolver.mjs +141 -49
- package/plugins/spec-driver/scripts/lib/project-profile-schema.mjs +88 -54
- package/plugins/spec-driver/scripts/postinstall.sh +13 -1
- package/plugins/spec-driver/scripts/record-workflow-run.mjs +75 -0
- package/plugins/spec-driver/scripts/sync-delegation-contract.mjs +187 -0
- package/plugins/spec-driver/scripts/validate-orchestrator-models.mjs +156 -0
- package/plugins/spec-driver/scripts/validate-wrapper-sources.mjs +46 -1
- package/plugins/spec-driver/skills/spec-driver-feature/SKILL.md +292 -0
- package/plugins/spec-driver/skills/spec-driver-fix/SKILL.md +87 -8
- package/plugins/spec-driver/skills/spec-driver-implement/SKILL.md +8 -0
- package/plugins/spec-driver/skills/spec-driver-resume/SKILL.md +12 -1
- package/plugins/spec-driver/skills/spec-driver-story/SKILL.md +20 -0
- package/plugins/spec-driver/templates/delegation-contract.md +27 -0
- package/plugins/spec-driver/templates/goal-loop-override-template.yaml +284 -0
- package/plugins/spec-driver/templates/specify-base/project-context-template.yaml +10 -0
- package/plugins/spectra/.claude-plugin/plugin.json +1 -1
- package/plugins/spectra/README.md +1 -1
- package/templates/module-spec.hbs +1 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F190 FR-015 — recall@k 机械判定(基于冻结 manifest + searchKbCore)
|
|
3
|
+
*
|
|
4
|
+
* 反过拟合:判定只看 query → top-k docId 是否命中 expected_doc_ids,
|
|
5
|
+
* 无任何按 query 文本的特例分支。systematic zero-recall(命中 0 且目标存在)= BLOCKER。
|
|
6
|
+
*/
|
|
7
|
+
import type { SqliteDb } from './sqlite-engine.js';
|
|
8
|
+
export type RecallCategory = 'chinese_word' | 'mixed' | 'api_symbol' | 'error_code' | 'synonym';
|
|
9
|
+
export interface RecallEntry {
|
|
10
|
+
id: string;
|
|
11
|
+
query: string;
|
|
12
|
+
fixture: 'zh' | 'en';
|
|
13
|
+
category: RecallCategory;
|
|
14
|
+
/** 标准答案文档 id 集;null = 占位(未冻结),跳过判定 */
|
|
15
|
+
expected_doc_ids: string[] | null;
|
|
16
|
+
expected_chunk_ids?: string[] | null;
|
|
17
|
+
}
|
|
18
|
+
export interface RecallManifest {
|
|
19
|
+
manifest_version: string;
|
|
20
|
+
created?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
entries: RecallEntry[];
|
|
23
|
+
}
|
|
24
|
+
export interface EntryOutcome {
|
|
25
|
+
id: string;
|
|
26
|
+
category: RecallCategory;
|
|
27
|
+
hit: boolean;
|
|
28
|
+
/** systematic zero-recall:返回 0 结果但 expected 文档确实在库中 → BLOCKER */
|
|
29
|
+
blocker: boolean;
|
|
30
|
+
skipped: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface CategoryRecall {
|
|
33
|
+
category: RecallCategory;
|
|
34
|
+
total: number;
|
|
35
|
+
hits: number;
|
|
36
|
+
recall: number;
|
|
37
|
+
}
|
|
38
|
+
export interface RecallReport {
|
|
39
|
+
byCategory: CategoryRecall[];
|
|
40
|
+
outcomes: EntryOutcome[];
|
|
41
|
+
blockers: string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 对单库(fixture 对应的 vendor DB)跑整份 manifest,按 category 计 recall@k。
|
|
45
|
+
* @param dbFor 按 fixture 返回对应已加载 DB(zh/en 各一)
|
|
46
|
+
*/
|
|
47
|
+
export declare function computeRecall(dbFor: (fixture: 'zh' | 'en') => SqliteDb, manifest: RecallManifest, k: number): RecallReport;
|
|
48
|
+
//# sourceMappingURL=recall-eval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-eval.d.ts","sourceRoot":"","sources":["../../src/scaffold-kb/recall-eval.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAInD,MAAM,MAAM,cAAc,GACtB,cAAc,GACd,OAAO,GACP,YAAY,GACZ,YAAY,GACZ,SAAS,CAAC;AAEd,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,cAAc,CAAC;IACzB,sCAAsC;IACtC,gBAAgB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,cAAc;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,OAAO,CAAC;IACb,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAQD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,KAAK,QAAQ,EACzC,QAAQ,EAAE,cAAc,EACxB,CAAC,EAAE,MAAM,GACR,YAAY,CAsCd"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F190 FR-015 — recall@k 机械判定(基于冻结 manifest + searchKbCore)
|
|
3
|
+
*
|
|
4
|
+
* 反过拟合:判定只看 query → top-k docId 是否命中 expected_doc_ids,
|
|
5
|
+
* 无任何按 query 文本的特例分支。systematic zero-recall(命中 0 且目标存在)= BLOCKER。
|
|
6
|
+
*/
|
|
7
|
+
import { queryRows } from './sqlite-engine.js';
|
|
8
|
+
import { searchKbCore } from './search-core.js';
|
|
9
|
+
/** 某 docId 是否存在于库(用于 zero-recall BLOCKER 判定) */
|
|
10
|
+
function docExists(db, docId) {
|
|
11
|
+
const rows = queryRows(db, 'SELECT 1 FROM chunk_meta WHERE doc_id = ? LIMIT 1', [docId]);
|
|
12
|
+
return rows.length > 0;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 对单库(fixture 对应的 vendor DB)跑整份 manifest,按 category 计 recall@k。
|
|
16
|
+
* @param dbFor 按 fixture 返回对应已加载 DB(zh/en 各一)
|
|
17
|
+
*/
|
|
18
|
+
export function computeRecall(dbFor, manifest, k) {
|
|
19
|
+
const outcomes = [];
|
|
20
|
+
const blockers = [];
|
|
21
|
+
for (const entry of manifest.entries) {
|
|
22
|
+
if (entry.expected_doc_ids === null || entry.expected_doc_ids.length === 0) {
|
|
23
|
+
outcomes.push({ id: entry.id, category: entry.category, hit: false, blocker: false, skipped: true });
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const db = dbFor(entry.fixture);
|
|
27
|
+
const res = searchKbCore(db, entry.query, k);
|
|
28
|
+
const topDocIds = res.ok ? res.results.slice(0, k).map((r) => r.docId) : [];
|
|
29
|
+
const expected = new Set(entry.expected_doc_ids);
|
|
30
|
+
const hit = topDocIds.some((d) => expected.has(d));
|
|
31
|
+
// systematic zero-recall:0 命中 + 目标确实在库 → BLOCKER
|
|
32
|
+
const blocker = topDocIds.length === 0 && entry.expected_doc_ids.some((d) => docExists(db, d));
|
|
33
|
+
if (blocker)
|
|
34
|
+
blockers.push(entry.id);
|
|
35
|
+
outcomes.push({ id: entry.id, category: entry.category, hit, blocker, skipped: false });
|
|
36
|
+
}
|
|
37
|
+
const cats = new Map();
|
|
38
|
+
for (const o of outcomes) {
|
|
39
|
+
if (o.skipped)
|
|
40
|
+
continue;
|
|
41
|
+
const c = cats.get(o.category) ?? { total: 0, hits: 0 };
|
|
42
|
+
c.total += 1;
|
|
43
|
+
if (o.hit)
|
|
44
|
+
c.hits += 1;
|
|
45
|
+
cats.set(o.category, c);
|
|
46
|
+
}
|
|
47
|
+
const byCategory = [...cats.entries()].map(([category, { total, hits }]) => ({
|
|
48
|
+
category,
|
|
49
|
+
total,
|
|
50
|
+
hits,
|
|
51
|
+
recall: total === 0 ? 0 : hits / total,
|
|
52
|
+
}));
|
|
53
|
+
return { byCategory, outcomes, blockers };
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=recall-eval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-eval.js","sourceRoot":"","sources":["../../src/scaffold-kb/recall-eval.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F192 — chunk_meta schema 向后兼容(C-1 / R-COMPAT-1)
|
|
3
|
+
*
|
|
4
|
+
* F190 已分发的 sqlite(厂商/demo 旧库)无 provenance 三列(ingest_source_type/
|
|
5
|
+
* ingest_origin/ingested_at)。对其显式 SELECT 这些列会 `no such column` 抛错。
|
|
6
|
+
*
|
|
7
|
+
* 本模块用 `PRAGMA table_info(chunk_meta)` 探测列是否存在,生成"始终产出 3 列"的
|
|
8
|
+
* SELECT 片段(旧库用 `NULL AS`)—— 使行索引布局对新旧库稳定,所有读取路径
|
|
9
|
+
* (search-core / kb_search / kb_doc_lookup / kb_api_lookup 的 provenance 投影)
|
|
10
|
+
* 一律走本片段,禁止任何处手写新列名(plan §6)。
|
|
11
|
+
*/
|
|
12
|
+
import type { SqliteDb } from './sqlite-engine.js';
|
|
13
|
+
/** provenance 列名(F192 §3.3);与 sqlite-writer CREATE_META 保持一致 */
|
|
14
|
+
export declare const PROVENANCE_COLUMNS: readonly ["ingest_source_type", "ingest_origin", "ingested_at"];
|
|
15
|
+
/** 探测 chunk_meta 是否含全部 provenance 列(F190 旧库返回 false) */
|
|
16
|
+
export declare function hasProvenanceColumns(db: SqliteDb): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* 生成 provenance 的 SELECT 片段(含前导逗号,拼接在已有列之后)。
|
|
19
|
+
* 始终产出 3 列:新库选真实列,旧库用 `NULL AS`,保证调用方行索引布局不变。
|
|
20
|
+
*/
|
|
21
|
+
export declare function provenanceSelectFragment(db: SqliteDb): string;
|
|
22
|
+
//# sourceMappingURL=schema-compat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../../src/scaffold-kb/schema-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAGnD,gEAAgE;AAChE,eAAO,MAAM,kBAAkB,iEAAkE,CAAC;AAElG,wDAAwD;AACxD,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAK1D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,CAK7D"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F192 — chunk_meta schema 向后兼容(C-1 / R-COMPAT-1)
|
|
3
|
+
*
|
|
4
|
+
* F190 已分发的 sqlite(厂商/demo 旧库)无 provenance 三列(ingest_source_type/
|
|
5
|
+
* ingest_origin/ingested_at)。对其显式 SELECT 这些列会 `no such column` 抛错。
|
|
6
|
+
*
|
|
7
|
+
* 本模块用 `PRAGMA table_info(chunk_meta)` 探测列是否存在,生成"始终产出 3 列"的
|
|
8
|
+
* SELECT 片段(旧库用 `NULL AS`)—— 使行索引布局对新旧库稳定,所有读取路径
|
|
9
|
+
* (search-core / kb_search / kb_doc_lookup / kb_api_lookup 的 provenance 投影)
|
|
10
|
+
* 一律走本片段,禁止任何处手写新列名(plan §6)。
|
|
11
|
+
*/
|
|
12
|
+
import { queryRows } from './sqlite-engine.js';
|
|
13
|
+
/** provenance 列名(F192 §3.3);与 sqlite-writer CREATE_META 保持一致 */
|
|
14
|
+
export const PROVENANCE_COLUMNS = ['ingest_source_type', 'ingest_origin', 'ingested_at'];
|
|
15
|
+
/** 探测 chunk_meta 是否含全部 provenance 列(F190 旧库返回 false) */
|
|
16
|
+
export function hasProvenanceColumns(db) {
|
|
17
|
+
// PRAGMA table_info 返回行:cid, name, type, notnull, dflt_value, pk
|
|
18
|
+
const rows = queryRows(db, 'PRAGMA table_info(chunk_meta)', []);
|
|
19
|
+
const cols = new Set(rows.map((r) => String(r[1] ?? '')));
|
|
20
|
+
return PROVENANCE_COLUMNS.every((c) => cols.has(c));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 生成 provenance 的 SELECT 片段(含前导逗号,拼接在已有列之后)。
|
|
24
|
+
* 始终产出 3 列:新库选真实列,旧库用 `NULL AS`,保证调用方行索引布局不变。
|
|
25
|
+
*/
|
|
26
|
+
export function provenanceSelectFragment(db) {
|
|
27
|
+
if (hasProvenanceColumns(db)) {
|
|
28
|
+
return ', chunk_meta.ingest_source_type, chunk_meta.ingest_origin, chunk_meta.ingested_at';
|
|
29
|
+
}
|
|
30
|
+
return ', NULL AS ingest_source_type, NULL AS ingest_origin, NULL AS ingested_at';
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=schema-compat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-compat.js","sourceRoot":"","sources":["../../src/scaffold-kb/schema-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,gEAAgE;AAChE,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,oBAAoB,EAAE,eAAe,EAAE,aAAa,CAAU,CAAC;AAElG,wDAAwD;AACxD,MAAM,UAAU,oBAAoB,CAAC,EAAY;IAC/C,iEAAiE;IACjE,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,EAAE,+BAA+B,EAAE,EAAE,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1D,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,EAAY;IACnD,IAAI,oBAAoB,CAAC,EAAE,CAAC,EAAE,CAAC;QAC7B,OAAO,mFAAmF,CAAC;IAC7F,CAAC;IACD,OAAO,0EAA0E,CAAC;AACpF,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F190 scaffold-kb — 单库查询核 searchKbCore(Phase A,recall 验收依赖此,非 kb_search)
|
|
3
|
+
*
|
|
4
|
+
* 修 Codex C-2(Phase A/B 边界):recall 门禁在 Phase A,故查询核也在 Phase A。
|
|
5
|
+
* kb_search(Phase B)= 包装本核 + 双库 result-merger + envelope + telemetry + MCP 注册。
|
|
6
|
+
*
|
|
7
|
+
* 流程:sanitizeQuery → FTS5 MATCH(bm25 排序,越负越相关)→ chunk_meta 联表
|
|
8
|
+
* → 短 CJK 查询 LIKE 兜底(EC-001)。返回带原始 bm25 score 的结果(供上层归一)。
|
|
9
|
+
*/
|
|
10
|
+
import type { SqliteDb } from './sqlite-engine.js';
|
|
11
|
+
import type { IngestSourceType } from './types.js';
|
|
12
|
+
export interface CoreResult {
|
|
13
|
+
chunkId: string;
|
|
14
|
+
docId: string;
|
|
15
|
+
contentRaw: string;
|
|
16
|
+
docTitle: string;
|
|
17
|
+
anchor: string | null;
|
|
18
|
+
sourceUrl: string | null;
|
|
19
|
+
sdkVersion: string | null;
|
|
20
|
+
builtAt: string;
|
|
21
|
+
/** F192 provenance(旧库 / 厂商构建为 null,经 schema-compat 投影,C-1) */
|
|
22
|
+
ingestSourceType: IngestSourceType | null;
|
|
23
|
+
ingestOrigin: string | null;
|
|
24
|
+
ingestedAt: string | null;
|
|
25
|
+
/** 原始 bm25 分(越负越相关);LIKE 兜底命中记为 1.0(最不相关) */
|
|
26
|
+
score: number;
|
|
27
|
+
via: 'fts' | 'like';
|
|
28
|
+
}
|
|
29
|
+
export interface SearchCoreError {
|
|
30
|
+
ok: false;
|
|
31
|
+
code: 'INVALID_QUERY';
|
|
32
|
+
}
|
|
33
|
+
export interface SearchCoreOk {
|
|
34
|
+
ok: true;
|
|
35
|
+
results: CoreResult[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 单库检索核。
|
|
39
|
+
* @returns ok+results(按相关度排序,FTS 优先、LIKE 兜底在后)或 INVALID_QUERY 错误
|
|
40
|
+
*/
|
|
41
|
+
export declare function searchKbCore(db: SqliteDb, query: string, topK: number, sdkVersion?: string, preTokenized?: boolean): SearchCoreOk | SearchCoreError;
|
|
42
|
+
//# sourceMappingURL=search-core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-core.d.ts","sourceRoot":"","sources":["../../src/scaffold-kb/search-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAKnD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,KAAK,GAAG,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,eAAe,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAwCD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,EACnB,YAAY,UAAQ,GACnB,YAAY,GAAG,eAAe,CA4ChC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F190 scaffold-kb — 单库查询核 searchKbCore(Phase A,recall 验收依赖此,非 kb_search)
|
|
3
|
+
*
|
|
4
|
+
* 修 Codex C-2(Phase A/B 边界):recall 门禁在 Phase A,故查询核也在 Phase A。
|
|
5
|
+
* kb_search(Phase B)= 包装本核 + 双库 result-merger + envelope + telemetry + MCP 注册。
|
|
6
|
+
*
|
|
7
|
+
* 流程:sanitizeQuery → FTS5 MATCH(bm25 排序,越负越相关)→ chunk_meta 联表
|
|
8
|
+
* → 短 CJK 查询 LIKE 兜底(EC-001)。返回带原始 bm25 score 的结果(供上层归一)。
|
|
9
|
+
*/
|
|
10
|
+
import { queryRows } from './sqlite-engine.js';
|
|
11
|
+
import { sanitizeQuery } from './query-sanitizer.js';
|
|
12
|
+
import { provenanceSelectFragment } from './schema-compat.js';
|
|
13
|
+
const HAN = /\p{Script=Han}/gu;
|
|
14
|
+
const LIKE_FALLBACK_FLOOR = 3;
|
|
15
|
+
const SELECT_COLS = `chunks.chunk_id, chunks.doc_id, chunks.content_raw,
|
|
16
|
+
chunk_meta.doc_title, chunk_meta.anchor, chunk_meta.source_url,
|
|
17
|
+
chunk_meta.sdk_version, chunk_meta.built_at`;
|
|
18
|
+
/** 转义 LIKE 通配符 `%` `_` `\`,配合 `ESCAPE '\'`(修 Codex WARNING:召回污染) */
|
|
19
|
+
function escapeLike(s) {
|
|
20
|
+
return s.replace(/[\\%_]/g, '\\$&');
|
|
21
|
+
}
|
|
22
|
+
function rowToResult(row, score, via) {
|
|
23
|
+
const str = (v) => (v == null ? '' : String(v));
|
|
24
|
+
const strOrNull = (v) => (v == null ? null : String(v));
|
|
25
|
+
return {
|
|
26
|
+
chunkId: str(row[0]),
|
|
27
|
+
docId: str(row[1]),
|
|
28
|
+
contentRaw: str(row[2]),
|
|
29
|
+
docTitle: str(row[3]),
|
|
30
|
+
anchor: strOrNull(row[4]),
|
|
31
|
+
sourceUrl: strOrNull(row[5]),
|
|
32
|
+
sdkVersion: strOrNull(row[6]),
|
|
33
|
+
builtAt: str(row[7]),
|
|
34
|
+
// provenance(schema-compat 片段固定产出 3 列:索引 8/9/10)
|
|
35
|
+
ingestSourceType: strOrNull(row[8]),
|
|
36
|
+
ingestOrigin: strOrNull(row[9]),
|
|
37
|
+
ingestedAt: strOrNull(row[10]),
|
|
38
|
+
score,
|
|
39
|
+
via,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** 统计查询中的 CJK 字符数(决定是否触发短词 LIKE 兜底) */
|
|
43
|
+
function countHan(query) {
|
|
44
|
+
const m = query.match(HAN);
|
|
45
|
+
return m ? m.length : 0;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 单库检索核。
|
|
49
|
+
* @returns ok+results(按相关度排序,FTS 优先、LIKE 兜底在后)或 INVALID_QUERY 错误
|
|
50
|
+
*/
|
|
51
|
+
export function searchKbCore(db, query, topK, sdkVersion, preTokenized = false) {
|
|
52
|
+
const sanitized = sanitizeQuery(query, 'OR', preTokenized);
|
|
53
|
+
if (!sanitized.ok) {
|
|
54
|
+
return { ok: false, code: 'INVALID_QUERY' };
|
|
55
|
+
}
|
|
56
|
+
// provenance 投影片段(始终 3 列,旧库 NULL AS;C-1 兼容)
|
|
57
|
+
const prov = provenanceSelectFragment(db);
|
|
58
|
+
// 可选 sdk_version 过滤(FR-007,修 Codex WARNING:原 sdk_version 暴露但未用)
|
|
59
|
+
const versionClause = sdkVersion !== undefined ? ' AND chunk_meta.sdk_version = ?' : '';
|
|
60
|
+
// 列顺序:SELECT_COLS(0-7) + prov(8-10) + bm25 score(11)
|
|
61
|
+
const ftsSql = `SELECT ${SELECT_COLS}${prov}, bm25(chunks) AS score
|
|
62
|
+
FROM chunks JOIN chunk_meta ON chunk_meta.chunk_id = chunks.chunk_id
|
|
63
|
+
WHERE chunks MATCH ?${versionClause}
|
|
64
|
+
ORDER BY bm25(chunks)
|
|
65
|
+
LIMIT ?`;
|
|
66
|
+
const ftsBind = sdkVersion !== undefined ? [sanitized.match, sdkVersion, topK] : [sanitized.match, topK];
|
|
67
|
+
const ftsRows = queryRows(db, ftsSql, ftsBind);
|
|
68
|
+
const results = ftsRows.map((row) => rowToResult(row, Number(row[11] ?? 0), 'fts'));
|
|
69
|
+
// EC-001:短 CJK 查询(≤2 字)且 FTS 命中不足 → content_raw LIKE 兜底(转义通配符),合并去重
|
|
70
|
+
const hanCount = countHan(query);
|
|
71
|
+
if (hanCount > 0 && hanCount <= 2 && results.length < LIKE_FALLBACK_FLOOR) {
|
|
72
|
+
const likeSql = `SELECT ${SELECT_COLS}${prov}
|
|
73
|
+
FROM chunks JOIN chunk_meta ON chunk_meta.chunk_id = chunks.chunk_id
|
|
74
|
+
WHERE chunks.content_raw LIKE ? ESCAPE '\\'${versionClause}
|
|
75
|
+
LIMIT ?`;
|
|
76
|
+
const likePattern = `%${escapeLike(query.trim())}%`;
|
|
77
|
+
const likeBind = sdkVersion !== undefined ? [likePattern, sdkVersion, topK] : [likePattern, topK];
|
|
78
|
+
const seen = new Set(results.map((r) => r.chunkId));
|
|
79
|
+
for (const row of queryRows(db, likeSql, likeBind)) {
|
|
80
|
+
const chunkId = String(row[0] ?? '');
|
|
81
|
+
if (!seen.has(chunkId)) {
|
|
82
|
+
seen.add(chunkId);
|
|
83
|
+
results.push(rowToResult(row, 1.0, 'like'));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { ok: true, results: results.slice(0, topK) };
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=search-core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-core.js","sourceRoot":"","sources":["../../src/scaffold-kb/search-core.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F190 scaffold-kb — @sqlite.org/sqlite-wasm 封装(WASM 单例 + 内存 DB 落盘/加载)
|
|
3
|
+
*
|
|
4
|
+
* 实证(codebase-grounding §6):官方 sqlite-wasm 含 FTS5;纯内存 DB 经
|
|
5
|
+
* `sqlite3_js_db_export` 导出字节落盘,经 `sqlite3_deserialize` 从字节重建。
|
|
6
|
+
* WASM module 初始化较重,进程内缓存为单例(cold/warm 性能前提,plan §4.9)。
|
|
7
|
+
*
|
|
8
|
+
* sqlite-wasm 的上层 oo1 API 有类型声明,但低层 capi/wasm 句柄类型不全,
|
|
9
|
+
* 故在本模块内用最小本地接口收口这些调用,避免污染上层类型。
|
|
10
|
+
*/
|
|
11
|
+
/** 一行查询结果(列值数组) */
|
|
12
|
+
export type Row = unknown[];
|
|
13
|
+
/** 对 sqlite-wasm oo1.DB 的最小类型视图(只声明本模块用到的成员) */
|
|
14
|
+
export interface SqliteDb {
|
|
15
|
+
exec(opts: {
|
|
16
|
+
sql: string;
|
|
17
|
+
bind?: unknown[];
|
|
18
|
+
rowMode?: 'array' | 'object';
|
|
19
|
+
resultRows?: Row[];
|
|
20
|
+
}): void;
|
|
21
|
+
exec(sql: string): void;
|
|
22
|
+
close(): void;
|
|
23
|
+
readonly pointer: number;
|
|
24
|
+
checkRc(rc: number): void;
|
|
25
|
+
}
|
|
26
|
+
/** 低层 capi/wasm 句柄(sqlite-wasm 未完整声明的部分,本地最小收口) */
|
|
27
|
+
interface Sqlite3Api {
|
|
28
|
+
oo1: {
|
|
29
|
+
DB: new (filename?: string) => SqliteDb;
|
|
30
|
+
};
|
|
31
|
+
capi: {
|
|
32
|
+
sqlite3_js_db_export(db: SqliteDb): Uint8Array;
|
|
33
|
+
sqlite3_deserialize(dbPtr: number, schema: string, data: number, szDb: number, szBuf: number, flags: number): number;
|
|
34
|
+
SQLITE_DESERIALIZE_FREEONCLOSE: number;
|
|
35
|
+
SQLITE_DESERIALIZE_RESIZEABLE: number;
|
|
36
|
+
};
|
|
37
|
+
wasm: {
|
|
38
|
+
allocFromTypedArray(bytes: Uint8Array): number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** 初始化(或复用)WASM module 单例 */
|
|
42
|
+
export declare function initSqlite(): Promise<Sqlite3Api>;
|
|
43
|
+
/** 打开一个空的内存 DB */
|
|
44
|
+
export declare function openMemoryDb(): Promise<{
|
|
45
|
+
sqlite3: Sqlite3Api;
|
|
46
|
+
db: SqliteDb;
|
|
47
|
+
}>;
|
|
48
|
+
/** 把内存 DB 导出为字节(供落盘 chunks.sqlite) */
|
|
49
|
+
export declare function exportDb(sqlite3: Sqlite3Api, db: SqliteDb): Uint8Array;
|
|
50
|
+
/** 从字节重建内存 DB(供 serve 加载已构建的 chunks.sqlite) */
|
|
51
|
+
export declare function loadDbFromBytes(bytes: Uint8Array): Promise<{
|
|
52
|
+
sqlite3: Sqlite3Api;
|
|
53
|
+
db: SqliteDb;
|
|
54
|
+
}>;
|
|
55
|
+
/** 便捷查询:返回行数组 */
|
|
56
|
+
export declare function queryRows(db: SqliteDb, sql: string, bind?: unknown[]): Row[];
|
|
57
|
+
export {};
|
|
58
|
+
//# sourceMappingURL=sqlite-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-engine.d.ts","sourceRoot":"","sources":["../../src/scaffold-kb/sqlite-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,mBAAmB;AACnB,MAAM,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;AAE5B,gDAAgD;AAChD,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,IAAI,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;QACjB,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;QAC7B,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC;KACpB,GAAG,IAAI,CAAC;IACT,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,mDAAmD;AACnD,UAAU,UAAU;IAClB,GAAG,EAAE;QAAE,EAAE,EAAE,KAAK,QAAQ,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAA;KAAE,CAAC;IACjD,IAAI,EAAE;QACJ,oBAAoB,CAAC,EAAE,EAAE,QAAQ,GAAG,UAAU,CAAC;QAC/C,mBAAmB,CACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC;QACV,8BAA8B,EAAE,MAAM,CAAC;QACvC,6BAA6B,EAAE,MAAM,CAAC;KACvC,CAAC;IACF,IAAI,EAAE;QAAE,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAAA;KAAE,CAAC;CAC1D;AAID,6BAA6B;AAC7B,wBAAsB,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,CAOtD;AAED,kBAAkB;AAClB,wBAAsB,YAAY,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,QAAQ,CAAA;CAAE,CAAC,CAInF;AAED,sCAAsC;AACtC,wBAAgB,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,GAAG,UAAU,CAEtE;AAED,+CAA+C;AAC/C,wBAAsB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,QAAQ,CAAA;CAAE,CAAC,CA4BvG;AAED,iBAAiB;AACjB,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,EAAE,CAI5E"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F190 scaffold-kb — @sqlite.org/sqlite-wasm 封装(WASM 单例 + 内存 DB 落盘/加载)
|
|
3
|
+
*
|
|
4
|
+
* 实证(codebase-grounding §6):官方 sqlite-wasm 含 FTS5;纯内存 DB 经
|
|
5
|
+
* `sqlite3_js_db_export` 导出字节落盘,经 `sqlite3_deserialize` 从字节重建。
|
|
6
|
+
* WASM module 初始化较重,进程内缓存为单例(cold/warm 性能前提,plan §4.9)。
|
|
7
|
+
*
|
|
8
|
+
* sqlite-wasm 的上层 oo1 API 有类型声明,但低层 capi/wasm 句柄类型不全,
|
|
9
|
+
* 故在本模块内用最小本地接口收口这些调用,避免污染上层类型。
|
|
10
|
+
*/
|
|
11
|
+
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
|
|
12
|
+
let modulePromise = null;
|
|
13
|
+
/** 初始化(或复用)WASM module 单例 */
|
|
14
|
+
export async function initSqlite() {
|
|
15
|
+
if (modulePromise === null) {
|
|
16
|
+
modulePromise = sqlite3InitModule().then((m) => m);
|
|
17
|
+
}
|
|
18
|
+
return modulePromise;
|
|
19
|
+
}
|
|
20
|
+
/** 打开一个空的内存 DB */
|
|
21
|
+
export async function openMemoryDb() {
|
|
22
|
+
const sqlite3 = await initSqlite();
|
|
23
|
+
const db = new sqlite3.oo1.DB(':memory:');
|
|
24
|
+
return { sqlite3, db };
|
|
25
|
+
}
|
|
26
|
+
/** 把内存 DB 导出为字节(供落盘 chunks.sqlite) */
|
|
27
|
+
export function exportDb(sqlite3, db) {
|
|
28
|
+
return sqlite3.capi.sqlite3_js_db_export(db);
|
|
29
|
+
}
|
|
30
|
+
/** 从字节重建内存 DB(供 serve 加载已构建的 chunks.sqlite) */
|
|
31
|
+
export async function loadDbFromBytes(bytes) {
|
|
32
|
+
const sqlite3 = await initSqlite();
|
|
33
|
+
const db = new sqlite3.oo1.DB();
|
|
34
|
+
// Node 的 readFileSync 返回 Buffer,其底层 ArrayBuffer 是池化的(byteOffset≠0 / 超长),
|
|
35
|
+
// 直接喂给 allocFromTypedArray 会触发 heapForSize 错误。复制为紧凑 Uint8Array 规避。
|
|
36
|
+
const tight = new Uint8Array(bytes.byteLength);
|
|
37
|
+
tight.set(bytes);
|
|
38
|
+
try {
|
|
39
|
+
const ptr = sqlite3.wasm.allocFromTypedArray(tight);
|
|
40
|
+
const rc = sqlite3.capi.sqlite3_deserialize(db.pointer, 'main', ptr, tight.byteLength, tight.byteLength, sqlite3.capi.SQLITE_DESERIALIZE_FREEONCLOSE | sqlite3.capi.SQLITE_DESERIALIZE_RESIZEABLE);
|
|
41
|
+
db.checkRc(rc);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
// 失败时关闭已创建的 DB 句柄,避免 WASM 内存泄漏(修 Codex WARNING)
|
|
45
|
+
try {
|
|
46
|
+
db.close();
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// close 本身失败忽略,优先抛原始错误
|
|
50
|
+
}
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
return { sqlite3, db };
|
|
54
|
+
}
|
|
55
|
+
/** 便捷查询:返回行数组 */
|
|
56
|
+
export function queryRows(db, sql, bind) {
|
|
57
|
+
const resultRows = [];
|
|
58
|
+
db.exec({ sql, bind: bind ?? [], rowMode: 'array', resultRows });
|
|
59
|
+
return resultRows;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=sqlite-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-engine.js","sourceRoot":"","sources":["../../src/scaffold-kb/sqlite-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,iBAAiB,MAAM,yBAAyB,CAAC;AAsCxD,IAAI,aAAa,GAA+B,IAAI,CAAC;AAErD,6BAA6B;AAC7B,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;QAC3B,aAAa,GAAI,iBAAiB,EAAuB,CAAC,IAAI,CAC5D,CAAC,CAAC,EAAE,EAAE,CAAC,CAAe,CACvB,CAAC;IACJ,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,kBAAkB;AAClB,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,OAAO,GAAG,MAAM,UAAU,EAAE,CAAC;IACnC,MAAM,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;IAC1C,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AACzB,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,QAAQ,CAAC,OAAmB,EAAE,EAAY;IACxD,OAAO,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,+CAA+C;AAC/C,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAiB;IACrD,MAAM,OAAO,GAAG,MAAM,UAAU,EAAE,CAAC;IACnC,MAAM,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;IAChC,yEAAyE;IACzE,mEAAmE;IACnE,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/C,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACpD,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,mBAAmB,CACzC,EAAE,CAAC,OAAO,EACV,MAAM,EACN,GAAG,EACH,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,UAAU,EAChB,OAAO,CAAC,IAAI,CAAC,8BAA8B,GAAG,OAAO,CAAC,IAAI,CAAC,6BAA6B,CACzF,CAAC;QACF,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,gDAAgD;QAChD,IAAI,CAAC;YACH,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AACzB,CAAC;AAED,iBAAiB;AACjB,MAAM,UAAU,SAAS,CAAC,EAAY,EAAE,GAAW,EAAE,IAAgB;IACnE,MAAM,UAAU,GAAU,EAAE,CAAC;IAC7B,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;IACjE,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F190 scaffold-kb — chunks.sqlite 写入(FTS5 建表 + 写入 + 落盘)
|
|
3
|
+
*
|
|
4
|
+
* 表结构(plan §4.1.2 / spec §3.3):
|
|
5
|
+
* - `chunks` FTS5 虚拟表:chunk_id/doc_id/content_raw 为 UNINDEXED(不参与匹配,
|
|
6
|
+
* content_raw 供 envelope 原样返回,R-003/Codex C4);content_tokenized 是唯一检索列
|
|
7
|
+
* (写入前过 normalizeForIndex,与查询侧同构)
|
|
8
|
+
* - `chunk_meta` 普通表:冗余 doc_title/source_url,使 kb_search 不依赖 doc-graph.json
|
|
9
|
+
*/
|
|
10
|
+
import type { Chunk, ChunkMeta } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* 在内存 DB 构建 chunks + chunk_meta,导出为字节。
|
|
13
|
+
* 纯函数(不落盘),便于测试。
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildChunksDbBytes(chunks: Chunk[], meta: ChunkMeta[]): Promise<Uint8Array>;
|
|
16
|
+
/** 构建并落盘 chunks.sqlite 到 outputPath */
|
|
17
|
+
export declare function writeChunksToSqlite(chunks: Chunk[], meta: ChunkMeta[], outputPath: string): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=sqlite-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-writer.d.ts","sourceRoot":"","sources":["../../src/scaffold-kb/sqlite-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA0BnD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,KAAK,EAAE,EACf,IAAI,EAAE,SAAS,EAAE,GAChB,OAAO,CAAC,UAAU,CAAC,CAiCrB;AAED,uCAAuC;AACvC,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,KAAK,EAAE,EACf,IAAI,EAAE,SAAS,EAAE,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F190 scaffold-kb — chunks.sqlite 写入(FTS5 建表 + 写入 + 落盘)
|
|
3
|
+
*
|
|
4
|
+
* 表结构(plan §4.1.2 / spec §3.3):
|
|
5
|
+
* - `chunks` FTS5 虚拟表:chunk_id/doc_id/content_raw 为 UNINDEXED(不参与匹配,
|
|
6
|
+
* content_raw 供 envelope 原样返回,R-003/Codex C4);content_tokenized 是唯一检索列
|
|
7
|
+
* (写入前过 normalizeForIndex,与查询侧同构)
|
|
8
|
+
* - `chunk_meta` 普通表:冗余 doc_title/source_url,使 kb_search 不依赖 doc-graph.json
|
|
9
|
+
*/
|
|
10
|
+
import { writeFileSync } from 'node:fs';
|
|
11
|
+
import { normalizeForIndex } from './tokenizer.js';
|
|
12
|
+
import { openMemoryDb, exportDb } from './sqlite-engine.js';
|
|
13
|
+
const CREATE_CHUNKS = `CREATE VIRTUAL TABLE chunks USING fts5(
|
|
14
|
+
chunk_id UNINDEXED,
|
|
15
|
+
doc_id UNINDEXED,
|
|
16
|
+
content_raw UNINDEXED,
|
|
17
|
+
content_tokenized,
|
|
18
|
+
tokenize = 'unicode61 remove_diacritics 1'
|
|
19
|
+
)`;
|
|
20
|
+
// F192:新建/项目库的 chunk_meta 含 provenance 三列(F190 旧库无,读取走 schema-compat)
|
|
21
|
+
const CREATE_META = `CREATE TABLE chunk_meta (
|
|
22
|
+
chunk_id TEXT PRIMARY KEY,
|
|
23
|
+
doc_id TEXT NOT NULL,
|
|
24
|
+
doc_title TEXT NOT NULL,
|
|
25
|
+
source_url TEXT,
|
|
26
|
+
anchor TEXT,
|
|
27
|
+
sdk_version TEXT,
|
|
28
|
+
built_at TEXT NOT NULL,
|
|
29
|
+
ingest_source_type TEXT,
|
|
30
|
+
ingest_origin TEXT,
|
|
31
|
+
ingested_at TEXT
|
|
32
|
+
)`;
|
|
33
|
+
/**
|
|
34
|
+
* 在内存 DB 构建 chunks + chunk_meta,导出为字节。
|
|
35
|
+
* 纯函数(不落盘),便于测试。
|
|
36
|
+
*/
|
|
37
|
+
export async function buildChunksDbBytes(chunks, meta) {
|
|
38
|
+
const { sqlite3, db } = await openMemoryDb();
|
|
39
|
+
try {
|
|
40
|
+
db.exec(CREATE_CHUNKS);
|
|
41
|
+
db.exec(CREATE_META);
|
|
42
|
+
for (const c of chunks) {
|
|
43
|
+
db.exec({
|
|
44
|
+
sql: 'INSERT INTO chunks(chunk_id, doc_id, content_raw, content_tokenized) VALUES(?,?,?,?)',
|
|
45
|
+
bind: [c.chunkId, c.docId, c.contentRaw, normalizeForIndex(c.contentRaw)],
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
for (const m of meta) {
|
|
49
|
+
db.exec({
|
|
50
|
+
sql: 'INSERT INTO chunk_meta(chunk_id, doc_id, doc_title, source_url, anchor, sdk_version, built_at, ingest_source_type, ingest_origin, ingested_at) VALUES(?,?,?,?,?,?,?,?,?,?)',
|
|
51
|
+
bind: [
|
|
52
|
+
m.chunkId,
|
|
53
|
+
m.docId,
|
|
54
|
+
m.docTitle,
|
|
55
|
+
m.sourceUrl,
|
|
56
|
+
m.anchor,
|
|
57
|
+
m.sdkVersion,
|
|
58
|
+
m.builtAt,
|
|
59
|
+
m.ingestSourceType ?? null,
|
|
60
|
+
m.ingestOrigin ?? null,
|
|
61
|
+
m.ingestedAt ?? null,
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return exportDb(sqlite3, db);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
db.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** 构建并落盘 chunks.sqlite 到 outputPath */
|
|
72
|
+
export async function writeChunksToSqlite(chunks, meta, outputPath) {
|
|
73
|
+
const bytes = await buildChunksDbBytes(chunks, meta);
|
|
74
|
+
writeFileSync(outputPath, bytes);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=sqlite-writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-writer.js","sourceRoot":"","sources":["../../src/scaffold-kb/sqlite-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE5D,MAAM,aAAa,GAAG;;;;;;EAMpB,CAAC;AAEH,sEAAsE;AACtE,MAAM,WAAW,GAAG;;;;;;;;;;;EAWlB,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAe,EACf,IAAiB;IAEjB,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,MAAM,YAAY,EAAE,CAAC;IAC7C,IAAI,CAAC;QACH,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACvB,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAErB,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,EAAE,CAAC,IAAI,CAAC;gBACN,GAAG,EAAE,sFAAsF;gBAC3F,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;aAC1E,CAAC,CAAC;QACL,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,EAAE,CAAC,IAAI,CAAC;gBACN,GAAG,EAAE,4KAA4K;gBACjL,IAAI,EAAE;oBACJ,CAAC,CAAC,OAAO;oBACT,CAAC,CAAC,KAAK;oBACP,CAAC,CAAC,QAAQ;oBACV,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,MAAM;oBACR,CAAC,CAAC,UAAU;oBACZ,CAAC,CAAC,OAAO;oBACT,CAAC,CAAC,gBAAgB,IAAI,IAAI;oBAC1B,CAAC,CAAC,YAAY,IAAI,IAAI;oBACtB,CAAC,CAAC,UAAU,IAAI,IAAI;iBACrB;aACF,CAAC,CAAC;QACL,CAAC;QACD,OAAO,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC/B,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,uCAAuC;AACvC,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAe,EACf,IAAiB,EACjB,UAAkB;IAElB,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACrD,aAAa,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F190 scaffold-kb — CJK/符号感知的索引规范化(唯一 canonical 形式)
|
|
3
|
+
*
|
|
4
|
+
* 写入侧与查询侧共用同一个 `tokenize` / `normalizeForIndex`,保证同构(plan §4.1.2)。
|
|
5
|
+
* 设计要点:
|
|
6
|
+
* - SQLite FTS5 默认 unicode61 不切中文、对 `.`/`-`/`_` 等符号按分隔处理;
|
|
7
|
+
* 故不依赖 tokenizer 的 CJK 感知,而是在写入/查询前把文本规范化成"空格分隔、
|
|
8
|
+
* unicode61 能逐 token 索引"的形式。
|
|
9
|
+
* - CJK 连续段 → unigram + bigram(单字保证短查询不零召回,bigram 提精度)。
|
|
10
|
+
* - ASCII 符号段(含 `._-<>@()/`)→ 各组件 + 拼接形(既能按组件查,也能按整符号查);
|
|
11
|
+
* 不保留原始 `sdk.Init()` 字面(unicode61 下必被切),不使用任何特殊编码。
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* 把文本切成有序 token 数组(写入侧与查询侧共用)。
|
|
15
|
+
*
|
|
16
|
+
* - 按字符类别(han / ascii / sep)切分为 run;sep run 丢弃
|
|
17
|
+
* - han run → unigram + bigram
|
|
18
|
+
* - ascii run → 组件 + 拼接形
|
|
19
|
+
*/
|
|
20
|
+
export declare function tokenize(rawText: string): string[];
|
|
21
|
+
/**
|
|
22
|
+
* 规范化为 FTS5 索引/查询用的空格分隔字符串。
|
|
23
|
+
* `normalizeForIndex(t) === tokenize(t).join(' ')`,写入与查询同构的单一事实源。
|
|
24
|
+
*/
|
|
25
|
+
export declare function normalizeForIndex(text: string): string;
|
|
26
|
+
//# sourceMappingURL=tokenizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenizer.d.ts","sourceRoot":"","sources":["../../src/scaffold-kb/tokenizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA2CH;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CA4BlD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD"}
|