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,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* F186 T2 — wrapper body 提取(单一 Node 实现,shell 生成端与 JS 校验端共用)。
|
|
4
|
+
*
|
|
5
|
+
* 动机(FINAL 设计审查 WARNING-1):原 codex-skills.sh 用 awk|sed 管道生成 wrapper body,
|
|
6
|
+
* 若 JS 校验端另写一套提取逻辑,两者极易逐字节漂移 → wrapper sha256 假阳性 fail。
|
|
7
|
+
* 抽为单一 helper 后,shell 的 write_skill_body 与 validate-wrapper-sources.mjs 都调用本模块,
|
|
8
|
+
* 杜绝双实现分叉。
|
|
9
|
+
*
|
|
10
|
+
* 提取逻辑 = codex-skills.sh:150-168 的 awk 等价(剥除首个 frontmatter 块)
|
|
11
|
+
* + codex-skills.sh:79-90 rewrite_codex_runtime_text 的 9 条 sed 替换纯 JS 等价。
|
|
12
|
+
*
|
|
13
|
+
* 一致性约束:本文件的 frontmatter 剥除 + 9 条替换必须与 codex-skills.sh 逐字节等价;
|
|
14
|
+
* 任一端改动须同步另一端,并由 wrapper-sha256.test.ts 的 helper 单测 + T019 字节一致性验证守护。
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import crypto from 'node:crypto';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 剥除 SKILL.md 首个 frontmatter 块(awk write_skill_body 等价)。
|
|
23
|
+
*
|
|
24
|
+
* awk 语义:NR==1 且 $0=="---" 进入 frontmatter;其后遇到下一个 "---" 退出 frontmatter
|
|
25
|
+
* 且该 "---" 行本身也被跳过(next);frontmatter 外的行原样 print。
|
|
26
|
+
*
|
|
27
|
+
* 与 awk 逐字节对齐的两个关键点(WARNING-1):
|
|
28
|
+
* 1. CRLF 规范化:awk 以 RS=\n 切记录,CRLF 文件的 "---\r" 不会等于 "---",故先把
|
|
29
|
+
* \r\n → \n(否则 frontmatter 匹配失败、frontmatter 不被剥除)。
|
|
30
|
+
* 2. 尾换行(ORS):awk 对每条 print 的记录补 ORS=\n,故 N 行输出 = 各行 join('\n') + 末尾 '\n'。
|
|
31
|
+
* 同时 awk 以 RS 切记录时,文件末尾的 \n 不产生额外空记录;而 JS split('\n') 会在尾随 \n
|
|
32
|
+
* 处留一个空串元素,须丢弃以避免多出一行。
|
|
33
|
+
*/
|
|
34
|
+
function stripFrontmatter(content) {
|
|
35
|
+
// CRLF → LF(对齐 awk 的 RS=\n 行切分)
|
|
36
|
+
const normalized = content.replace(/\r\n/g, '\n');
|
|
37
|
+
// 丢弃尾随 \n 产生的空记录(awk 不会为文件末尾 \n 生成额外空行)
|
|
38
|
+
const body = normalized.endsWith('\n') ? normalized.slice(0, -1) : normalized;
|
|
39
|
+
const lines = body.split('\n');
|
|
40
|
+
const out = [];
|
|
41
|
+
let inFrontmatter = false;
|
|
42
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
43
|
+
const line = lines[i];
|
|
44
|
+
if (i === 0 && line === '---') {
|
|
45
|
+
// 进入 frontmatter,跳过首个 ---
|
|
46
|
+
inFrontmatter = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (inFrontmatter) {
|
|
50
|
+
if (line === '---') {
|
|
51
|
+
inFrontmatter = false;
|
|
52
|
+
}
|
|
53
|
+
// frontmatter 内(含闭合 ---)全部跳过
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
out.push(line);
|
|
57
|
+
}
|
|
58
|
+
// 空输出(全部为 frontmatter)时 awk 不 print 任何记录 → 空串;
|
|
59
|
+
// 非空时还原 awk ORS:每条记录补 \n,等价 join('\n') + '\n'。
|
|
60
|
+
if (out.length === 0) {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
return `${out.join('\n')}\n`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* rewrite_codex_runtime_text 的 9 条 sed 替换纯 JS 等价(codex-skills.sh:80-89)。
|
|
68
|
+
* 全部为全局替换(sed 的 g 标志)。逐条照搬,顺序与 shell 一致。
|
|
69
|
+
*/
|
|
70
|
+
function rewriteCodexRuntimeText(text) {
|
|
71
|
+
const replacements = [
|
|
72
|
+
['/spec-driver:spec-driver-feature', '$spec-driver-feature'],
|
|
73
|
+
['/spec-driver:spec-driver-implement', '$spec-driver-implement'],
|
|
74
|
+
['/spec-driver:spec-driver-story', '$spec-driver-story'],
|
|
75
|
+
['/spec-driver:spec-driver-fix', '$spec-driver-fix'],
|
|
76
|
+
['/spec-driver:spec-driver-resume', '$spec-driver-resume'],
|
|
77
|
+
['/spec-driver:spec-driver-sync', '$spec-driver-sync'],
|
|
78
|
+
['/spec-driver:spec-driver-doc', '$spec-driver-doc'],
|
|
79
|
+
['Claude Code 的 Task tool', 'Task tool(Codex 下按内联子代理执行)'],
|
|
80
|
+
[
|
|
81
|
+
'在同一消息中同时发出多个 Task tool 调用。Claude Code 的 function calling 机制支持在单个 assistant 消息中发出多个 tool calls,这些 tool calls 会被并行执行。',
|
|
82
|
+
'若当前环境支持并行工具调用,则在同一消息中并行执行;否则按本 Skill 的回退规则串行执行。',
|
|
83
|
+
],
|
|
84
|
+
];
|
|
85
|
+
let result = text;
|
|
86
|
+
for (const [from, to] of replacements) {
|
|
87
|
+
result = result.split(from).join(to);
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 提取 wrapper body 文本(frontmatter 剥除 + 9 条 runtime text 替换)。
|
|
94
|
+
* @param {string} sourceSkillPath canonical SKILL.md 绝对路径
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
export function extractWrapperBody(sourceSkillPath) {
|
|
98
|
+
const content = fs.readFileSync(sourceSkillPath, 'utf-8');
|
|
99
|
+
return rewriteCodexRuntimeText(stripFrontmatter(content));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 计算 wrapper body 的 sha256(hex)。
|
|
104
|
+
* @param {string} sourceSkillPath canonical SKILL.md 绝对路径
|
|
105
|
+
* @returns {string} 64 位 hex
|
|
106
|
+
*/
|
|
107
|
+
export function computeWrapperBodySha256(sourceSkillPath) {
|
|
108
|
+
const body = extractWrapperBody(sourceSkillPath);
|
|
109
|
+
return crypto.createHash('sha256').update(body, 'utf-8').digest('hex');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isDirectExecution() {
|
|
113
|
+
if (!process.argv[1]) return false;
|
|
114
|
+
try {
|
|
115
|
+
// CRITICAL-1:用 fileURLToPath 而非 new URL(...).pathname——后者在安装路径含空格/非 ASCII 时
|
|
116
|
+
// 保留 %20/%E4... 转义,与 process.argv[1] 比对失败 → main() 不执行 → 静默生成空 wrapper。
|
|
117
|
+
// 与 validate-wrapper-sources.mjs 的 isDirectExecution 保持同一实现。
|
|
118
|
+
return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// CLI 入口:shell write_skill_body 调 `node extract-wrapper-body.mjs <source>` 取 body(写 stdout)
|
|
125
|
+
if (isDirectExecution()) {
|
|
126
|
+
const sourcePath = process.argv[2];
|
|
127
|
+
if (!sourcePath) {
|
|
128
|
+
console.error('用法: node extract-wrapper-body.mjs <source-skill-path> [--sha256]');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
if (process.argv[3] === '--sha256') {
|
|
132
|
+
process.stdout.write(computeWrapperBodySha256(sourcePath));
|
|
133
|
+
} else {
|
|
134
|
+
process.stdout.write(extractWrapperBody(sourcePath));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fix-compliance-core.mjs
|
|
3
|
+
* Feature 208 — fix 模式流程依从性判定核心(纯函数,零 I/O)
|
|
4
|
+
*
|
|
5
|
+
* 设计原则(research.md D3):本模块全部函数为纯函数——不读文件、不碰 process.env、
|
|
6
|
+
* 不调网络/LLM/子代理。transcript 已由 io 层解析为 TranscriptEntry 数组、配置已读为对象后传入,
|
|
7
|
+
* core 只负责结构化判定。分层惯例参照 goal-loop-core.mjs(fail-loud + 类型守卫先例)。
|
|
8
|
+
*
|
|
9
|
+
* 反伪造硬化(research.md D1):展开痕迹只认 user 消息 text 块、委派只认 assistant 消息
|
|
10
|
+
* tool_use 块——这两类 envelope 由 harness 写入,模型无法在其中注入内容(模型输出落在
|
|
11
|
+
* assistant text 块与 user 的 tool_result 块)。normalizeTranscriptEntry 在解析期即把
|
|
12
|
+
* tool_result 排除出 textBlocks,从源头堵死"自导自演更新展开痕迹"的 Goodhart 洞。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// ────────────────────────────────────────
|
|
16
|
+
// 常量
|
|
17
|
+
// ────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/** enforcement 三档合法值(FR-015) */
|
|
20
|
+
export const ENFORCEMENT_VALUES = new Set(['block', 'warn', 'off']);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 技能展开痕迹正则(research.md D1)。harness 注入的 user 文本块形如:
|
|
24
|
+
* `Base directory for this skill: <pluginPath>/skills/spec-driver-<mode>`
|
|
25
|
+
* mode 限定小写字母,避免把路径尾随字符误并入 mode。全局匹配便于取"最晚一次"。
|
|
26
|
+
*/
|
|
27
|
+
export const SKILL_EXPANSION_REGEX = /Base directory for this skill:\s*([^\n]+?)\/skills\/spec-driver-([a-z]+)/g;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 特性目录提名正则(research.md D1 + codex implement 审查 C-2 硬化):
|
|
31
|
+
* 提名必须锚定**required artifact 路径**(fix-report.md / verification-report.md),
|
|
32
|
+
* 而非任意目录字符串命中——否则当前会话仅在 Bash 里提及一个旧特性目录路径,
|
|
33
|
+
* 就能把磁盘上前次会话的合规制品绑定为"本次收口的制品",绕过 FR-007 判定窗口。
|
|
34
|
+
* 提名≠判据,磁盘核验才采信。
|
|
35
|
+
*/
|
|
36
|
+
export const ARTIFACT_PATH_REGEX = /specs\/\d+-fix-[a-z0-9-]+\/(?:fix-report\.md|verification\/verification-report\.md)/g;
|
|
37
|
+
|
|
38
|
+
/** Bash 提名额外要求命令含写入指示符(重定向/heredoc/tee),排除 echo/cat 纯提及旧路径的读形态 */
|
|
39
|
+
export const BASH_WRITE_INDICATOR_REGEX = /(?:>>?|<<|\btee\b)/;
|
|
40
|
+
|
|
41
|
+
/** 修复收口制品必填章节锚点(既有 Phase 1 模板固有 Root Cause 行) */
|
|
42
|
+
export const ROOT_CAUSE_HEADING_REGEX = /Root Cause/i;
|
|
43
|
+
|
|
44
|
+
/** no-op 收口制品必填章节锚点(D5 新模板 canonical 标题) */
|
|
45
|
+
export const NOOP_JUDGMENT_HEADING_REGEX = /^##\s*判定依据\s*$/m;
|
|
46
|
+
|
|
47
|
+
/** 未替换花括号占位符探测(FR-012a 空壳判据) */
|
|
48
|
+
const PLACEHOLDER_BRACE_REGEX = /\{[^}]*\}/;
|
|
49
|
+
|
|
50
|
+
/** 章节正文非占位所需的最小非空白字符数(no-op-report-template.md 合同:> 20) */
|
|
51
|
+
const MIN_SECTION_BODY_CHARS = 20;
|
|
52
|
+
|
|
53
|
+
// 角色分类模式(research.md D6,双语窄模式,subagent_type 与 description 共用)
|
|
54
|
+
// 刻意不把裸"修复"纳入 implement——"规划修复方案"/"生成修复任务"含"修复"但非"代码修复",
|
|
55
|
+
// 宽模式会把 plan/tasks 误判为 implement 造成假合规。
|
|
56
|
+
// 裸"实现"同样剔除(codex implement 审查 W-4)——"验证实现正确性"这类 verify 描述含"实现",
|
|
57
|
+
// 会被误判为 implement 凑出假角色配额;canonical implement 文本"执行代码修复"由"代码修复"覆盖。
|
|
58
|
+
const IMPLEMENT_ROLE_REGEX = /implement|代码修复/i;
|
|
59
|
+
const VERIFY_ROLE_REGEX = /verify|quality-review|spec-review|review|验证|审查/i;
|
|
60
|
+
// no-op 交叉核实类(比 verify 更宽,额外含 核实/确认;codex plan 审查 W-2 堵廉价委派)
|
|
61
|
+
const NOOP_VERIFY_ROLE_REGEX = /verify|spec-review|quality-review|review|验证|审查|核实|确认/i;
|
|
62
|
+
|
|
63
|
+
/** 委派工具名白名单(Agent=当前 CLI 记录名,Task=历史/未来名,等价对待) */
|
|
64
|
+
const DELEGATION_TOOL_NAMES = new Set(['Agent', 'Task']);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* missing 枚举 → 固定 action 文案映射(contracts/fix-compliance-judge-cli.md)。
|
|
68
|
+
* 放 core 便于 T011 CLI 机械拼装反馈文本;单测断言每枚举都有文案,防新增枚举漏配。
|
|
69
|
+
*/
|
|
70
|
+
export const MISSING_ACTION_TEXT = {
|
|
71
|
+
'fix-report.md': '缺少诊断报告:请完成问题诊断并将 fix-report.md 写入 specs/NNN-fix-<name>/(含 Root Cause 章节)',
|
|
72
|
+
'verification-report.md': '缺少验证报告:请委派 verify 子代理完成 Phase 4 验证闭环(产出 verification/verification-report.md)',
|
|
73
|
+
'delegation:implement': '缺少 implement 类委派:代码修复必须经 Task 委派 implement 子代理执行(禁止编排器行内修改)',
|
|
74
|
+
'delegation:verify': '缺少 verify 类委派:验证闭环必须经 Task 委派 verify/review 类子代理执行',
|
|
75
|
+
'delegation:noop-verify': '缺少 no-op 交叉核实委派:请委派一次 verify 类子代理核实"确实无需改动"这一判断',
|
|
76
|
+
'noop:judgment-section': 'no-op 判定记录不完整:fix-report.md 必须含"## 判定依据"章节且给出具体证据(非占位文本)',
|
|
77
|
+
'artifact:placeholder': '制品为占位空壳:请把模板占位符替换为真实内容',
|
|
78
|
+
'feature-dir': '未建立特性目录:请按 specs/NNN-fix-<short-name>/ 约定创建特性目录并落盘诊断制品',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** 双路径收口指引(尾部固定文案,逐字来自 contracts/fix-compliance-judge-cli.md) */
|
|
82
|
+
export const DUAL_PATH_GUIDANCE = [
|
|
83
|
+
'两条合法收口路径任选其一:',
|
|
84
|
+
'(A) 完整修复路径:诊断(fix-report.md) → 委派 implement 修复 → 委派 verify 验证(verification-report.md)',
|
|
85
|
+
'(B) 确认无需改动路径:fix-report.md 写入"## 判定依据"章节(含具体证据) + 委派 1 次 verify 类子代理交叉核实',
|
|
86
|
+
].join('\n');
|
|
87
|
+
|
|
88
|
+
/** 降级放行前置说明行(GATE-DEGRADED 场景在缺口清单前追加) */
|
|
89
|
+
export const GATE_DEGRADED_PREFIX_LINE = '已达阻断上限(2 次),本次降级放行——以下缺口仍未补齐,已落盘降级审计记录:';
|
|
90
|
+
|
|
91
|
+
// ────────────────────────────────────────
|
|
92
|
+
// transcript envelope → TranscriptEntry(纯转换,io 层复用以保证同源)
|
|
93
|
+
// ────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 把单条 transcript envelope 归一化为 TranscriptEntry(data-model.md §2)。
|
|
97
|
+
* @param {object|null} raw - JSON.parse 后的 envelope;parseError 时传 null
|
|
98
|
+
* @param {number} lineIndex
|
|
99
|
+
* @param {boolean} parseError
|
|
100
|
+
* @returns {{ lineIndex:number, role:string|undefined, textBlocks:string[], toolUseBlocks:{name:string,input:object}[], parseError:boolean }}
|
|
101
|
+
*/
|
|
102
|
+
export function normalizeTranscriptEntry(raw, lineIndex, parseError = false) {
|
|
103
|
+
if (parseError || !raw || typeof raw !== 'object') {
|
|
104
|
+
return { lineIndex, role: undefined, textBlocks: [], toolUseBlocks: [], parseError: true };
|
|
105
|
+
}
|
|
106
|
+
const role = typeof raw.type === 'string' ? raw.type : undefined;
|
|
107
|
+
const content = raw.message && raw.message.content;
|
|
108
|
+
const textBlocks = [];
|
|
109
|
+
const toolUseBlocks = [];
|
|
110
|
+
|
|
111
|
+
if (typeof content === 'string') {
|
|
112
|
+
// 字符串 content 视为单一文本块(T001 结论 2)
|
|
113
|
+
textBlocks.push(content);
|
|
114
|
+
} else if (Array.isArray(content)) {
|
|
115
|
+
for (const block of content) {
|
|
116
|
+
if (!block || typeof block !== 'object') continue;
|
|
117
|
+
if (block.type === 'text' && typeof block.text === 'string') {
|
|
118
|
+
textBlocks.push(block.text);
|
|
119
|
+
} else if (block.type === 'tool_use' && typeof block.name === 'string') {
|
|
120
|
+
toolUseBlocks.push({ name: block.name, input: (block.input && typeof block.input === 'object') ? block.input : {} });
|
|
121
|
+
}
|
|
122
|
+
// tool_result 与其余块型刻意忽略(反伪造 + 噪声容错)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// 非 user/assistant 顶层类型或缺 content → 空集(T001 补充结论 7)
|
|
126
|
+
return { lineIndex, role, textBlocks, toolUseBlocks, parseError: false };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ────────────────────────────────────────
|
|
130
|
+
// 判定窗口锚定(D1)
|
|
131
|
+
// ────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 检测最晚一次 spec-driver 技能展开(只认 user 文本块)。
|
|
135
|
+
* @param {ReturnType<typeof normalizeTranscriptEntry>[]} entries
|
|
136
|
+
* @returns {{ found:boolean, mode:string|null, anchorLineIndex:number|null }}
|
|
137
|
+
*/
|
|
138
|
+
export function detectFixSkillExpansion(entries) {
|
|
139
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
140
|
+
let latest = { found: false, mode: null, anchorLineIndex: null };
|
|
141
|
+
for (const entry of list) {
|
|
142
|
+
// 反伪造:只接受 user 角色(harness 注入)的 text 块,排除 assistant / tool_result
|
|
143
|
+
if (!entry || entry.role !== 'user') continue;
|
|
144
|
+
for (const text of entry.textBlocks) {
|
|
145
|
+
// 全局匹配取该块内最后一次(同块多痕迹时取最晚)
|
|
146
|
+
let match;
|
|
147
|
+
let lastMode = null;
|
|
148
|
+
SKILL_EXPANSION_REGEX.lastIndex = 0;
|
|
149
|
+
while ((match = SKILL_EXPANSION_REGEX.exec(text)) !== null) {
|
|
150
|
+
lastMode = match[2];
|
|
151
|
+
}
|
|
152
|
+
if (lastMode !== null) {
|
|
153
|
+
latest = { found: true, mode: lastMode, anchorLineIndex: entry.lineIndex };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return latest;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ────────────────────────────────────────
|
|
161
|
+
// 委派抽取与角色分类(D6)
|
|
162
|
+
// ────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
/** 对单个 pattern 做角色匹配,命中返回角色名,否则 null */
|
|
165
|
+
function matchRole(text) {
|
|
166
|
+
if (typeof text !== 'string' || text.length === 0) return null;
|
|
167
|
+
// implement 优先于 verify:canonical 文本已精确切分,二者互斥,顺序仅为确定性
|
|
168
|
+
if (IMPLEMENT_ROLE_REGEX.test(text)) return 'implement';
|
|
169
|
+
if (VERIFY_ROLE_REGEX.test(text)) return 'verify';
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 委派角色级联分类(D6):subagent_type 权威优先,无角色信息回落 description。
|
|
175
|
+
* @param {string|null} subagentType
|
|
176
|
+
* @param {string|null} description
|
|
177
|
+
* @returns {'implement'|'verify'|'other'}
|
|
178
|
+
*/
|
|
179
|
+
export function classifyDelegationRole(subagentType, description) {
|
|
180
|
+
return matchRole(subagentType) ?? matchRole(description) ?? 'other';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** 判定单条委派是否属 no-op 交叉核实类(级联,模式比 verify 宽) */
|
|
184
|
+
function isNoopVerifyDelegation(subagentType, description) {
|
|
185
|
+
const hit = (t) => typeof t === 'string' && NOOP_VERIFY_ROLE_REGEX.test(t);
|
|
186
|
+
return hit(subagentType) || hit(description);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 抽取锚点之后的委派记录(只认 assistant tool_use,name ∈ {Agent,Task})。
|
|
191
|
+
* @param {ReturnType<typeof normalizeTranscriptEntry>[]} entries
|
|
192
|
+
* @param {number|null} anchorLineIndex
|
|
193
|
+
* @returns {{ lineIndex:number, toolName:string, subagentType:string|null, description:string|null, roleClass:string, noopVerify:boolean }[]}
|
|
194
|
+
*/
|
|
195
|
+
export function extractDelegationsAfter(entries, anchorLineIndex) {
|
|
196
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
197
|
+
const anchor = typeof anchorLineIndex === 'number' ? anchorLineIndex : -1;
|
|
198
|
+
const out = [];
|
|
199
|
+
for (const entry of list) {
|
|
200
|
+
if (!entry || entry.role !== 'assistant' || entry.lineIndex <= anchor) continue;
|
|
201
|
+
for (const block of entry.toolUseBlocks) {
|
|
202
|
+
if (!DELEGATION_TOOL_NAMES.has(block.name)) continue;
|
|
203
|
+
const input = block.input || {};
|
|
204
|
+
const subagentType = typeof input.subagent_type === 'string' ? input.subagent_type : null;
|
|
205
|
+
const description = typeof input.description === 'string' ? input.description : null;
|
|
206
|
+
out.push({
|
|
207
|
+
lineIndex: entry.lineIndex,
|
|
208
|
+
toolName: block.name,
|
|
209
|
+
subagentType,
|
|
210
|
+
description,
|
|
211
|
+
roleClass: classifyDelegationRole(subagentType, description),
|
|
212
|
+
noopVerify: isNoopVerifyDelegation(subagentType, description),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ────────────────────────────────────────
|
|
220
|
+
// 特性目录提名(D1)
|
|
221
|
+
// ────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 从锚点后的制品写入痕迹提名特性目录候选,取最后出现者(codex implement 审查 C-2 硬化):
|
|
225
|
+
* - Write/Edit:`input.file_path` 必须命中 required artifact 路径(fix-report.md / verification-report.md)
|
|
226
|
+
* - Bash:`input.command` 命中 artifact 路径 **且** 含写入指示符(重定向/heredoc/tee)——
|
|
227
|
+
* 排除 `echo specs/301-fix-old` / `cat .../fix-report.md` 等纯提及旧路径的读形态
|
|
228
|
+
* 提名后取 artifact 路径的目录前缀作为候选;提名≠判据——磁盘核验(io 层)才采信。
|
|
229
|
+
* 说明:诚实流程的 fix-report.md 由编排器亲自(Phase 1 inline 豁免)经 Write 写入主 transcript,
|
|
230
|
+
* 提名可靠;verification-report.md 由 verify 子代理在 sidechain 写入、主 transcript 可能不可见,
|
|
231
|
+
* 但目录前缀由 fix-report.md 提名即可,verification-report 的存在性走磁盘核验。
|
|
232
|
+
* @param {ReturnType<typeof normalizeTranscriptEntry>[]} entries
|
|
233
|
+
* @param {number|null} anchorLineIndex
|
|
234
|
+
* @returns {{ path: string|null }}
|
|
235
|
+
*/
|
|
236
|
+
export function resolveFeatureDirCandidate(entries, anchorLineIndex) {
|
|
237
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
238
|
+
const anchor = typeof anchorLineIndex === 'number' ? anchorLineIndex : -1;
|
|
239
|
+
let candidate = null;
|
|
240
|
+
const scanArtifactPath = (text) => {
|
|
241
|
+
if (typeof text !== 'string') return;
|
|
242
|
+
let match;
|
|
243
|
+
ARTIFACT_PATH_REGEX.lastIndex = 0;
|
|
244
|
+
while ((match = ARTIFACT_PATH_REGEX.exec(text)) !== null) {
|
|
245
|
+
// 取 artifact 路径的特性目录前缀(specs/NNN-fix-<name>)
|
|
246
|
+
candidate = match[0].replace(/\/(?:fix-report\.md|verification\/verification-report\.md)$/, '');
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
for (const entry of list) {
|
|
250
|
+
if (!entry || entry.role !== 'assistant' || entry.lineIndex <= anchor) continue;
|
|
251
|
+
for (const block of entry.toolUseBlocks) {
|
|
252
|
+
const input = block.input || {};
|
|
253
|
+
if ((block.name === 'Write' || block.name === 'Edit') && typeof input.file_path === 'string') {
|
|
254
|
+
scanArtifactPath(input.file_path);
|
|
255
|
+
} else if (block.name === 'Bash' && typeof input.command === 'string'
|
|
256
|
+
&& BASH_WRITE_INDICATOR_REGEX.test(input.command)) {
|
|
257
|
+
scanArtifactPath(input.command);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return { path: candidate };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ────────────────────────────────────────
|
|
265
|
+
// 制品章节判据(D6 / FR-012a)
|
|
266
|
+
// ────────────────────────────────────────
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* 抽取 requiredHeading 命中处到下一个二级标题(或文件尾)之间的正文。
|
|
270
|
+
* heading 未命中 → 返回空字符串。
|
|
271
|
+
*/
|
|
272
|
+
function extractSectionBody(content, requiredHeading) {
|
|
273
|
+
const text = typeof content === 'string' ? content : '';
|
|
274
|
+
const lines = text.split('\n');
|
|
275
|
+
const probe = new RegExp(requiredHeading.source, requiredHeading.flags.replace('g', ''));
|
|
276
|
+
// 定位命中 heading 的行
|
|
277
|
+
let startLine = -1;
|
|
278
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
279
|
+
// 逐行匹配 requiredHeading(对 /m 锚定的 `## 判定依据` 与内联 `Root Cause` 均适用)
|
|
280
|
+
if (probe.test(lines[i])) {
|
|
281
|
+
startLine = i;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (startLine === -1) return '';
|
|
286
|
+
// 正文起点包含"匹配行锚点之后的剩余文本"——修复形态 `**Root Cause**: <text>` 的证据与锚点同行,
|
|
287
|
+
// no-op 形态 `## 判定依据` 的证据在后续行(同行剩余为空),二者统一处理。
|
|
288
|
+
const headMatch = new RegExp(requiredHeading.source, requiredHeading.flags.replace('g', '')).exec(lines[startLine]);
|
|
289
|
+
const remainder = headMatch ? lines[startLine].slice(headMatch.index + headMatch[0].length) : lines[startLine];
|
|
290
|
+
const body = [remainder];
|
|
291
|
+
for (let i = startLine + 1; i < lines.length; i += 1) {
|
|
292
|
+
if (/^#{1,2}\s/.test(lines[i])) break; // 下一个一/二级标题终止
|
|
293
|
+
body.push(lines[i]);
|
|
294
|
+
}
|
|
295
|
+
return body.join('\n');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 机械校验制品章节(FR-012a)。
|
|
300
|
+
* @param {string} content - 制品文件内容
|
|
301
|
+
* @param {RegExp} requiredHeading - 必填章节锚点正则
|
|
302
|
+
* @returns {{ nonEmpty:boolean, hasRequiredSection:boolean, placeholderResidue:boolean }}
|
|
303
|
+
*/
|
|
304
|
+
export function checkArtifactSection(content, requiredHeading) {
|
|
305
|
+
const text = typeof content === 'string' ? content : '';
|
|
306
|
+
const nonEmpty = text.replace(/\s/g, '').length > 0;
|
|
307
|
+
const headingProbe = new RegExp(requiredHeading.source, requiredHeading.flags.replace('g', ''));
|
|
308
|
+
const hasRequiredSection = headingProbe.test(text);
|
|
309
|
+
if (!hasRequiredSection) {
|
|
310
|
+
return { nonEmpty, hasRequiredSection: false, placeholderResidue: false };
|
|
311
|
+
}
|
|
312
|
+
const body = extractSectionBody(text, requiredHeading);
|
|
313
|
+
const bodyChars = body.replace(/\s/g, '').length;
|
|
314
|
+
// 占位空壳 = 正文过短(≤20 非空白字符)或残留未替换花括号占位符
|
|
315
|
+
const placeholderResidue = bodyChars <= MIN_SECTION_BODY_CHARS || PLACEHOLDER_BRACE_REGEX.test(body);
|
|
316
|
+
return { nonEmpty, hasRequiredSection: true, placeholderResidue };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* 按内容特征区分收口形态(no-op-report-template.md:两锚点集刻意互斥)。
|
|
321
|
+
* 双锚点同时命中 → 按修复收口判据(取严,codex implement 审查 W-1):若报告既声称
|
|
322
|
+
* Root Cause 又带"## 判定依据",不允许借 no-op 的更低门槛绕过 verification-report
|
|
323
|
+
* 与 implement/verify 双角色要求。
|
|
324
|
+
* @param {string|null} fixReportContent
|
|
325
|
+
* @returns {'repair'|'no-op'|'undetermined'}
|
|
326
|
+
*/
|
|
327
|
+
export function classifyClosureForm(fixReportContent) {
|
|
328
|
+
const text = typeof fixReportContent === 'string' ? fixReportContent : '';
|
|
329
|
+
const hasNoop = NOOP_JUDGMENT_HEADING_REGEX.test(text);
|
|
330
|
+
const hasRepair = ROOT_CAUSE_HEADING_REGEX.test(text);
|
|
331
|
+
if (hasNoop && hasRepair) return 'repair';
|
|
332
|
+
if (hasNoop) return 'no-op';
|
|
333
|
+
if (hasRepair) return 'repair';
|
|
334
|
+
return 'undetermined';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ────────────────────────────────────────
|
|
338
|
+
// 合规最终判定(FR-002 三支)
|
|
339
|
+
// ────────────────────────────────────────
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 综合判定 fix 会话合规状态(data-model.md §7 ComplianceVerdict)。
|
|
343
|
+
* 全部输入已由 io/judge 层解析:transcript 侧 delegations、磁盘侧 featureDir/artifacts、配置侧 enforcement。
|
|
344
|
+
* @param {{
|
|
345
|
+
* delegations: {roleClass:string, subagentType:string|null, description:string|null, noopVerify?:boolean}[],
|
|
346
|
+
* featureDir: { path:string|null, existsOnDisk:boolean },
|
|
347
|
+
* fixReport: { exists:boolean, content:string|null },
|
|
348
|
+
* verificationReport: { exists:boolean, nonEmpty:boolean },
|
|
349
|
+
* enforcement: string, configDegraded: boolean, diagnostics: string[],
|
|
350
|
+
* }} input
|
|
351
|
+
* @returns {{ closureForm:string, compliant:boolean, missing:string[], delegationCounts:object, enforcement:string, configDegraded:boolean, diagnostics:string[] }}
|
|
352
|
+
*/
|
|
353
|
+
export function judgeCompliance(input) {
|
|
354
|
+
const {
|
|
355
|
+
delegations = [], featureDir = { path: null, existsOnDisk: false },
|
|
356
|
+
fixReport = { exists: false, content: null }, verificationReport = { exists: false, nonEmpty: false },
|
|
357
|
+
enforcement = 'block', configDegraded = false, diagnostics = [],
|
|
358
|
+
} = input || {};
|
|
359
|
+
|
|
360
|
+
const counts = { implement: 0, verify: 0, other: 0 };
|
|
361
|
+
let noopVerifyCount = 0;
|
|
362
|
+
for (const d of delegations) {
|
|
363
|
+
const cls = d && d.roleClass;
|
|
364
|
+
if (cls === 'implement' || cls === 'verify' || cls === 'other') counts[cls] += 1;
|
|
365
|
+
// noopVerify 优先用抽取时携带的标记;缺省时按角色回退(verify 类天然属核实语义)
|
|
366
|
+
if (d && (d.noopVerify === true || (d.noopVerify === undefined && cls === 'verify'))) noopVerifyCount += 1;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const closureForm = fixReport.exists ? classifyClosureForm(fixReport.content) : 'undetermined';
|
|
370
|
+
const missing = [];
|
|
371
|
+
|
|
372
|
+
// 特性目录:候选存在且磁盘核验通过才算就绪
|
|
373
|
+
const featureDirOk = Boolean(featureDir && featureDir.path && featureDir.existsOnDisk);
|
|
374
|
+
if (!featureDirOk) missing.push('feature-dir');
|
|
375
|
+
|
|
376
|
+
if (closureForm === 'no-op') {
|
|
377
|
+
const section = checkArtifactSection(fixReport.content, NOOP_JUDGMENT_HEADING_REGEX);
|
|
378
|
+
if (!section.hasRequiredSection) {
|
|
379
|
+
missing.push('noop:judgment-section');
|
|
380
|
+
} else if (section.placeholderResidue) {
|
|
381
|
+
missing.push('artifact:placeholder');
|
|
382
|
+
}
|
|
383
|
+
if (noopVerifyCount < 1) missing.push('delegation:noop-verify');
|
|
384
|
+
} else if (closureForm === 'repair') {
|
|
385
|
+
const section = checkArtifactSection(fixReport.content, ROOT_CAUSE_HEADING_REGEX);
|
|
386
|
+
if (!section.hasRequiredSection) {
|
|
387
|
+
missing.push('fix-report.md');
|
|
388
|
+
} else if (section.placeholderResidue) {
|
|
389
|
+
missing.push('artifact:placeholder');
|
|
390
|
+
}
|
|
391
|
+
if (!(verificationReport && verificationReport.exists && verificationReport.nonEmpty)) {
|
|
392
|
+
missing.push('verification-report.md');
|
|
393
|
+
}
|
|
394
|
+
if (counts.implement < 1) missing.push('delegation:implement');
|
|
395
|
+
if (counts.verify < 1) missing.push('delegation:verify');
|
|
396
|
+
} else {
|
|
397
|
+
// undetermined:既非有效修复报告也非 no-op 报告(含 F206 坍塌:连制品都没有)
|
|
398
|
+
missing.push('fix-report.md');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
closureForm,
|
|
403
|
+
compliant: missing.length === 0,
|
|
404
|
+
missing,
|
|
405
|
+
delegationCounts: counts,
|
|
406
|
+
enforcement,
|
|
407
|
+
configDegraded,
|
|
408
|
+
diagnostics: Array.isArray(diagnostics) ? [...diagnostics] : [],
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ────────────────────────────────────────
|
|
413
|
+
// 配置强制程度解析(FR-015 三步顺序)
|
|
414
|
+
// ────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* 从(io 层读取的)配置状态解析生效 enforcement(fix-compliance-config-field.md 三步序)。
|
|
418
|
+
* 纯函数:io 负责查找文件与捕获解析异常,本函数只做取值→归约。
|
|
419
|
+
* @param {{ found:boolean, parseFailed:boolean, config:object|null }} input
|
|
420
|
+
* @returns {{ enforcement:string, configDegraded:boolean }}
|
|
421
|
+
*/
|
|
422
|
+
export function resolveEnforcementFromConfig(input) {
|
|
423
|
+
const { found = false, parseFailed = false, config = null } = input || {};
|
|
424
|
+
// 步 1:无配置文件 → 默认 block,非降级
|
|
425
|
+
if (!found) return { enforcement: 'block', configDegraded: false };
|
|
426
|
+
// 步 2a:文件存在但解析失败 → block + 降级
|
|
427
|
+
if (parseFailed) return { enforcement: 'block', configDegraded: true };
|
|
428
|
+
const value = config && config.fix_compliance && config.fix_compliance.enforcement;
|
|
429
|
+
// 缺 fix_compliance 字段 → 默认 block,非降级(缺字段=默认,不视作损坏)
|
|
430
|
+
if (value === undefined || value === null) return { enforcement: 'block', configDegraded: false };
|
|
431
|
+
// 步 3:合法取值直接采用;步 2b:非法取值 → block + 降级
|
|
432
|
+
if (ENFORCEMENT_VALUES.has(value)) return { enforcement: value, configDegraded: false };
|
|
433
|
+
return { enforcement: 'block', configDegraded: true };
|
|
434
|
+
}
|