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
|
@@ -20,10 +20,11 @@ import { estimateModuleCost, buildDryRunReport, renderDryRunReport, runBudgetGat
|
|
|
20
20
|
import { decideModelOverride } from './model-override-decision.js';
|
|
21
21
|
import { createLogger } from '../panoramic/utils/logger.js';
|
|
22
22
|
import { groupFilesToModules } from './module-grouper.js';
|
|
23
|
+
import { buildSpecCacheKey, normalizeProjectPath, resolveRegenPlan, resolveSourceTarget } from './regen-plan.js';
|
|
23
24
|
import { groupFilesByLanguage } from './language-grouper.js';
|
|
24
|
-
import { scanFiles } from '../utils/file-scanner.js';
|
|
25
|
+
import { scanFiles, createGitignoreFilter } from '../utils/file-scanner.js';
|
|
25
26
|
import { LanguageAdapterRegistry } from '../adapters/language-adapter-registry.js';
|
|
26
|
-
import { buildDocGraph, runAnchorIntegration, runHyperedgeIntegration, scanStoredModuleSpecs, } from '../panoramic/builders/doc-graph-builder.js';
|
|
27
|
+
import { buildDocGraph, isBatchGenerated, runAnchorIntegration, runHyperedgeIntegration, scanStoredModuleSpecs, } from '../panoramic/builders/doc-graph-builder.js';
|
|
27
28
|
import { chunkMarkdownFiles } from '../panoramic/anchoring/chunker.js';
|
|
28
29
|
import { createEmbeddingProvider } from '../panoramic/anchoring/providers/factory.js';
|
|
29
30
|
import { DOC_NODE_KINDS } from '../panoramic/hyperedges/constants.js';
|
|
@@ -37,12 +38,13 @@ import { detectAuth } from '../auth/auth-detector.js';
|
|
|
37
38
|
import { orchestrateDocsBundle } from '../panoramic/pipelines/docs-bundle-orchestrator.js';
|
|
38
39
|
import { generateDebtIntelligence, } from '../panoramic/pipelines/debt-intelligence-pipeline.js';
|
|
39
40
|
import { BATCH_OUTPUT_SUBDIRS } from '../panoramic/output-filenames.js';
|
|
40
|
-
import { buildKnowledgeGraph, writeKnowledgeGraph } from '../panoramic/graph/index.js';
|
|
41
|
+
import { buildKnowledgeGraph, writeKnowledgeGraph, normalizeGraphForWrite } from '../panoramic/graph/index.js';
|
|
41
42
|
import { buildUnifiedGraph, setCurrentUnifiedGraph, getCurrentUnifiedGraph, } from '../knowledge-graph/index.js';
|
|
42
43
|
import { buildHtmlTemplate } from '../panoramic/exporters/html-template.js';
|
|
43
44
|
import { SpecStore } from '../spec-store/index.js';
|
|
44
45
|
import { createRequire } from 'node:module';
|
|
45
|
-
import { resolvePythonImport
|
|
46
|
+
import { resolvePythonImport } from '../knowledge-graph/import-resolver.js';
|
|
47
|
+
import { resolveTsJsImport, findNearestTsConfig, buildTsConfigContext, } from '../core/import-resolver.js';
|
|
46
48
|
// 从 package.json 读取版本号(避免硬编码)
|
|
47
49
|
const _require = createRequire(import.meta.url);
|
|
48
50
|
const SPECTRA_VERSION = _require('../../package.json').version;
|
|
@@ -64,6 +66,39 @@ const logger = createLogger('batch-orchestrator');
|
|
|
64
66
|
// ============================================================
|
|
65
67
|
// 内部辅助函数
|
|
66
68
|
// ============================================================
|
|
69
|
+
/**
|
|
70
|
+
* Feature 182 修复面 4:checkpoint replace 语义(completed/failed 互斥去重)。
|
|
71
|
+
*
|
|
72
|
+
* 背景:F175 给 checkpoint 加了「mustRegen 失效重跑」(fall-through)语义,但 completedModules
|
|
73
|
+
* 仍沿用 append-only 写法——已完成 module 被重跑后会二次 push,导致 resume 进度超 totalModules,
|
|
74
|
+
* 且 completed / failed 可能交叉污染(同一 module 同时出现在两个集合)。
|
|
75
|
+
*
|
|
76
|
+
* 本 helper 以 path 为身份键先剔除两个集合中的同名旧条目,再 push 到目标集合,保证:
|
|
77
|
+
* (1) 同一 module 在每个集合最多出现一次;(2) completed 与 failed 互斥。
|
|
78
|
+
*
|
|
79
|
+
* 注意:helper 内不得有 await——JS 单线程下同步段不被 pLimit 并发交错,
|
|
80
|
+
* 保持纯同步可防未来插入 await 后语义退化为「读-改-写」竞态。
|
|
81
|
+
*/
|
|
82
|
+
function upsertCompletedModule(state, entry) {
|
|
83
|
+
state.completedModules = state.completedModules.filter((m) => m.path !== entry.path);
|
|
84
|
+
state.failedModules = state.failedModules.filter((m) => m.path !== entry.path);
|
|
85
|
+
state.completedModules.push(entry);
|
|
86
|
+
}
|
|
87
|
+
function recordFailedModule(state, entry) {
|
|
88
|
+
state.failedModules = state.failedModules.filter((m) => m.path !== entry.path);
|
|
89
|
+
state.completedModules = state.completedModules.filter((m) => m.path !== entry.path);
|
|
90
|
+
state.failedModules.push(entry);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* F175 FR-017/EC-009:判定 absPath 是否位于受管 modules/ 输出目录内(孤儿删除 ownership 必要条件2)。
|
|
94
|
+
*
|
|
95
|
+
* 用 path.relative 判定目录归属,禁用字符串 startsWith——否则 `specs/modules-old/...` 这类
|
|
96
|
+
* sibling 目录会被前缀匹配误判为受管目录(目录穿越)。同时校验 .spec.md 后缀。
|
|
97
|
+
*/
|
|
98
|
+
function isInManagedOutputDir(absPath, modulesDir) {
|
|
99
|
+
const rel = path.relative(modulesDir, path.resolve(absPath));
|
|
100
|
+
return !rel.startsWith('..') && !path.isAbsolute(rel) && absPath.endsWith('.spec.md');
|
|
101
|
+
}
|
|
67
102
|
/**
|
|
68
103
|
* 合并多个语言的 ModuleGraph 用于全局拓扑排序
|
|
69
104
|
* 仅合并 modules 和 edges,SCC/Mermaid 按语言独立保留(TD-002 选项 C)
|
|
@@ -172,7 +207,14 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
172
207
|
const effectiveMode = options.mode ?? 'full';
|
|
173
208
|
// F5:FR-006 — 日志输出当前 mode
|
|
174
209
|
logger.info(`[info] batch mode: ${effectiveMode}`);
|
|
175
|
-
const {
|
|
210
|
+
const { maxRetries = 3, outputDir = 'specs', } = options;
|
|
211
|
+
// F175 FR-002:唯一默认值来源——对直接调用方兜底解析 RegenPlan(CLI/MCP 已在各自入口解析后传入有效值)。
|
|
212
|
+
// 删除原 `incremental = false` 硬编码,默认增量(regenPlan.incremental)由 resolveRegenPlan 决定(FR-001)。
|
|
213
|
+
const regenPlan = resolveRegenPlan({
|
|
214
|
+
incremental: options.incremental,
|
|
215
|
+
full: options.full,
|
|
216
|
+
force: options.force,
|
|
217
|
+
});
|
|
176
218
|
const startTime = Date.now();
|
|
177
219
|
const resolvedRoot = path.resolve(projectRoot);
|
|
178
220
|
const resolvedOutputDir = path.isAbsolute(outputDir)
|
|
@@ -187,7 +229,6 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
187
229
|
const rel = path.relative(resolvedRoot, absPath);
|
|
188
230
|
return rel.startsWith('..') ? absPath : rel;
|
|
189
231
|
};
|
|
190
|
-
const normalizeProjectPath = (inputPath) => inputPath.split(path.sep).join('/');
|
|
191
232
|
// 步骤 1:扫描文件获取 languageStats
|
|
192
233
|
const scanResult = scanFiles(resolvedRoot, { projectRoot: resolvedRoot });
|
|
193
234
|
const languageStats = scanResult.languageStats;
|
|
@@ -237,42 +278,34 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
237
278
|
const moduleGroups = new Map(groupResult.groups.map((g) => [g.name, g]));
|
|
238
279
|
const rootModuleName = options.grouping?.rootModuleName ?? 'root';
|
|
239
280
|
const existingStoredSpecs = scanStoredModuleSpecs(resolvedOutputDir, resolvedRoot);
|
|
240
|
-
|
|
281
|
+
// Feature 182:以 cache key 入索引(languageSplit 组带 `::language` 后缀),与 processOneModule
|
|
282
|
+
// 的 moduleCacheKey 口径对齐;旧 spec 无 sourceTargetKey 时回落纯路径 sourceTarget。
|
|
283
|
+
const storedSpecByTarget = new Map(existingStoredSpecs.map((spec) => [spec.sourceTargetKey ?? spec.sourceTarget, spec]));
|
|
284
|
+
// F175:全量逃生口(--full / --force 合并为 regenPlan.full)打可观测日志,
|
|
285
|
+
// 替代原 fallbackReason='force-enabled' 内部信号(W-1 取舍)。
|
|
286
|
+
if (regenPlan.full) {
|
|
287
|
+
logger.info(`[regen] full regeneration (source=${regenPlan.source})`);
|
|
288
|
+
}
|
|
289
|
+
// F175:仅增量路径(regenPlan.incremental)调用 DeltaRegenerator;full 路径直接走全量。
|
|
241
290
|
let deltaReport;
|
|
242
|
-
if (incremental) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
regenerateTargets: [],
|
|
254
|
-
directChanges: [],
|
|
255
|
-
propagatedChanges: [],
|
|
256
|
-
unchangedTargets: [],
|
|
257
|
-
fallbackReason: 'force-enabled',
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
const deltaRegenerator = new DeltaRegenerator();
|
|
262
|
-
deltaReport = await deltaRegenerator.plan({
|
|
263
|
-
projectRoot: resolvedRoot,
|
|
264
|
-
dependencyGraph: mergedGraph,
|
|
265
|
-
moduleGroups: groupResult.groups,
|
|
266
|
-
storedSpecs: existingStoredSpecs,
|
|
267
|
-
// Bug 142:传入 effectiveMode 启用 mode-aware cache,
|
|
268
|
-
// 旧 spec(无 generatedByMode)或 mode 不匹配时强制 cache miss。
|
|
269
|
-
effectiveMode,
|
|
270
|
-
});
|
|
271
|
-
}
|
|
291
|
+
if (regenPlan.incremental) {
|
|
292
|
+
const deltaRegenerator = new DeltaRegenerator();
|
|
293
|
+
deltaReport = await deltaRegenerator.plan({
|
|
294
|
+
projectRoot: resolvedRoot,
|
|
295
|
+
dependencyGraph: mergedGraph,
|
|
296
|
+
moduleGroups: groupResult.groups,
|
|
297
|
+
storedSpecs: existingStoredSpecs,
|
|
298
|
+
// Bug 142:传入 effectiveMode 启用 mode-aware cache,
|
|
299
|
+
// 旧 spec(无 generatedByMode)或 mode 不匹配时强制 cache miss。
|
|
300
|
+
effectiveMode,
|
|
301
|
+
});
|
|
272
302
|
}
|
|
273
303
|
const regenerateTargets = new Set(deltaReport?.regenerateTargets ?? []);
|
|
274
|
-
|
|
275
|
-
|
|
304
|
+
// EC-001 force/full 优先:regenPlan.full 直接全量;增量路径 deltaReport.mode==='full'
|
|
305
|
+
//(首次运行 / 无历史 spec / mode 切换)退化全量仍走 forceFullRegeneration。
|
|
306
|
+
// Feature 182:改为 let——full-resume 时序修复需在 checkpoint 加载后回写(见下方 isResume 块)。
|
|
307
|
+
let forceFullRegeneration = regenPlan.full || (regenPlan.incremental && deltaReport?.mode === 'full');
|
|
308
|
+
let shouldUseIncrementalPlan = regenPlan.incremental && deltaReport?.mode === 'incremental';
|
|
276
309
|
console.log(`发现 ${mergedGraph.modules.length} 个文件,聚合为 ${processingOrder.length} 个模块`);
|
|
277
310
|
if (isMultiLang) {
|
|
278
311
|
console.log(`检测到 ${processedLanguages.length} 种语言: ${processedLanguages.join(', ')}`);
|
|
@@ -363,6 +396,18 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
363
396
|
}
|
|
364
397
|
// 步骤 3:检查是否存在检查点
|
|
365
398
|
let state = loadCheckpoint(checkpointPath);
|
|
399
|
+
// F175 FR-016/EC-007:full/force 启动时丢弃已加载的 checkpoint completed + failed state,
|
|
400
|
+
// 防止残留 checkpoint 命中导致全量语义被绕过(必须在 completedPaths 构建前清空——
|
|
401
|
+
// 加载时即清,而非在 processOneModule 内"忽略",避免中途崩溃 resume 复用脏 set)。
|
|
402
|
+
// W-1:必须同时清空 failedModules——否则 full 后 summary(:1749 起用 checkedState.failedModules)
|
|
403
|
+
// 仍混入上一轮的旧失败详情,与"full 是干净全量"语义矛盾。
|
|
404
|
+
if (state &&
|
|
405
|
+
regenPlan.full &&
|
|
406
|
+
(state.completedModules.length > 0 || state.failedModules.length > 0)) {
|
|
407
|
+
logger.info(`[regen] full regeneration 丢弃残留 checkpoint state(completed ${state.completedModules.length} / failed ${state.failedModules.length} 模块)`);
|
|
408
|
+
state = { ...state, completedModules: [], failedModules: [] };
|
|
409
|
+
saveCheckpoint(state, checkpointPath);
|
|
410
|
+
}
|
|
366
411
|
const isResume = state !== null;
|
|
367
412
|
if (!state) {
|
|
368
413
|
state = {
|
|
@@ -374,7 +419,7 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
374
419
|
processingOrder,
|
|
375
420
|
completedModules: [],
|
|
376
421
|
failedModules: [],
|
|
377
|
-
forceRegenerate:
|
|
422
|
+
forceRegenerate: regenPlan.full,
|
|
378
423
|
// 多语言扩展字段
|
|
379
424
|
languageGroups: isMultiLang
|
|
380
425
|
? Object.fromEntries(languageGroupsList.map((g) => [g.adapterId, g.files]))
|
|
@@ -384,6 +429,17 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
384
429
|
}
|
|
385
430
|
if (isResume) {
|
|
386
431
|
console.log(`恢复断点: 已完成 ${state.completedModules.length}/${state.totalModules} 模块`);
|
|
432
|
+
// Feature 182 修复面 5:恢复中断的 --full run 意图。
|
|
433
|
+
// 背景:首轮 --full 把 forceRegenerate=true 落盘到 checkpoint(:660),但 runtime 仅用本轮
|
|
434
|
+
// regenPlan 重算的 forceFullRegeneration——裸增量 resume 时为 false,剩余模块静默降级为增量,
|
|
435
|
+
// 产出半新半旧混合产物。此处从 checkpoint 恢复 full 意图,剩余模块继续全量。
|
|
436
|
+
// 时序安全:两变量消费点全在 processOneModule 内(在此回写之后才被调用)。
|
|
437
|
+
// 注意:full-resume 不清空 completed(:636 清空条件用 regenPlan.full 而非本变量),正是 resume 语义。
|
|
438
|
+
if (state.forceRegenerate && !forceFullRegeneration) {
|
|
439
|
+
forceFullRegeneration = true;
|
|
440
|
+
shouldUseIncrementalPlan = false;
|
|
441
|
+
logger.info('[resume] 检测到中断的 full run,剩余模块继续全量');
|
|
442
|
+
}
|
|
387
443
|
}
|
|
388
444
|
// 步骤 4:按模块级拓扑顺序处理
|
|
389
445
|
// Feature 146 FR-010/FR-011:通过 getActiveCount getter 注入 limit.activeCount,
|
|
@@ -443,16 +499,30 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
443
499
|
const group = moduleGroups.get(moduleName);
|
|
444
500
|
if (!group)
|
|
445
501
|
return;
|
|
446
|
-
if (completedPaths.has(moduleName))
|
|
447
|
-
return;
|
|
448
502
|
const isRoot = moduleName === rootModuleName || moduleName.startsWith(`${rootModuleName}--`);
|
|
449
503
|
const specPath = path.join(modulesDir, `${moduleName}.spec.md`);
|
|
450
|
-
//
|
|
451
|
-
// 否则 --incremental 的 regenerateTargets 查询和 storedSpecByTarget
|
|
452
|
-
|
|
453
|
-
const moduleSourceTarget =
|
|
454
|
-
|
|
455
|
-
|
|
504
|
+
// FR-019:target 口径与 DeltaRegenerator 共用 resolveSourceTarget(文件级降级分支一致),
|
|
505
|
+
// 否则 --incremental 的 regenerateTargets 查询和 storedSpecByTarget 查询全部错位。
|
|
506
|
+
// T020:target 计算前移到 checkpoint 判定之前,供增量 resume 失效判定使用。
|
|
507
|
+
const moduleSourceTarget = resolveSourceTarget(group, conflictingDirPaths, isRoot);
|
|
508
|
+
// Feature 182:cache key(languageSplit 组带 `::language` 后缀),用于 regenerateTargets /
|
|
509
|
+
// storedSpecByTarget / existingVersion 查询,消除同目录多语言组键碰撞;targetPath 与
|
|
510
|
+
// frontmatter 继续用纯路径 moduleSourceTarget(statSync 路径 + panoramic 匹配口径不变)。
|
|
511
|
+
const moduleCacheKey = buildSpecCacheKey(moduleSourceTarget, group);
|
|
512
|
+
// F175 FR-016:checkpoint 判定。full 路径已在加载时清空 completedPaths(必为干净);
|
|
513
|
+
// 增量 resume 下若 checkpoint 命中但本轮 delta 要求重生成该 target,则失效重跑。
|
|
514
|
+
// C-3:root 模块的 regenerateTargets 是文件级(DeltaRegenerator 对 root 按每个文件
|
|
515
|
+
// 产出 snapshot),但 moduleSourceTarget(root 时为 group.dirPath)不在文件级集合里,
|
|
516
|
+
// 用 dirPath 查永远 miss → mustRegen=false → root 文件变更时 checkpoint 命中被错误跳过。
|
|
517
|
+
// 故 root 模块改判 group.files 中任一文件 target 是否命中 regenerateTargets。
|
|
518
|
+
if (completedPaths.has(moduleName)) {
|
|
519
|
+
const mustRegen = shouldUseIncrementalPlan &&
|
|
520
|
+
(isRoot
|
|
521
|
+
? group.files.some((filePath) => regenerateTargets.has(normalizeProjectPath(filePath)))
|
|
522
|
+
: regenerateTargets.has(moduleCacheKey));
|
|
523
|
+
if (!mustRegen)
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
456
526
|
const rootTargetsToGenerate = isRoot
|
|
457
527
|
? group.files
|
|
458
528
|
.map((filePath) => normalizeProjectPath(filePath))
|
|
@@ -475,7 +545,7 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
475
545
|
else {
|
|
476
546
|
const shouldGenerate = forceFullRegeneration
|
|
477
547
|
|| (shouldUseIncrementalPlan
|
|
478
|
-
? regenerateTargets.has(
|
|
548
|
+
? regenerateTargets.has(moduleCacheKey)
|
|
479
549
|
: !fs.existsSync(specPath));
|
|
480
550
|
if (!shouldGenerate) {
|
|
481
551
|
skipped.push(moduleName);
|
|
@@ -589,24 +659,32 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
589
659
|
}
|
|
590
660
|
successful.push(moduleName);
|
|
591
661
|
reporter.complete(moduleName, 'success');
|
|
592
|
-
|
|
662
|
+
// Feature 182:replace 语义(失效重跑时去重 + completed/failed 互斥)
|
|
663
|
+
upsertCompletedModule(checkedState, {
|
|
593
664
|
path: moduleName,
|
|
594
665
|
specPath: generatedRootSpecs[0],
|
|
595
666
|
completedAt: new Date().toISOString(),
|
|
596
667
|
});
|
|
597
668
|
}
|
|
598
669
|
else {
|
|
599
|
-
// BUG-A
|
|
600
|
-
//
|
|
601
|
-
|
|
602
|
-
// 注意:hasDirPathConflict 已在上方计算(H4 修复)
|
|
603
|
-
const targetPath = hasDirPathConflict
|
|
604
|
-
? path.join(resolvedRoot, group.files[0])
|
|
605
|
-
: path.join(resolvedRoot, group.dirPath);
|
|
670
|
+
// BUG-A / H4 修复:targetPath 直接由 moduleSourceTarget 推导(FR-019:resolveSourceTarget
|
|
671
|
+
// 已处理"同一 dirPath 多单文件冲突走文件路径"的降级分支),与 sourceTarget 口径保持一致。
|
|
672
|
+
const targetPath = path.join(resolvedRoot, moduleSourceTarget);
|
|
606
673
|
// genOptions.skipEnrichment 已在 L648 处理 reading/code-only 模式分派
|
|
607
674
|
const result = await generateSpec(targetPath, {
|
|
608
675
|
...genOptions,
|
|
609
|
-
|
|
676
|
+
// Feature 182:注入 group.files(语言限定子集,绝对路径),使写侧只分析本语言文件,
|
|
677
|
+
// 与读侧 group.files 文件集口径一致(消除混语言 hash 分叉 + 双倍分析)。
|
|
678
|
+
files: group.files.map((f) => path.join(resolvedRoot, f)),
|
|
679
|
+
existingVersion: storedSpecByTarget.get(moduleCacheKey)?.version,
|
|
680
|
+
// Feature 182 修复 1:仅 languageSplit 组按 moduleName 显式命名输出文件,
|
|
681
|
+
// 避免同目录多语言组(ts / py)都派生为同名 `<dir>.spec.md` 互相覆盖;
|
|
682
|
+
// 非拆分组不传 → basename 派生,存量单语言仓库命名零变化。
|
|
683
|
+
...(group.languageSplit ? { outputFileName: `${moduleName}.spec.md` } : {}),
|
|
684
|
+
// Feature 182 修复 2:languageSplit 组首写即落入 frontmatter.sourceTargetKey,
|
|
685
|
+
// 消除「post-mutation 依赖 doc-graph re-render 落盘晚于 checkpoint save」崩溃窗口;
|
|
686
|
+
// in-memory moduleSpec.frontmatter 由 generateFrontmatter 返回自然带上。
|
|
687
|
+
sourceTargetKey: group.languageSplit ? moduleCacheKey : undefined,
|
|
610
688
|
});
|
|
611
689
|
// Bug 142:非 root 分支累积 input token,供 catch 块统一做预算检查。
|
|
612
690
|
if (result.costMetadata?.tokenUsage.input) {
|
|
@@ -631,7 +709,8 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
631
709
|
successful.push(moduleName);
|
|
632
710
|
reporter.complete(moduleName, 'success');
|
|
633
711
|
}
|
|
634
|
-
|
|
712
|
+
// Feature 182:replace 语义(失效重跑时去重 + completed/failed 互斥)
|
|
713
|
+
upsertCompletedModule(checkedState, {
|
|
635
714
|
path: moduleName,
|
|
636
715
|
specPath: toProjectPath(path.resolve(result.specPath)),
|
|
637
716
|
completedAt: new Date().toISOString(),
|
|
@@ -686,7 +765,8 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
686
765
|
reason: 'retry-budget-exceeded',
|
|
687
766
|
};
|
|
688
767
|
failed.push(failedModule);
|
|
689
|
-
|
|
768
|
+
// Feature 182:replace 语义(失效重跑时去重 + completed/failed 互斥)
|
|
769
|
+
recordFailedModule(checkedState, failedModule);
|
|
690
770
|
reporter.complete(moduleName, 'failed');
|
|
691
771
|
break;
|
|
692
772
|
}
|
|
@@ -700,7 +780,8 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
700
780
|
degradedToAstOnly: false,
|
|
701
781
|
};
|
|
702
782
|
failed.push(failedModule);
|
|
703
|
-
|
|
783
|
+
// Feature 182:replace 语义(失效重跑时去重 + completed/failed 互斥)
|
|
784
|
+
recordFailedModule(checkedState, failedModule);
|
|
704
785
|
reporter.complete(moduleName, 'failed');
|
|
705
786
|
}
|
|
706
787
|
}
|
|
@@ -745,7 +826,8 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
745
826
|
reason: 'unhandled-exception',
|
|
746
827
|
};
|
|
747
828
|
failed.push(failedModule);
|
|
748
|
-
|
|
829
|
+
// Feature 182:replace 语义(失效重跑时去重 + completed/failed 互斥)
|
|
830
|
+
recordFailedModule(checkedState, failedModule);
|
|
749
831
|
try {
|
|
750
832
|
reporter.complete(moduleName, 'failed');
|
|
751
833
|
}
|
|
@@ -764,10 +846,63 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
764
846
|
let projectDocsResult;
|
|
765
847
|
let docsBundleManifestPath;
|
|
766
848
|
let docsBundleProfiles;
|
|
767
|
-
//
|
|
849
|
+
// F175 FR-017/EC-008/EC-009:删除孤儿 spec(源文件已删除的 batch 产物),使增量产物文件集与全量一致。
|
|
850
|
+
// 必须在构造 SpecStore 之前执行 + 从 storedSpecs 集合中剔除已删项(C-2)——否则后续
|
|
851
|
+
// 所有聚合(index/graph/coverage/cross-ref)仍引用陈旧的已删 spec 视图,产生 stale 输出。
|
|
852
|
+
// 仅在增量/全量路径执行;ownership 边界缺一不删:(1) generatedByMode 存在(batch 专属标记)+
|
|
853
|
+
// (2) 位于受管 modules/ 输出目录内(path.relative 防目录穿越,禁字符串 startsWith)+
|
|
854
|
+
// (3) sourceTarget 已无任何在库源文件(EC-008:删除模块最后一个源文件后,空目录仍 existsSync=true,
|
|
855
|
+
// 故不能仅靠目录存在性判定——以当前扫描到的源文件集合为准)。
|
|
856
|
+
let storedSpecsForStore = existingStoredSpecs;
|
|
857
|
+
if (regenPlan.incremental || regenPlan.full) {
|
|
858
|
+
// 当前扫描到的源文件(项目相对,正斜杠口径),用于判定 sourceTarget 是否仍有活跃源文件
|
|
859
|
+
const liveSourceFiles = new Set(scanResult.files.map((f) => normalizeProjectPath(f)));
|
|
860
|
+
const hasLiveSource = (sourceTarget) => {
|
|
861
|
+
const prefix = `${sourceTarget}/`;
|
|
862
|
+
for (const f of liveSourceFiles) {
|
|
863
|
+
if (f === sourceTarget || f.startsWith(prefix))
|
|
864
|
+
return true;
|
|
865
|
+
}
|
|
866
|
+
return false;
|
|
867
|
+
};
|
|
868
|
+
// 已确认删除的孤儿 outputPath 集合,用于从 storedSpecs 中剔除,使 SpecStore 不持有陈旧视图。
|
|
869
|
+
const deletedOrphanPaths = new Set();
|
|
870
|
+
for (const orphan of existingStoredSpecs) {
|
|
871
|
+
if (!isBatchGenerated(orphan))
|
|
872
|
+
continue; // 必要条件1:无 generatedByMode → 跳过(防误删手写 / 单文件 generate 产物)
|
|
873
|
+
if (hasLiveSource(orphan.sourceTarget))
|
|
874
|
+
continue; // 必要条件3:仍有活跃源文件 → 非孤儿
|
|
875
|
+
const absPath = path.isAbsolute(orphan.outputPath)
|
|
876
|
+
? orphan.outputPath
|
|
877
|
+
: path.join(resolvedRoot, orphan.outputPath);
|
|
878
|
+
if (!isInManagedOutputDir(absPath, modulesDir))
|
|
879
|
+
continue; // 必要条件2:受管 modules/ 目录外 → 跳过
|
|
880
|
+
// 磁盘文件已被本轮其它逻辑清理:视为已删除,从内存视图剔除(保持内存与磁盘一致)。
|
|
881
|
+
if (!fs.existsSync(absPath)) {
|
|
882
|
+
deletedOrphanPaths.add(orphan.outputPath);
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
// W-1(quality-review):rmSync 是破坏性新操作,权限/OS 错误不应让整个 batch 崩溃。
|
|
886
|
+
// 失败时 warn + 跳过,且【不】加入 deletedOrphanPaths——文件仍在磁盘,内存视图须保留它,
|
|
887
|
+
// 避免"内存说已删、磁盘还在"的不一致。仅删除成功才剔除内存视图。
|
|
888
|
+
try {
|
|
889
|
+
logger.info(`[orphan-cleanup] 删除孤儿 spec: ${orphan.outputPath}`);
|
|
890
|
+
fs.rmSync(absPath, { force: true });
|
|
891
|
+
deletedOrphanPaths.add(orphan.outputPath);
|
|
892
|
+
}
|
|
893
|
+
catch (err) {
|
|
894
|
+
logger.warn(`[orphan-cleanup] 删除孤儿 spec 失败,保守跳过: ${orphan.outputPath}(${err.message})`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
if (deletedOrphanPaths.size > 0) {
|
|
898
|
+
storedSpecsForStore = existingStoredSpecs.filter((spec) => !deletedOrphanPaths.has(spec.outputPath));
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
// SpecStore 统一查询入口:所有消费方(README、graph、coverage、index、cross-ref)共享此源。
|
|
902
|
+
// 使用已剔除孤儿的 storedSpecsForStore,确保后续聚合不引用已删 spec(C-2)。
|
|
768
903
|
const specStore = new SpecStore({
|
|
769
904
|
currentSpecs: collectedModuleSpecs,
|
|
770
|
-
storedSpecs:
|
|
905
|
+
storedSpecs: storedSpecsForStore,
|
|
771
906
|
projectRoot: resolvedRoot,
|
|
772
907
|
toProjectPath,
|
|
773
908
|
});
|
|
@@ -1115,6 +1250,11 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
1115
1250
|
catch (communityErr) {
|
|
1116
1251
|
logger.warn(`community-analysis: 社区分析失败,跳过报告生成: ${communityErr instanceof Error ? communityErr.message : String(communityErr)}`);
|
|
1117
1252
|
}
|
|
1253
|
+
// F175 FR-006/FR-007:写盘前归一化(byte-stable)——nodes/links/hyperedges 确定性排序,
|
|
1254
|
+
// 在追加 semantic edges + 社区分析之后调用,覆盖完整边集。
|
|
1255
|
+
// F179:stripTimestamps:true 使 graph.generatedAt 固定为 epoch,落盘 graph.json 真 byte-stable
|
|
1256
|
+
// (此前 F175 仅在测试读取侧 delete generatedAt,是 over-claim;现在落盘侧直接固定)。
|
|
1257
|
+
normalizeGraphForWrite(graphJson, { stripTimestamps: true });
|
|
1118
1258
|
// 社区分析完成后写盘(graphJson 已含 degree metadata)
|
|
1119
1259
|
const graphWrittenPath = writeKnowledgeGraph(graphJson, resolvedOutputDir);
|
|
1120
1260
|
docGraphPath = toProjectPath(graphWrittenPath);
|
|
@@ -1374,6 +1514,7 @@ export async function runBatch(projectRoot, options = {}) {
|
|
|
1374
1514
|
docGraphPath,
|
|
1375
1515
|
coverageReportPath,
|
|
1376
1516
|
deltaReportPath,
|
|
1517
|
+
deltaReport,
|
|
1377
1518
|
projectDocs,
|
|
1378
1519
|
docsBundleManifestPath,
|
|
1379
1520
|
docsBundleProfiles,
|
|
@@ -1616,8 +1757,10 @@ export async function collectPythonCodeSkeletons(projectRoot) {
|
|
|
1616
1757
|
// 避免调用方传相对路径 → Map key 与 imports[].resolvedPath 形态不一致
|
|
1617
1758
|
// → call-resolver buildImportIndex lookup miss
|
|
1618
1759
|
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
1760
|
+
// F194:构建 .gitignore 过滤器,基准 = resolvedProjectRoot(与 walk 内 path.relative 同口径)
|
|
1761
|
+
const isGitignored = createGitignoreFilter(resolvedProjectRoot);
|
|
1619
1762
|
const pyFiles = [];
|
|
1620
|
-
walkPyFiles(resolvedProjectRoot, pyFiles);
|
|
1763
|
+
walkPyFiles(resolvedProjectRoot, pyFiles, isGitignored, resolvedProjectRoot);
|
|
1621
1764
|
// Feature 152 P3 T-017:大文件 size guard 提前到 parse 之前(避免 tree-sitter 解析阻塞)
|
|
1622
1765
|
// 1MB 阈值与 PythonMapper.CALLSITES_MAX_FILE_BYTES 对齐(EC-14)
|
|
1623
1766
|
const MAX_FILE_BYTES = 1_000_000;
|
|
@@ -1695,7 +1838,11 @@ export async function collectPythonCodeSkeletons(projectRoot) {
|
|
|
1695
1838
|
}
|
|
1696
1839
|
return out;
|
|
1697
1840
|
}
|
|
1698
|
-
|
|
1841
|
+
/**
|
|
1842
|
+
* F194:isGitignored 与 resolvedRoot 由 collectPythonCodeSkeletons 构建并通过参数传入,
|
|
1843
|
+
* 在自写 walk 上叠加 .gitignore 过滤层(保留 PY_SKELETON_IGNORE_DIRS 与点前缀剪枝不变)。
|
|
1844
|
+
*/
|
|
1845
|
+
function walkPyFiles(dir, out, isGitignored, resolvedRoot) {
|
|
1699
1846
|
let entries;
|
|
1700
1847
|
try {
|
|
1701
1848
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -1704,14 +1851,20 @@ function walkPyFiles(dir, out) {
|
|
|
1704
1851
|
return;
|
|
1705
1852
|
}
|
|
1706
1853
|
for (const entry of entries) {
|
|
1854
|
+
// 基准 = resolvedRoot,path.relative 输出不做 sep 转换(与 file-scanner walkDir 一致)
|
|
1855
|
+
const relPath = path.relative(resolvedRoot, path.join(dir, entry.name));
|
|
1707
1856
|
if (entry.isDirectory()) {
|
|
1708
1857
|
if (entry.name.startsWith('.'))
|
|
1709
1858
|
continue;
|
|
1710
1859
|
if (PY_SKELETON_IGNORE_DIRS.has(entry.name))
|
|
1711
1860
|
continue;
|
|
1712
|
-
|
|
1861
|
+
if (isGitignored(relPath))
|
|
1862
|
+
continue; // 目录命中 .gitignore → 剪枝
|
|
1863
|
+
walkPyFiles(path.join(dir, entry.name), out, isGitignored, resolvedRoot);
|
|
1713
1864
|
}
|
|
1714
1865
|
else if (entry.isFile() && (entry.name.endsWith('.py') || entry.name.endsWith('.pyi'))) {
|
|
1866
|
+
if (isGitignored(relPath))
|
|
1867
|
+
continue; // 文件命中 .gitignore → 跳过
|
|
1715
1868
|
out.push(path.join(dir, entry.name));
|
|
1716
1869
|
}
|
|
1717
1870
|
}
|
|
@@ -1742,8 +1895,10 @@ export async function collectTsJsCodeSkeletons(projectRoot, options) {
|
|
|
1742
1895
|
// Codex P3+P4 复审 C-2 修复:projectRoot 显式 normalize 为绝对路径
|
|
1743
1896
|
// 避免调用方传相对路径 → Map key 与 imports[].resolvedPath 形态不一致
|
|
1744
1897
|
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
1898
|
+
// F194:构建 .gitignore 过滤器,基准 = resolvedProjectRoot(与 walk 内 path.relative 同口径)
|
|
1899
|
+
const isGitignored = createGitignoreFilter(resolvedProjectRoot);
|
|
1745
1900
|
const tsJsFiles = [];
|
|
1746
|
-
walkTsJsFiles(resolvedProjectRoot, tsJsFiles);
|
|
1901
|
+
walkTsJsFiles(resolvedProjectRoot, tsJsFiles, isGitignored, resolvedProjectRoot);
|
|
1747
1902
|
// T-021a:tsconfig context 缓存(by configDir),避免每个文件重复读
|
|
1748
1903
|
const tsConfigCache = new Map();
|
|
1749
1904
|
// 大文件 size guard 与 Python 版本对齐(EC-14:1MB 阈值)
|
|
@@ -1764,14 +1919,14 @@ export async function collectTsJsCodeSkeletons(projectRoot, options) {
|
|
|
1764
1919
|
const skeleton = await adapter.analyzeFile(filePath, {
|
|
1765
1920
|
extractCallSites: options?.extractCallSites,
|
|
1766
1921
|
});
|
|
1767
|
-
// T-021a:查找最近的 tsconfig.json 并缓存 context
|
|
1768
|
-
const
|
|
1922
|
+
// T-021a:查找最近的 tsconfig.json 并缓存 context(Feature 181:loader 收口为 configPath 单参)
|
|
1923
|
+
const configPath = findNearestTsConfig(filePath, resolvedProjectRoot);
|
|
1769
1924
|
let tsConfigContext = null;
|
|
1770
|
-
if (
|
|
1771
|
-
if (!tsConfigCache.has(
|
|
1772
|
-
tsConfigCache.set(
|
|
1925
|
+
if (configPath) {
|
|
1926
|
+
if (!tsConfigCache.has(configPath)) {
|
|
1927
|
+
tsConfigCache.set(configPath, buildTsConfigContext(configPath));
|
|
1773
1928
|
}
|
|
1774
|
-
tsConfigContext = tsConfigCache.get(
|
|
1929
|
+
tsConfigContext = tsConfigCache.get(configPath) ?? null;
|
|
1775
1930
|
}
|
|
1776
1931
|
// 解析 imports[].resolvedPath(EC-10:转绝对路径)
|
|
1777
1932
|
const resolvedSkeleton = {
|
|
@@ -1798,8 +1953,11 @@ export async function collectTsJsCodeSkeletons(projectRoot, options) {
|
|
|
1798
1953
|
/**
|
|
1799
1954
|
* 递归扫描 .ts/.tsx/.js/.jsx 文件(排除产物目录)。
|
|
1800
1955
|
* 复用 walkPyFiles 的扫描模式,扩展 TS/JS 扩展名集合。
|
|
1956
|
+
*
|
|
1957
|
+
* F194:isGitignored 与 resolvedRoot 由 collectTsJsCodeSkeletons 构建并通过参数传入,
|
|
1958
|
+
* 在自写 walk 上叠加 .gitignore 过滤层(保留 TSJS_SKELETON_IGNORE_DIRS 与点前缀剪枝不变)。
|
|
1801
1959
|
*/
|
|
1802
|
-
function walkTsJsFiles(dir, out) {
|
|
1960
|
+
function walkTsJsFiles(dir, out, isGitignored, resolvedRoot) {
|
|
1803
1961
|
let entries;
|
|
1804
1962
|
try {
|
|
1805
1963
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -1808,12 +1966,16 @@ function walkTsJsFiles(dir, out) {
|
|
|
1808
1966
|
return;
|
|
1809
1967
|
}
|
|
1810
1968
|
for (const entry of entries) {
|
|
1969
|
+
// 基准 = resolvedRoot,path.relative 输出不做 sep 转换(与 file-scanner walkDir 一致)
|
|
1970
|
+
const relPath = path.relative(resolvedRoot, path.join(dir, entry.name));
|
|
1811
1971
|
if (entry.isDirectory()) {
|
|
1812
1972
|
if (entry.name.startsWith('.'))
|
|
1813
1973
|
continue;
|
|
1814
1974
|
if (TSJS_SKELETON_IGNORE_DIRS.has(entry.name))
|
|
1815
1975
|
continue;
|
|
1816
|
-
|
|
1976
|
+
if (isGitignored(relPath))
|
|
1977
|
+
continue; // 目录命中 .gitignore → 剪枝
|
|
1978
|
+
walkTsJsFiles(path.join(dir, entry.name), out, isGitignored, resolvedRoot);
|
|
1817
1979
|
}
|
|
1818
1980
|
else if (entry.isFile()) {
|
|
1819
1981
|
const name = entry.name;
|
|
@@ -1821,9 +1983,83 @@ function walkTsJsFiles(dir, out) {
|
|
|
1821
1983
|
name.endsWith('.tsx') ||
|
|
1822
1984
|
name.endsWith('.js') ||
|
|
1823
1985
|
name.endsWith('.jsx')) {
|
|
1986
|
+
if (isGitignored(relPath))
|
|
1987
|
+
continue; // 文件命中 .gitignore → 跳过
|
|
1824
1988
|
out.push(path.join(dir, entry.name));
|
|
1825
1989
|
}
|
|
1826
1990
|
}
|
|
1827
1991
|
}
|
|
1828
1992
|
}
|
|
1993
|
+
/**
|
|
1994
|
+
* Feature 195:全仓纯 AST、零 LLM 的 graph-only 建图。
|
|
1995
|
+
*
|
|
1996
|
+
* 与 runBatch 解耦的姊妹管线——只跑 AST 采集 → unifiedGraph(call graph + depends-on)
|
|
1997
|
+
* + Python 符号节点 → buildKnowledgeGraph → writeKnowledgeGraph,全程不调用任何
|
|
1998
|
+
* spec-gen / enrichment / hyperedge LLM,也不需要认证。
|
|
1999
|
+
*
|
|
2000
|
+
* why 复用 collectPython/collectTsJs 而非 scanFiles:batch 的 unifiedGraph(见 runBatch
|
|
2001
|
+
* 内早期 UnifiedGraph 构建段)正是用这两个采集器构建,复用它们使 graph-only 的 unifiedGraph
|
|
2002
|
+
* 与 batch 口径逐一对齐(扩展名集合 + ignore 规则一致)。不按 languages 过滤,与 batch
|
|
2003
|
+
* unifiedGraph 行为一致。
|
|
2004
|
+
*
|
|
2005
|
+
* why writeKnowledgeGraph 传 stripTimestamps:true:与 batch 写盘一致,剥除 generatedAt
|
|
2006
|
+
* 实时戳,保 byte-stable 与跨 worktree 一致(F193/NFR-002)。
|
|
2007
|
+
*/
|
|
2008
|
+
export async function buildAstGraphOnly(projectRoot, options) {
|
|
2009
|
+
const startTime = Date.now();
|
|
2010
|
+
const resolvedRoot = path.resolve(projectRoot);
|
|
2011
|
+
const outputDir = options?.outputDir ?? 'specs';
|
|
2012
|
+
const resolvedOutputDir = path.isAbsolute(outputDir)
|
|
2013
|
+
? outputDir
|
|
2014
|
+
: path.join(resolvedRoot, outputDir);
|
|
2015
|
+
logger.info('[Feature 195] graph-only 建图:纯 AST / 零 LLM');
|
|
2016
|
+
// 步骤 1:AST 采集(与 batch unifiedGraph 同款采集器,合并 Python + TS/JS)
|
|
2017
|
+
const pythonSkeletons = await collectPythonCodeSkeletons(resolvedRoot);
|
|
2018
|
+
const tsJsSkeletons = await collectTsJsCodeSkeletons(resolvedRoot, {
|
|
2019
|
+
extractCallSites: true,
|
|
2020
|
+
});
|
|
2021
|
+
const codeSkeletons = new Map([...pythonSkeletons, ...tsJsSkeletons]);
|
|
2022
|
+
if (codeSkeletons.size === 0) {
|
|
2023
|
+
// EC-001:无可解析源码 → 仍产出 schema 合法的空图,不崩溃
|
|
2024
|
+
logger.warn('[Feature 195] 未发现可建图的源码(将产出空图)');
|
|
2025
|
+
}
|
|
2026
|
+
// 步骤 2:UnifiedGraph(calls + depends-on,纯 AST)
|
|
2027
|
+
const unifiedGraph = codeSkeletons.size > 0
|
|
2028
|
+
? buildUnifiedGraph({ projectRoot: resolvedRoot, codeSkeletons })
|
|
2029
|
+
: undefined;
|
|
2030
|
+
// 步骤 3:Python 符号节点(纯 AST,extractionResults 第四路)
|
|
2031
|
+
// EC-003:extractSymbolNodes 内部已 best-effort(单文件失败仍产文件级节点),此处仅兜底捕获
|
|
2032
|
+
let pythonSymbolResults = [];
|
|
2033
|
+
try {
|
|
2034
|
+
const { PythonLanguageAdapter } = await import('../adapters/python-adapter.js');
|
|
2035
|
+
pythonSymbolResults = await new PythonLanguageAdapter().extractSymbolNodes(resolvedRoot);
|
|
2036
|
+
}
|
|
2037
|
+
catch (pyErr) {
|
|
2038
|
+
logger.warn(`[Feature 195] Python 符号提取失败,跳过: ${String(pyErr)}`);
|
|
2039
|
+
}
|
|
2040
|
+
const pythonSymbolCount = pythonSymbolResults.reduce((sum, r) => sum + r.nodes.filter((n) => n.kind === 'component').length, 0);
|
|
2041
|
+
// 步骤 4:合并构建 knowledge graph(不传 docGraph/architectureIR/crossReferenceLinks——
|
|
2042
|
+
// 那三路依赖 spec-gen 产物;EC-004:buildKnowledgeGraph 对缺席数据源 graceful skip)
|
|
2043
|
+
const graphJson = buildKnowledgeGraph({
|
|
2044
|
+
...(unifiedGraph ? { unifiedGraph } : {}),
|
|
2045
|
+
...(pythonSymbolResults.length > 0 ? { extractionResults: pythonSymbolResults } : {}),
|
|
2046
|
+
});
|
|
2047
|
+
// 步骤 5:复用 F183 写盘出口(内部 portable 守卫扫描 → normalizeGraphForWrite → 原子写)
|
|
2048
|
+
const graphPath = writeKnowledgeGraph(graphJson, resolvedOutputDir, {
|
|
2049
|
+
stripTimestamps: true,
|
|
2050
|
+
});
|
|
2051
|
+
const callEdgeCount = graphJson.links.filter((e) => e.relation === 'calls').length;
|
|
2052
|
+
const dependsOnEdgeCount = graphJson.links.filter((e) => e.relation === 'depends-on').length;
|
|
2053
|
+
logger.info(`[Feature 195] graph-only 完成:${graphJson.nodes.length} 节点 / ${graphJson.links.length} 边 ` +
|
|
2054
|
+
`(${callEdgeCount} calls, ${dependsOnEdgeCount} depends-on, ${pythonSymbolCount} Python 符号)`);
|
|
2055
|
+
return {
|
|
2056
|
+
graphPath,
|
|
2057
|
+
nodeCount: graphJson.nodes.length,
|
|
2058
|
+
edgeCount: graphJson.links.length,
|
|
2059
|
+
callEdgeCount,
|
|
2060
|
+
dependsOnEdgeCount,
|
|
2061
|
+
pythonSymbolCount,
|
|
2062
|
+
durationMs: Date.now() - startTime,
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
1829
2065
|
//# sourceMappingURL=batch-orchestrator.js.map
|