openlore 2.0.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/LICENSE +21 -0
- package/README.md +268 -0
- package/dist/api/analyze.d.ts +17 -0
- package/dist/api/analyze.d.ts.map +1 -0
- package/dist/api/analyze.js +143 -0
- package/dist/api/analyze.js.map +1 -0
- package/dist/api/audit.d.ts +10 -0
- package/dist/api/audit.d.ts.map +1 -0
- package/dist/api/audit.js +117 -0
- package/dist/api/audit.js.map +1 -0
- package/dist/api/decisions.d.ts +55 -0
- package/dist/api/decisions.d.ts.map +1 -0
- package/dist/api/decisions.js +157 -0
- package/dist/api/decisions.js.map +1 -0
- package/dist/api/drift.d.ts +21 -0
- package/dist/api/drift.d.ts.map +1 -0
- package/dist/api/drift.js +152 -0
- package/dist/api/drift.js.map +1 -0
- package/dist/api/generate.d.ts +18 -0
- package/dist/api/generate.d.ts.map +1 -0
- package/dist/api/generate.js +259 -0
- package/dist/api/generate.js.map +1 -0
- package/dist/api/index.d.ts +41 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +34 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/init.d.ts +18 -0
- package/dist/api/init.d.ts.map +1 -0
- package/dist/api/init.js +83 -0
- package/dist/api/init.js.map +1 -0
- package/dist/api/run.d.ts +19 -0
- package/dist/api/run.d.ts.map +1 -0
- package/dist/api/run.js +312 -0
- package/dist/api/run.js.map +1 -0
- package/dist/api/specs.d.ts +49 -0
- package/dist/api/specs.d.ts.map +1 -0
- package/dist/api/specs.js +137 -0
- package/dist/api/specs.js.map +1 -0
- package/dist/api/types.d.ts +201 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +9 -0
- package/dist/api/types.js.map +1 -0
- package/dist/api/verify.d.ts +20 -0
- package/dist/api/verify.d.ts.map +1 -0
- package/dist/api/verify.js +117 -0
- package/dist/api/verify.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +30 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +683 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/audit.d.ts +9 -0
- package/dist/cli/commands/audit.d.ts.map +1 -0
- package/dist/cli/commands/audit.js +98 -0
- package/dist/cli/commands/audit.js.map +1 -0
- package/dist/cli/commands/decisions.d.ts +16 -0
- package/dist/cli/commands/decisions.d.ts.map +1 -0
- package/dist/cli/commands/decisions.js +864 -0
- package/dist/cli/commands/decisions.js.map +1 -0
- package/dist/cli/commands/digest.d.ts +9 -0
- package/dist/cli/commands/digest.d.ts.map +1 -0
- package/dist/cli/commands/digest.js +61 -0
- package/dist/cli/commands/digest.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +9 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +398 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/drift.d.ts +9 -0
- package/dist/cli/commands/drift.d.ts.map +1 -0
- package/dist/cli/commands/drift.js +550 -0
- package/dist/cli/commands/drift.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +9 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +565 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +173 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +2235 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +1384 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/refresh-stories.d.ts +10 -0
- package/dist/cli/commands/refresh-stories.d.ts.map +1 -0
- package/dist/cli/commands/refresh-stories.js +314 -0
- package/dist/cli/commands/refresh-stories.js.map +1 -0
- package/dist/cli/commands/run.d.ts +9 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +459 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +19 -0
- package/dist/cli/commands/setup.d.ts.map +1 -0
- package/dist/cli/commands/setup.js +355 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/test.d.ts +22 -0
- package/dist/cli/commands/test.d.ts.map +1 -0
- package/dist/cli/commands/test.js +180 -0
- package/dist/cli/commands/test.js.map +1 -0
- package/dist/cli/commands/verify.d.ts +9 -0
- package/dist/cli/commands/verify.d.ts.map +1 -0
- package/dist/cli/commands/verify.js +383 -0
- package/dist/cli/commands/verify.js.map +1 -0
- package/dist/cli/commands/view.d.ts +13 -0
- package/dist/cli/commands/view.d.ts.map +1 -0
- package/dist/cli/commands/view.js +547 -0
- package/dist/cli/commands/view.js.map +1 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +118 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/tui-approval.d.ts +11 -0
- package/dist/cli/tui-approval.d.ts.map +1 -0
- package/dist/cli/tui-approval.js +129 -0
- package/dist/cli/tui-approval.js.map +1 -0
- package/dist/constants.d.ts +314 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +382 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/analyzer/ai-config-generator.d.ts +54 -0
- package/dist/core/analyzer/ai-config-generator.d.ts.map +1 -0
- package/dist/core/analyzer/ai-config-generator.js +98 -0
- package/dist/core/analyzer/ai-config-generator.js.map +1 -0
- package/dist/core/analyzer/architecture-writer.d.ts +67 -0
- package/dist/core/analyzer/architecture-writer.d.ts.map +1 -0
- package/dist/core/analyzer/architecture-writer.js +209 -0
- package/dist/core/analyzer/architecture-writer.js.map +1 -0
- package/dist/core/analyzer/artifact-generator.d.ts +261 -0
- package/dist/core/analyzer/artifact-generator.d.ts.map +1 -0
- package/dist/core/analyzer/artifact-generator.js +909 -0
- package/dist/core/analyzer/artifact-generator.js.map +1 -0
- package/dist/core/analyzer/ast-chunker.d.ts +24 -0
- package/dist/core/analyzer/ast-chunker.d.ts.map +1 -0
- package/dist/core/analyzer/ast-chunker.js +198 -0
- package/dist/core/analyzer/ast-chunker.js.map +1 -0
- package/dist/core/analyzer/call-graph.d.ts +162 -0
- package/dist/core/analyzer/call-graph.d.ts.map +1 -0
- package/dist/core/analyzer/call-graph.js +2040 -0
- package/dist/core/analyzer/call-graph.js.map +1 -0
- package/dist/core/analyzer/code-shaper.d.ts +33 -0
- package/dist/core/analyzer/code-shaper.d.ts.map +1 -0
- package/dist/core/analyzer/code-shaper.js +154 -0
- package/dist/core/analyzer/code-shaper.js.map +1 -0
- package/dist/core/analyzer/codebase-digest.d.ts +40 -0
- package/dist/core/analyzer/codebase-digest.d.ts.map +1 -0
- package/dist/core/analyzer/codebase-digest.js +195 -0
- package/dist/core/analyzer/codebase-digest.js.map +1 -0
- package/dist/core/analyzer/cpp-header-resolver.d.ts +30 -0
- package/dist/core/analyzer/cpp-header-resolver.d.ts.map +1 -0
- package/dist/core/analyzer/cpp-header-resolver.js +71 -0
- package/dist/core/analyzer/cpp-header-resolver.js.map +1 -0
- package/dist/core/analyzer/dependency-graph.d.ts +230 -0
- package/dist/core/analyzer/dependency-graph.d.ts.map +1 -0
- package/dist/core/analyzer/dependency-graph.js +752 -0
- package/dist/core/analyzer/dependency-graph.js.map +1 -0
- package/dist/core/analyzer/duplicate-detector.d.ts +52 -0
- package/dist/core/analyzer/duplicate-detector.d.ts.map +1 -0
- package/dist/core/analyzer/duplicate-detector.js +289 -0
- package/dist/core/analyzer/duplicate-detector.js.map +1 -0
- package/dist/core/analyzer/embedding-service.d.ts +56 -0
- package/dist/core/analyzer/embedding-service.d.ts.map +1 -0
- package/dist/core/analyzer/embedding-service.js +118 -0
- package/dist/core/analyzer/embedding-service.js.map +1 -0
- package/dist/core/analyzer/env-extractor.d.ts +33 -0
- package/dist/core/analyzer/env-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/env-extractor.js +196 -0
- package/dist/core/analyzer/env-extractor.js.map +1 -0
- package/dist/core/analyzer/external-packages.d.ts +20 -0
- package/dist/core/analyzer/external-packages.d.ts.map +1 -0
- package/dist/core/analyzer/external-packages.js +175 -0
- package/dist/core/analyzer/external-packages.js.map +1 -0
- package/dist/core/analyzer/file-walker.d.ts +78 -0
- package/dist/core/analyzer/file-walker.d.ts.map +1 -0
- package/dist/core/analyzer/file-walker.js +532 -0
- package/dist/core/analyzer/file-walker.js.map +1 -0
- package/dist/core/analyzer/function-registry-trie.d.ts +21 -0
- package/dist/core/analyzer/function-registry-trie.d.ts.map +1 -0
- package/dist/core/analyzer/function-registry-trie.js +39 -0
- package/dist/core/analyzer/function-registry-trie.js.map +1 -0
- package/dist/core/analyzer/http-route-parser.d.ts +152 -0
- package/dist/core/analyzer/http-route-parser.d.ts.map +1 -0
- package/dist/core/analyzer/http-route-parser.js +971 -0
- package/dist/core/analyzer/http-route-parser.js.map +1 -0
- package/dist/core/analyzer/import-parser.d.ts +100 -0
- package/dist/core/analyzer/import-parser.d.ts.map +1 -0
- package/dist/core/analyzer/import-parser.js +952 -0
- package/dist/core/analyzer/import-parser.js.map +1 -0
- package/dist/core/analyzer/import-resolver-bridge.d.ts +25 -0
- package/dist/core/analyzer/import-resolver-bridge.d.ts.map +1 -0
- package/dist/core/analyzer/import-resolver-bridge.js +99 -0
- package/dist/core/analyzer/import-resolver-bridge.js.map +1 -0
- package/dist/core/analyzer/index.d.ts +10 -0
- package/dist/core/analyzer/index.d.ts.map +1 -0
- package/dist/core/analyzer/index.js +10 -0
- package/dist/core/analyzer/index.js.map +1 -0
- package/dist/core/analyzer/middleware-extractor.d.ts +29 -0
- package/dist/core/analyzer/middleware-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/middleware-extractor.js +195 -0
- package/dist/core/analyzer/middleware-extractor.js.map +1 -0
- package/dist/core/analyzer/refactor-analyzer.d.ts +83 -0
- package/dist/core/analyzer/refactor-analyzer.d.ts.map +1 -0
- package/dist/core/analyzer/refactor-analyzer.js +351 -0
- package/dist/core/analyzer/refactor-analyzer.js.map +1 -0
- package/dist/core/analyzer/repository-mapper.d.ts +150 -0
- package/dist/core/analyzer/repository-mapper.d.ts.map +1 -0
- package/dist/core/analyzer/repository-mapper.js +740 -0
- package/dist/core/analyzer/repository-mapper.js.map +1 -0
- package/dist/core/analyzer/schema-extractor.d.ts +41 -0
- package/dist/core/analyzer/schema-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/schema-extractor.js +229 -0
- package/dist/core/analyzer/schema-extractor.js.map +1 -0
- package/dist/core/analyzer/signature-extractor.d.ts +31 -0
- package/dist/core/analyzer/signature-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/signature-extractor.js +675 -0
- package/dist/core/analyzer/signature-extractor.js.map +1 -0
- package/dist/core/analyzer/significance-scorer.d.ts +79 -0
- package/dist/core/analyzer/significance-scorer.d.ts.map +1 -0
- package/dist/core/analyzer/significance-scorer.js +407 -0
- package/dist/core/analyzer/significance-scorer.js.map +1 -0
- package/dist/core/analyzer/spec-snapshot-generator.d.ts +17 -0
- package/dist/core/analyzer/spec-snapshot-generator.d.ts.map +1 -0
- package/dist/core/analyzer/spec-snapshot-generator.js +201 -0
- package/dist/core/analyzer/spec-snapshot-generator.js.map +1 -0
- package/dist/core/analyzer/spec-vector-index.d.ts +68 -0
- package/dist/core/analyzer/spec-vector-index.d.ts.map +1 -0
- package/dist/core/analyzer/spec-vector-index.js +340 -0
- package/dist/core/analyzer/spec-vector-index.js.map +1 -0
- package/dist/core/analyzer/subgraph-extractor.d.ts +51 -0
- package/dist/core/analyzer/subgraph-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/subgraph-extractor.js +147 -0
- package/dist/core/analyzer/subgraph-extractor.js.map +1 -0
- package/dist/core/analyzer/type-inference-engine.d.ts +23 -0
- package/dist/core/analyzer/type-inference-engine.d.ts.map +1 -0
- package/dist/core/analyzer/type-inference-engine.js +130 -0
- package/dist/core/analyzer/type-inference-engine.js.map +1 -0
- package/dist/core/analyzer/ui-component-extractor.d.ts +43 -0
- package/dist/core/analyzer/ui-component-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/ui-component-extractor.js +245 -0
- package/dist/core/analyzer/ui-component-extractor.js.map +1 -0
- package/dist/core/analyzer/unified-search.d.ts +116 -0
- package/dist/core/analyzer/unified-search.d.ts.map +1 -0
- package/dist/core/analyzer/unified-search.js +231 -0
- package/dist/core/analyzer/unified-search.js.map +1 -0
- package/dist/core/analyzer/vector-index.d.ts +92 -0
- package/dist/core/analyzer/vector-index.d.ts.map +1 -0
- package/dist/core/analyzer/vector-index.js +451 -0
- package/dist/core/analyzer/vector-index.js.map +1 -0
- package/dist/core/decisions/consolidator.d.ts +14 -0
- package/dist/core/decisions/consolidator.d.ts.map +1 -0
- package/dist/core/decisions/consolidator.js +169 -0
- package/dist/core/decisions/consolidator.js.map +1 -0
- package/dist/core/decisions/extractor.d.ts +26 -0
- package/dist/core/decisions/extractor.d.ts.map +1 -0
- package/dist/core/decisions/extractor.js +156 -0
- package/dist/core/decisions/extractor.js.map +1 -0
- package/dist/core/decisions/index.d.ts +19 -0
- package/dist/core/decisions/index.d.ts.map +1 -0
- package/dist/core/decisions/index.js +16 -0
- package/dist/core/decisions/index.js.map +1 -0
- package/dist/core/decisions/store.d.ts +36 -0
- package/dist/core/decisions/store.d.ts.map +1 -0
- package/dist/core/decisions/store.js +109 -0
- package/dist/core/decisions/store.js.map +1 -0
- package/dist/core/decisions/syncer.d.ts +27 -0
- package/dist/core/decisions/syncer.d.ts.map +1 -0
- package/dist/core/decisions/syncer.js +214 -0
- package/dist/core/decisions/syncer.js.map +1 -0
- package/dist/core/decisions/verifier.d.ts +20 -0
- package/dist/core/decisions/verifier.d.ts.map +1 -0
- package/dist/core/decisions/verifier.js +115 -0
- package/dist/core/decisions/verifier.js.map +1 -0
- package/dist/core/digest/digest-generator.d.ts +29 -0
- package/dist/core/digest/digest-generator.d.ts.map +1 -0
- package/dist/core/digest/digest-generator.js +181 -0
- package/dist/core/digest/digest-generator.js.map +1 -0
- package/dist/core/drift/drift-detector.d.ts +102 -0
- package/dist/core/drift/drift-detector.d.ts.map +1 -0
- package/dist/core/drift/drift-detector.js +598 -0
- package/dist/core/drift/drift-detector.js.map +1 -0
- package/dist/core/drift/git-diff.d.ts +60 -0
- package/dist/core/drift/git-diff.d.ts.map +1 -0
- package/dist/core/drift/git-diff.js +383 -0
- package/dist/core/drift/git-diff.js.map +1 -0
- package/dist/core/drift/index.d.ts +12 -0
- package/dist/core/drift/index.d.ts.map +1 -0
- package/dist/core/drift/index.js +9 -0
- package/dist/core/drift/index.js.map +1 -0
- package/dist/core/drift/spec-mapper.d.ts +73 -0
- package/dist/core/drift/spec-mapper.d.ts.map +1 -0
- package/dist/core/drift/spec-mapper.js +353 -0
- package/dist/core/drift/spec-mapper.js.map +1 -0
- package/dist/core/drift/test-suggester.d.ts +18 -0
- package/dist/core/drift/test-suggester.d.ts.map +1 -0
- package/dist/core/drift/test-suggester.js +107 -0
- package/dist/core/drift/test-suggester.js.map +1 -0
- package/dist/core/generator/adr-generator.d.ts +32 -0
- package/dist/core/generator/adr-generator.d.ts.map +1 -0
- package/dist/core/generator/adr-generator.js +192 -0
- package/dist/core/generator/adr-generator.js.map +1 -0
- package/dist/core/generator/index.d.ts +9 -0
- package/dist/core/generator/index.d.ts.map +1 -0
- package/dist/core/generator/index.js +12 -0
- package/dist/core/generator/index.js.map +1 -0
- package/dist/core/generator/mapping-generator.d.ts +54 -0
- package/dist/core/generator/mapping-generator.d.ts.map +1 -0
- package/dist/core/generator/mapping-generator.js +240 -0
- package/dist/core/generator/mapping-generator.js.map +1 -0
- package/dist/core/generator/openspec-compat.d.ts +160 -0
- package/dist/core/generator/openspec-compat.d.ts.map +1 -0
- package/dist/core/generator/openspec-compat.js +524 -0
- package/dist/core/generator/openspec-compat.js.map +1 -0
- package/dist/core/generator/openspec-format-generator.d.ts +131 -0
- package/dist/core/generator/openspec-format-generator.d.ts.map +1 -0
- package/dist/core/generator/openspec-format-generator.js +963 -0
- package/dist/core/generator/openspec-format-generator.js.map +1 -0
- package/dist/core/generator/openspec-writer.d.ts +130 -0
- package/dist/core/generator/openspec-writer.d.ts.map +1 -0
- package/dist/core/generator/openspec-writer.js +404 -0
- package/dist/core/generator/openspec-writer.js.map +1 -0
- package/dist/core/generator/prompts.d.ts +35 -0
- package/dist/core/generator/prompts.d.ts.map +1 -0
- package/dist/core/generator/prompts.js +212 -0
- package/dist/core/generator/prompts.js.map +1 -0
- package/dist/core/generator/rag-manifest-generator.d.ts +37 -0
- package/dist/core/generator/rag-manifest-generator.d.ts.map +1 -0
- package/dist/core/generator/rag-manifest-generator.js +134 -0
- package/dist/core/generator/rag-manifest-generator.js.map +1 -0
- package/dist/core/generator/schemas.d.ts +365 -0
- package/dist/core/generator/schemas.d.ts.map +1 -0
- package/dist/core/generator/schemas.js +190 -0
- package/dist/core/generator/schemas.js.map +1 -0
- package/dist/core/generator/spec-pipeline.d.ts +123 -0
- package/dist/core/generator/spec-pipeline.d.ts.map +1 -0
- package/dist/core/generator/spec-pipeline.js +699 -0
- package/dist/core/generator/spec-pipeline.js.map +1 -0
- package/dist/core/generator/stages/stage1-survey.d.ts +19 -0
- package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -0
- package/dist/core/generator/stages/stage1-survey.js +171 -0
- package/dist/core/generator/stages/stage1-survey.js.map +1 -0
- package/dist/core/generator/stages/stage2-entities.d.ts +11 -0
- package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -0
- package/dist/core/generator/stages/stage2-entities.js +74 -0
- package/dist/core/generator/stages/stage2-entities.js.map +1 -0
- package/dist/core/generator/stages/stage3-services.d.ts +11 -0
- package/dist/core/generator/stages/stage3-services.d.ts.map +1 -0
- package/dist/core/generator/stages/stage3-services.js +85 -0
- package/dist/core/generator/stages/stage3-services.js.map +1 -0
- package/dist/core/generator/stages/stage4-api.d.ts +11 -0
- package/dist/core/generator/stages/stage4-api.d.ts.map +1 -0
- package/dist/core/generator/stages/stage4-api.js +72 -0
- package/dist/core/generator/stages/stage4-api.js.map +1 -0
- package/dist/core/generator/stages/stage5-architecture.d.ts +11 -0
- package/dist/core/generator/stages/stage5-architecture.d.ts.map +1 -0
- package/dist/core/generator/stages/stage5-architecture.js +75 -0
- package/dist/core/generator/stages/stage5-architecture.js.map +1 -0
- package/dist/core/generator/stages/stage6-adr.d.ts +8 -0
- package/dist/core/generator/stages/stage6-adr.d.ts.map +1 -0
- package/dist/core/generator/stages/stage6-adr.js +47 -0
- package/dist/core/generator/stages/stage6-adr.js.map +1 -0
- package/dist/core/services/chat-agent.d.ts +50 -0
- package/dist/core/services/chat-agent.d.ts.map +1 -0
- package/dist/core/services/chat-agent.js +369 -0
- package/dist/core/services/chat-agent.js.map +1 -0
- package/dist/core/services/chat-tools.d.ts +32 -0
- package/dist/core/services/chat-tools.d.ts.map +1 -0
- package/dist/core/services/chat-tools.js +494 -0
- package/dist/core/services/chat-tools.js.map +1 -0
- package/dist/core/services/config-manager.d.ts +61 -0
- package/dist/core/services/config-manager.d.ts.map +1 -0
- package/dist/core/services/config-manager.js +149 -0
- package/dist/core/services/config-manager.js.map +1 -0
- package/dist/core/services/edge-store.d.ts +57 -0
- package/dist/core/services/edge-store.d.ts.map +1 -0
- package/dist/core/services/edge-store.js +419 -0
- package/dist/core/services/edge-store.js.map +1 -0
- package/dist/core/services/gitignore-manager.d.ts +29 -0
- package/dist/core/services/gitignore-manager.d.ts.map +1 -0
- package/dist/core/services/gitignore-manager.js +95 -0
- package/dist/core/services/gitignore-manager.js.map +1 -0
- package/dist/core/services/index.d.ts +8 -0
- package/dist/core/services/index.d.ts.map +1 -0
- package/dist/core/services/index.js +8 -0
- package/dist/core/services/index.js.map +1 -0
- package/dist/core/services/llm-service.d.ts +379 -0
- package/dist/core/services/llm-service.d.ts.map +1 -0
- package/dist/core/services/llm-service.js +1553 -0
- package/dist/core/services/llm-service.js.map +1 -0
- package/dist/core/services/mcp-handlers/analysis.d.ts +127 -0
- package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/analysis.js +1185 -0
- package/dist/core/services/mcp-handlers/analysis.js.map +1 -0
- package/dist/core/services/mcp-handlers/change.d.ts +14 -0
- package/dist/core/services/mcp-handlers/change.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/change.js +416 -0
- package/dist/core/services/mcp-handlers/change.js.map +1 -0
- package/dist/core/services/mcp-handlers/decisions.d.ts +16 -0
- package/dist/core/services/mcp-handlers/decisions.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/decisions.js +239 -0
- package/dist/core/services/mcp-handlers/decisions.js.map +1 -0
- package/dist/core/services/mcp-handlers/graph.d.ts +94 -0
- package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/graph.js +693 -0
- package/dist/core/services/mcp-handlers/graph.js.map +1 -0
- package/dist/core/services/mcp-handlers/orient.d.ts +17 -0
- package/dist/core/services/mcp-handlers/orient.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/orient.js +357 -0
- package/dist/core/services/mcp-handlers/orient.js.map +1 -0
- package/dist/core/services/mcp-handlers/semantic.d.ts +66 -0
- package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/semantic.js +432 -0
- package/dist/core/services/mcp-handlers/semantic.js.map +1 -0
- package/dist/core/services/mcp-handlers/utils.d.ts +85 -0
- package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/utils.js +262 -0
- package/dist/core/services/mcp-handlers/utils.js.map +1 -0
- package/dist/core/services/mcp-watcher.d.ts +41 -0
- package/dist/core/services/mcp-watcher.d.ts.map +1 -0
- package/dist/core/services/mcp-watcher.js +254 -0
- package/dist/core/services/mcp-watcher.js.map +1 -0
- package/dist/core/services/project-detector.d.ts +32 -0
- package/dist/core/services/project-detector.d.ts.map +1 -0
- package/dist/core/services/project-detector.js +100 -0
- package/dist/core/services/project-detector.js.map +1 -0
- package/dist/core/test-generator/coverage-analyzer.d.ts +27 -0
- package/dist/core/test-generator/coverage-analyzer.d.ts.map +1 -0
- package/dist/core/test-generator/coverage-analyzer.js +285 -0
- package/dist/core/test-generator/coverage-analyzer.js.map +1 -0
- package/dist/core/test-generator/framework-detector.d.ts +17 -0
- package/dist/core/test-generator/framework-detector.d.ts.map +1 -0
- package/dist/core/test-generator/framework-detector.js +65 -0
- package/dist/core/test-generator/framework-detector.js.map +1 -0
- package/dist/core/test-generator/index.d.ts +14 -0
- package/dist/core/test-generator/index.d.ts.map +1 -0
- package/dist/core/test-generator/index.js +11 -0
- package/dist/core/test-generator/index.js.map +1 -0
- package/dist/core/test-generator/renderers/catch2.d.ts +8 -0
- package/dist/core/test-generator/renderers/catch2.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/catch2.js +47 -0
- package/dist/core/test-generator/renderers/catch2.js.map +1 -0
- package/dist/core/test-generator/renderers/gtest.d.ts +8 -0
- package/dist/core/test-generator/renderers/gtest.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/gtest.js +45 -0
- package/dist/core/test-generator/renderers/gtest.js.map +1 -0
- package/dist/core/test-generator/renderers/index.d.ts +20 -0
- package/dist/core/test-generator/renderers/index.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/index.js +35 -0
- package/dist/core/test-generator/renderers/index.js.map +1 -0
- package/dist/core/test-generator/renderers/playwright.d.ts +8 -0
- package/dist/core/test-generator/renderers/playwright.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/playwright.js +44 -0
- package/dist/core/test-generator/renderers/playwright.js.map +1 -0
- package/dist/core/test-generator/renderers/pytest.d.ts +8 -0
- package/dist/core/test-generator/renderers/pytest.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/pytest.js +44 -0
- package/dist/core/test-generator/renderers/pytest.js.map +1 -0
- package/dist/core/test-generator/renderers/shared.d.ts +21 -0
- package/dist/core/test-generator/renderers/shared.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/shared.js +56 -0
- package/dist/core/test-generator/renderers/shared.js.map +1 -0
- package/dist/core/test-generator/renderers/vitest.d.ts +8 -0
- package/dist/core/test-generator/renderers/vitest.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/vitest.js +52 -0
- package/dist/core/test-generator/renderers/vitest.js.map +1 -0
- package/dist/core/test-generator/scenario-parser.d.ts +33 -0
- package/dist/core/test-generator/scenario-parser.d.ts.map +1 -0
- package/dist/core/test-generator/scenario-parser.js +244 -0
- package/dist/core/test-generator/scenario-parser.js.map +1 -0
- package/dist/core/test-generator/test-generator.d.ts +30 -0
- package/dist/core/test-generator/test-generator.d.ts.map +1 -0
- package/dist/core/test-generator/test-generator.js +174 -0
- package/dist/core/test-generator/test-generator.js.map +1 -0
- package/dist/core/test-generator/test-writer.d.ts +25 -0
- package/dist/core/test-generator/test-writer.d.ts.map +1 -0
- package/dist/core/test-generator/test-writer.js +128 -0
- package/dist/core/test-generator/test-writer.js.map +1 -0
- package/dist/core/test-generator/then-matchers.d.ts +35 -0
- package/dist/core/test-generator/then-matchers.d.ts.map +1 -0
- package/dist/core/test-generator/then-matchers.js +211 -0
- package/dist/core/test-generator/then-matchers.js.map +1 -0
- package/dist/core/verifier/index.d.ts +5 -0
- package/dist/core/verifier/index.d.ts.map +1 -0
- package/dist/core/verifier/index.js +5 -0
- package/dist/core/verifier/index.js.map +1 -0
- package/dist/core/verifier/verification-engine.d.ts +293 -0
- package/dist/core/verifier/verification-engine.d.ts.map +1 -0
- package/dist/core/verifier/verification-engine.js +919 -0
- package/dist/core/verifier/verification-engine.js.map +1 -0
- package/dist/types/index.d.ts +368 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/pipeline.d.ts +167 -0
- package/dist/types/pipeline.d.ts.map +1 -0
- package/dist/types/pipeline.js +5 -0
- package/dist/types/pipeline.js.map +1 -0
- package/dist/types/test-generator.d.ts +103 -0
- package/dist/types/test-generator.d.ts.map +1 -0
- package/dist/types/test-generator.js +17 -0
- package/dist/types/test-generator.js.map +1 -0
- package/dist/utils/command-helpers.d.ts +68 -0
- package/dist/utils/command-helpers.d.ts.map +1 -0
- package/dist/utils/command-helpers.js +150 -0
- package/dist/utils/command-helpers.js.map +1 -0
- package/dist/utils/errors.d.ts +51 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +129 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +149 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +342 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/misc.d.ts +10 -0
- package/dist/utils/misc.d.ts.map +1 -0
- package/dist/utils/misc.js +21 -0
- package/dist/utils/misc.js.map +1 -0
- package/dist/utils/progress.d.ts +142 -0
- package/dist/utils/progress.d.ts.map +1 -0
- package/dist/utils/progress.js +283 -0
- package/dist/utils/progress.js.map +1 -0
- package/dist/utils/prompts.d.ts +53 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +199 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/shutdown.d.ts +89 -0
- package/dist/utils/shutdown.d.ts.map +1 -0
- package/dist/utils/shutdown.js +238 -0
- package/dist/utils/shutdown.js.map +1 -0
- package/examples/bmad/README.md +113 -0
- package/examples/bmad/agents/architect.md +226 -0
- package/examples/bmad/agents/dev-brownfield.md +69 -0
- package/examples/bmad/setup/architect.customize.yaml +14 -0
- package/examples/bmad/tasks/implement-story.md +254 -0
- package/examples/bmad/tasks/onboarding.md +169 -0
- package/examples/bmad/tasks/refactor.md +178 -0
- package/examples/bmad/tasks/sprint-planning.md +168 -0
- package/examples/bmad/templates/story.md +108 -0
- package/examples/cline-workflows/openlore-analyze-codebase.md +101 -0
- package/examples/cline-workflows/openlore-check-spec-drift.md +102 -0
- package/examples/cline-workflows/openlore-execute-refactor.md +212 -0
- package/examples/cline-workflows/openlore-implement-feature.md +266 -0
- package/examples/cline-workflows/openlore-plan-refactor.md +279 -0
- package/examples/cline-workflows/openlore-refactor-codebase.md +16 -0
- package/examples/cline-workflows/openlore-write-tests.md +177 -0
- package/examples/drift-demo/openspec/config.yaml +14 -0
- package/examples/drift-demo/openspec/specs/architecture/spec.md +30 -0
- package/examples/drift-demo/openspec/specs/auth/spec.md +71 -0
- package/examples/drift-demo/openspec/specs/database/spec.md +33 -0
- package/examples/drift-demo/openspec/specs/overview/spec.md +20 -0
- package/examples/drift-demo/openspec/specs/projects/spec.md +55 -0
- package/examples/drift-demo/openspec/specs/tasks/spec.md +78 -0
- package/examples/drift-demo/package.json +21 -0
- package/examples/drift-demo/src/auth/auth-middleware.ts +30 -0
- package/examples/drift-demo/src/auth/auth-routes.ts +29 -0
- package/examples/drift-demo/src/auth/auth-service.ts +45 -0
- package/examples/drift-demo/src/database/connection.ts +27 -0
- package/examples/drift-demo/src/index.ts +16 -0
- package/examples/drift-demo/src/projects/project-model.ts +15 -0
- package/examples/drift-demo/src/projects/project-service.ts +34 -0
- package/examples/drift-demo/src/tasks/task-model.ts +37 -0
- package/examples/drift-demo/src/tasks/task-routes.ts +53 -0
- package/examples/drift-demo/src/tasks/task-service.ts +60 -0
- package/examples/drift-demo/src/utils/validation.ts +11 -0
- package/examples/drift-demo/tests/auth.test.ts +4 -0
- package/examples/drift-demo/tests/tasks.test.ts +4 -0
- package/examples/drift-demo/tsconfig.json +10 -0
- package/examples/drift-test/run-drift-test.sh +1087 -0
- package/examples/gsd/README.md +119 -0
- package/examples/gsd/commands/gsd/openlore-drift.md +111 -0
- package/examples/gsd/commands/gsd/openlore-orient.md +191 -0
- package/examples/mistral-vibe/README.md +101 -0
- package/examples/mistral-vibe/antipatterns-template.md +18 -0
- package/examples/mistral-vibe/skills/openlore-analyze-codebase/SKILL.md +124 -0
- package/examples/mistral-vibe/skills/openlore-brainstorm/SKILL.md +379 -0
- package/examples/mistral-vibe/skills/openlore-debug/SKILL.md +330 -0
- package/examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md +291 -0
- package/examples/mistral-vibe/skills/openlore-generate/SKILL.md +245 -0
- package/examples/mistral-vibe/skills/openlore-implement-story/SKILL.md +326 -0
- package/examples/mistral-vibe/skills/openlore-plan-refactor/SKILL.md +365 -0
- package/examples/mistral-vibe/skills/openlore-review-changes/SKILL.md +128 -0
- package/examples/mistral-vibe/skills/openlore-write-tests/SKILL.md +261 -0
- package/examples/opencode/agent-guard.ts +170 -0
- package/examples/opencode/plugins/anti-laziness.ts +202 -0
- package/examples/opencode/plugins/lib/openlore-context-injector-helpers.ts +116 -0
- package/examples/opencode/plugins/lib/openlore-decision-extractor-helpers.ts +65 -0
- package/examples/opencode/plugins/openlore-context-injector.test.ts +211 -0
- package/examples/opencode/plugins/openlore-context-injector.ts +165 -0
- package/examples/opencode/plugins/openlore-decision-extractor.test.ts +131 -0
- package/examples/opencode/plugins/openlore-decision-extractor.ts +322 -0
- package/examples/opencode/plugins/openlore-enforcer.ts +227 -0
- package/examples/opencode/prompts/sisyphus-sdd.md +150 -0
- package/examples/opencode-skills/openlore-analyze-codebase/SKILL.md +101 -0
- package/examples/opencode-skills/openlore-brainstorm/SKILL.md +354 -0
- package/examples/opencode-skills/openlore-debug/SKILL.md +291 -0
- package/examples/opencode-skills/openlore-execute-refactor/SKILL.md +241 -0
- package/examples/opencode-skills/openlore-generate/SKILL.md +236 -0
- package/examples/opencode-skills/openlore-implement-story/SKILL.md +251 -0
- package/examples/opencode-skills/openlore-plan-refactor/SKILL.md +298 -0
- package/examples/opencode-skills/openlore-review-changes/SKILL.md +134 -0
- package/examples/opencode-skills/openlore-write-tests/SKILL.md +230 -0
- package/examples/openspec-analysis/README.md +59 -0
- package/examples/openspec-analysis/SUMMARY.md +72 -0
- package/examples/openspec-analysis/config.json +16 -0
- package/examples/openspec-analysis/dependencies.mermaid +35 -0
- package/examples/openspec-analysis/dependency-graph.json +12116 -0
- package/examples/openspec-analysis/llm-context.json +119 -0
- package/examples/openspec-analysis/repo-structure.json +871 -0
- package/examples/openspec-cli/README.md +67 -0
- package/examples/openspec-cli/openspec/config.yaml +26 -0
- package/examples/openspec-cli/openspec/specs/architecture/spec.md +178 -0
- package/examples/openspec-cli/openspec/specs/artifact-graph/spec.md +143 -0
- package/examples/openspec-cli/openspec/specs/cli/spec.md +138 -0
- package/examples/openspec-cli/openspec/specs/overview/spec.md +60 -0
- package/examples/openspec-cli/openspec/specs/parsing/spec.md +123 -0
- package/examples/openspec-cli/openspec/specs/validation/spec.md +108 -0
- package/examples/spec-kit/README.md +104 -0
- package/examples/spec-kit/commands/drift.md +87 -0
- package/examples/spec-kit/commands/orient.md +138 -0
- package/examples/spec-kit/extension.yml +54 -0
- package/package.json +125 -0
- package/src/viewer/InteractiveGraphViewer.jsx +1600 -0
- package/src/viewer/app/index.html +17 -0
- package/src/viewer/app/main.jsx +13 -0
- package/src/viewer/components/ArchitectureView.jsx +177 -0
- package/src/viewer/components/ChatPanel.jsx +450 -0
- package/src/viewer/components/ClassGraph.jsx +782 -0
- package/src/viewer/components/ClusterGraph.jsx +469 -0
- package/src/viewer/components/FilterBar.jsx +179 -0
- package/src/viewer/components/FlatGraph.jsx +282 -0
- package/src/viewer/components/MicroComponents.jsx +85 -0
- package/src/viewer/hooks/usePanZoom.js +79 -0
- package/src/viewer/utils/constants.js +64 -0
- package/src/viewer/utils/graph-helpers.js +303 -0
- package/src/viewer/utils/graph-helpers.test.ts +39 -0
- package/src/viewer/utils/themes.js +206 -0
- package/stubs/tree-sitter-cli-stub/package.json +6 -0
|
@@ -0,0 +1,919 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec Verification Engine
|
|
3
|
+
*
|
|
4
|
+
* Tests whether generated specs accurately describe the codebase by using
|
|
5
|
+
* the specs to predict code behavior and comparing against actual files.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, writeFile, mkdir, access, readdir } from 'node:fs/promises';
|
|
8
|
+
import { join, basename, relative } from 'node:path';
|
|
9
|
+
import logger from '../../utils/logger.js';
|
|
10
|
+
import { VERIFICATION_PREDICTION_MAX_TOKENS } from '../../constants.js';
|
|
11
|
+
import { ImportExportParser } from '../analyzer/import-parser.js';
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// SYSTEM PROMPTS
|
|
14
|
+
// ============================================================================
|
|
15
|
+
const PREDICTION_SYSTEM_PROMPT = `You are testing the accuracy of OpenSpec specifications.
|
|
16
|
+
|
|
17
|
+
You will be given:
|
|
18
|
+
1. A set of specifications describing a software system (in OpenSpec format)
|
|
19
|
+
2. A file path within that system
|
|
20
|
+
|
|
21
|
+
Your task: Based ONLY on the specifications, predict:
|
|
22
|
+
- What this file likely does (purpose)
|
|
23
|
+
- What modules/files it probably imports
|
|
24
|
+
- What it probably exports (functions, classes, etc.)
|
|
25
|
+
- Key logic patterns you'd expect to see based on the spec requirements
|
|
26
|
+
|
|
27
|
+
Be specific. If the specs don't provide enough info, say so.
|
|
28
|
+
|
|
29
|
+
Respond with valid JSON only.`;
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// VERIFICATION ENGINE
|
|
32
|
+
// ============================================================================
|
|
33
|
+
/**
|
|
34
|
+
* Spec Verification Engine
|
|
35
|
+
*/
|
|
36
|
+
export class SpecVerificationEngine {
|
|
37
|
+
llm;
|
|
38
|
+
options;
|
|
39
|
+
specs = [];
|
|
40
|
+
fileDomainMap = new Map();
|
|
41
|
+
parser;
|
|
42
|
+
constructor(llm, options) {
|
|
43
|
+
this.llm = llm;
|
|
44
|
+
this.parser = new ImportExportParser();
|
|
45
|
+
this.options = {
|
|
46
|
+
rootPath: options.rootPath,
|
|
47
|
+
openspecPath: options.openspecPath,
|
|
48
|
+
outputDir: options.outputDir,
|
|
49
|
+
minComplexity: options.minComplexity ?? 50,
|
|
50
|
+
maxComplexity: options.maxComplexity ?? 500,
|
|
51
|
+
filesPerDomain: options.filesPerDomain ?? 3,
|
|
52
|
+
passThreshold: options.passThreshold ?? 0.5,
|
|
53
|
+
generationContext: options.generationContext ?? [],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Run full verification
|
|
58
|
+
*/
|
|
59
|
+
async verify(depGraph, specVersion) {
|
|
60
|
+
const startTime = Date.now();
|
|
61
|
+
// Load all specs and the file→domain mapping
|
|
62
|
+
await this.loadSpecs();
|
|
63
|
+
await this.loadFileDomainMap();
|
|
64
|
+
if (this.specs.length === 0) {
|
|
65
|
+
throw new Error('No specs found to verify against');
|
|
66
|
+
}
|
|
67
|
+
logger.analysis(`Loaded ${this.specs.length} spec(s) for verification`);
|
|
68
|
+
// Select verification candidates
|
|
69
|
+
const candidates = this.selectCandidates(depGraph);
|
|
70
|
+
logger.discovery(`Selected ${candidates.length} candidate file(s) for verification`);
|
|
71
|
+
if (candidates.length === 0) {
|
|
72
|
+
throw new Error('No suitable verification candidates found');
|
|
73
|
+
}
|
|
74
|
+
// Run verification for each candidate
|
|
75
|
+
const results = [];
|
|
76
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
77
|
+
const candidate = candidates[i];
|
|
78
|
+
logger.analysis(`Verifying ${i + 1}/${candidates.length}: ${candidate.path}`);
|
|
79
|
+
try {
|
|
80
|
+
const result = await this.verifyFile(candidate);
|
|
81
|
+
results.push(result);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
logger.warning(`Failed to verify ${candidate.path}: ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Generate report
|
|
88
|
+
const report = this.generateReport(results, specVersion);
|
|
89
|
+
// Save report
|
|
90
|
+
await this.saveReport(report);
|
|
91
|
+
const duration = Date.now() - startTime;
|
|
92
|
+
logger.success(`Verification complete in ${(duration / 1000).toFixed(1)}s`);
|
|
93
|
+
return report;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Load all specs from openspec directory
|
|
97
|
+
*/
|
|
98
|
+
async loadSpecs() {
|
|
99
|
+
this.specs = [];
|
|
100
|
+
const specsDir = join(this.options.openspecPath, 'specs');
|
|
101
|
+
try {
|
|
102
|
+
await access(specsDir);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const entries = await readdir(specsDir, { withFileTypes: true });
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
if (!entry.isDirectory())
|
|
110
|
+
continue;
|
|
111
|
+
const specPath = join(specsDir, entry.name, 'spec.md');
|
|
112
|
+
try {
|
|
113
|
+
const content = await readFile(specPath, 'utf-8');
|
|
114
|
+
this.specs.push({
|
|
115
|
+
domain: entry.name,
|
|
116
|
+
path: relative(this.options.rootPath, specPath),
|
|
117
|
+
content,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Spec doesn't exist for this domain
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Load file→domain mapping from .openlore/analysis/mapping.json.
|
|
127
|
+
* Falls back silently if the file doesn't exist (e.g. before first analysis run).
|
|
128
|
+
*/
|
|
129
|
+
async loadFileDomainMap() {
|
|
130
|
+
this.fileDomainMap = new Map();
|
|
131
|
+
const mappingPath = join(this.options.rootPath, '.openlore', 'analysis', 'mapping.json');
|
|
132
|
+
try {
|
|
133
|
+
const raw = await readFile(mappingPath, 'utf-8');
|
|
134
|
+
const data = JSON.parse(raw);
|
|
135
|
+
// Count how many distinct domains each file appears in
|
|
136
|
+
const fileDomains = new Map();
|
|
137
|
+
for (const entry of data.mappings ?? []) {
|
|
138
|
+
for (const fn of entry.functions ?? []) {
|
|
139
|
+
if (!fn.file || !entry.domain)
|
|
140
|
+
continue;
|
|
141
|
+
if (!fileDomains.has(fn.file))
|
|
142
|
+
fileDomains.set(fn.file, new Set());
|
|
143
|
+
fileDomains.get(fn.file).add(entry.domain);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Only map files that belong to exactly one domain — cross-cutting files
|
|
147
|
+
// (e.g. constants.ts, logger.ts) appear in many domains and can't be fairly
|
|
148
|
+
// verified against any single spec.
|
|
149
|
+
for (const [file, domains] of fileDomains) {
|
|
150
|
+
if (domains.size === 1) {
|
|
151
|
+
this.fileDomainMap.set(file, [...domains][0]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
logger.analysis(`Loaded file→domain mapping for ${this.fileDomainMap.size} file(s)`);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// mapping.json not available — inferDomain falls back to path heuristics
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Select verification candidate files
|
|
162
|
+
*/
|
|
163
|
+
selectCandidates(depGraph) {
|
|
164
|
+
const candidates = [];
|
|
165
|
+
const usedPaths = new Set(this.options.generationContext);
|
|
166
|
+
// Group files by domain
|
|
167
|
+
const filesByDomain = new Map();
|
|
168
|
+
for (const node of depGraph.nodes) {
|
|
169
|
+
// Skip files used in generation
|
|
170
|
+
if (usedPaths.has(node.file.path))
|
|
171
|
+
continue;
|
|
172
|
+
// Skip test files
|
|
173
|
+
if (node.file.isTest)
|
|
174
|
+
continue;
|
|
175
|
+
// Skip generated files
|
|
176
|
+
if (node.file.isGenerated)
|
|
177
|
+
continue;
|
|
178
|
+
// Skip non-source files (config, manifests, markup, data)
|
|
179
|
+
const ext = node.file.path.split('.').pop()?.toLowerCase() ?? '';
|
|
180
|
+
const sourceExts = new Set(['ts', 'tsx', 'js', 'jsx', 'py', 'go', 'rs', 'rb', 'java', 'cpp', 'c', 'cs', 'swift', 'kt']);
|
|
181
|
+
if (!sourceExts.has(ext))
|
|
182
|
+
continue;
|
|
183
|
+
// Skip files outside complexity range
|
|
184
|
+
if (node.file.lines < this.options.minComplexity)
|
|
185
|
+
continue;
|
|
186
|
+
if (node.file.lines > this.options.maxComplexity)
|
|
187
|
+
continue;
|
|
188
|
+
// Determine domain from path — skip files with no matching spec
|
|
189
|
+
// (only filter misc when specs are loaded; without specs every file maps to misc)
|
|
190
|
+
const domain = this.inferDomain(node.file.path);
|
|
191
|
+
if (domain === 'misc' && this.specs.length > 0)
|
|
192
|
+
continue;
|
|
193
|
+
if (!filesByDomain.has(domain)) {
|
|
194
|
+
filesByDomain.set(domain, []);
|
|
195
|
+
}
|
|
196
|
+
filesByDomain.get(domain).push(node);
|
|
197
|
+
}
|
|
198
|
+
// Select files from each domain
|
|
199
|
+
for (const [domain, nodes] of filesByDomain) {
|
|
200
|
+
// Prefer high-connectivity (core) files — they're what specs actually describe
|
|
201
|
+
// and are more likely to have docstrings. Leaf/utility nodes were previously
|
|
202
|
+
// preferred (ascending sort) but produced systematically low scores.
|
|
203
|
+
const sorted = nodes.sort((a, b) => {
|
|
204
|
+
const aConnectivity = a.metrics.inDegree + a.metrics.outDegree;
|
|
205
|
+
const bConnectivity = b.metrics.inDegree + b.metrics.outDegree;
|
|
206
|
+
return bConnectivity - aConnectivity;
|
|
207
|
+
});
|
|
208
|
+
// Take up to filesPerDomain
|
|
209
|
+
const selected = sorted.slice(0, this.options.filesPerDomain);
|
|
210
|
+
for (const node of selected) {
|
|
211
|
+
candidates.push({
|
|
212
|
+
path: node.file.path,
|
|
213
|
+
absolutePath: node.file.absolutePath,
|
|
214
|
+
domain,
|
|
215
|
+
usedInGeneration: false,
|
|
216
|
+
complexity: node.file.lines,
|
|
217
|
+
lines: node.file.lines,
|
|
218
|
+
imports: node.metrics.outDegree,
|
|
219
|
+
exports: node.exports.length,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return candidates;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Resolve the spec domain for a file.
|
|
227
|
+
*
|
|
228
|
+
* Priority:
|
|
229
|
+
* 1. mapping.json lookup — deterministic, built from the analysis run.
|
|
230
|
+
* 2. Path heuristic — walk segments, match against known spec domain names
|
|
231
|
+
* (exact, then prefix ≥4 chars to handle utils→utilities etc.).
|
|
232
|
+
* 3. Fallback — first meaningful non-structural segment.
|
|
233
|
+
*/
|
|
234
|
+
inferDomain(filePath) {
|
|
235
|
+
// 1. Deterministic lookup from mapping.json
|
|
236
|
+
const mapped = this.fileDomainMap.get(filePath);
|
|
237
|
+
if (mapped)
|
|
238
|
+
return mapped;
|
|
239
|
+
// 2. Path-based matching against known spec domains
|
|
240
|
+
const knownDomains = this.specs.map(s => s.domain);
|
|
241
|
+
const structural = new Set(['src', 'lib', 'app', 'core', 'utils', 'helpers', 'common', 'shared']);
|
|
242
|
+
const rawParts = filePath.split('/');
|
|
243
|
+
const segments = rawParts.map((p, i) => i === rawParts.length - 1 ? p.replace(/\.[^.]+$/, '').toLowerCase() : p.toLowerCase());
|
|
244
|
+
// Exact match against known domains — iterate deepest-first (reverse) so that
|
|
245
|
+
// src/core/services/mcp-handlers/x.ts matches "mcp-handlers" not "services".
|
|
246
|
+
const reversed = [...segments].reverse();
|
|
247
|
+
for (const seg of reversed) {
|
|
248
|
+
if (!structural.has(seg) && knownDomains.includes(seg))
|
|
249
|
+
return seg;
|
|
250
|
+
}
|
|
251
|
+
for (const seg of reversed) {
|
|
252
|
+
if (structural.has(seg) && knownDomains.includes(seg))
|
|
253
|
+
return seg;
|
|
254
|
+
}
|
|
255
|
+
// Shared-prefix match (≥4 chars) — deepest-first, e.g. "utils"→"utilities"
|
|
256
|
+
const commonPrefixLen = (a, b) => {
|
|
257
|
+
let i = 0;
|
|
258
|
+
while (i < a.length && i < b.length && a[i] === b[i])
|
|
259
|
+
i++;
|
|
260
|
+
return i;
|
|
261
|
+
};
|
|
262
|
+
for (const seg of reversed) {
|
|
263
|
+
if (seg.length < 4)
|
|
264
|
+
continue;
|
|
265
|
+
const hit = knownDomains.find(d => commonPrefixLen(seg, d) >= 4);
|
|
266
|
+
if (hit)
|
|
267
|
+
return hit;
|
|
268
|
+
}
|
|
269
|
+
// No match found — return 'misc' rather than inventing a phantom domain
|
|
270
|
+
// from the filename (which would score 0% against a non-existent spec).
|
|
271
|
+
return 'misc';
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Verify a single file
|
|
275
|
+
*/
|
|
276
|
+
async verifyFile(candidate) {
|
|
277
|
+
// Read actual file first — content is passed to getPrediction for LLM-as-judge scoring
|
|
278
|
+
const fileContent = await readFile(candidate.absolutePath, 'utf-8');
|
|
279
|
+
const fileAnalysis = await this.parser.parseFile(candidate.absolutePath);
|
|
280
|
+
// Get prediction from LLM (includes spec accuracy score via LLM-as-judge)
|
|
281
|
+
const prediction = await this.getPrediction(candidate, fileContent);
|
|
282
|
+
// Compare prediction to actual
|
|
283
|
+
const purposeMatch = this.comparePurpose(prediction.predictedPurpose, fileContent, prediction.specAccuracyScore);
|
|
284
|
+
const importMatch = this.analyzeImportCoverage(fileAnalysis.imports.map(i => i.source), candidate.domain);
|
|
285
|
+
const exportMatch = this.compareExports(prediction.predictedExports, fileAnalysis.exports.map(e => e.name));
|
|
286
|
+
const requirementCoverage = this.analyzeRequirementCoverage(candidate.domain, fileContent, prediction.requirementCoverageScore);
|
|
287
|
+
// Calculate overall score
|
|
288
|
+
const overallScore = this.calculateOverallScore(purposeMatch, importMatch, exportMatch, requirementCoverage);
|
|
289
|
+
// Generate feedback
|
|
290
|
+
const feedback = this.generateFeedback(candidate, prediction, purposeMatch, importMatch, exportMatch, requirementCoverage);
|
|
291
|
+
return {
|
|
292
|
+
filePath: candidate.path,
|
|
293
|
+
domain: candidate.domain,
|
|
294
|
+
purposeMatch,
|
|
295
|
+
importMatch,
|
|
296
|
+
exportMatch,
|
|
297
|
+
requirementCoverage,
|
|
298
|
+
overallScore,
|
|
299
|
+
llmConfidence: prediction.confidence,
|
|
300
|
+
feedback,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Build specs context string capped at maxChars to avoid silent LLM token overflow.
|
|
305
|
+
* Specs are included in order; the last spec may be truncated if the budget is tight.
|
|
306
|
+
*/
|
|
307
|
+
buildSpecsContext(maxChars) {
|
|
308
|
+
const parts = [];
|
|
309
|
+
let total = 0;
|
|
310
|
+
for (const s of this.specs) {
|
|
311
|
+
const header = `=== ${s.domain} (${s.path}) ===\n`;
|
|
312
|
+
const budget = maxChars - total - header.length;
|
|
313
|
+
if (budget <= 0)
|
|
314
|
+
break;
|
|
315
|
+
const body = s.content.length > budget
|
|
316
|
+
? s.content.slice(0, budget) + '\n[truncated]'
|
|
317
|
+
: s.content;
|
|
318
|
+
parts.push(header + body);
|
|
319
|
+
total += header.length + body.length;
|
|
320
|
+
}
|
|
321
|
+
return parts.join('\n\n');
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get prediction from LLM.
|
|
325
|
+
*
|
|
326
|
+
* When fileContent is provided the prompt uses an LLM-as-judge approach:
|
|
327
|
+
* the model sees both the spec and the actual file content, and returns a
|
|
328
|
+
* specAccuracyScore (0–1) measuring how well the spec describes the file.
|
|
329
|
+
* This replaces the brittle Jaccard keyword-overlap used for purposeMatch.
|
|
330
|
+
*/
|
|
331
|
+
async getPrediction(candidate, fileContent) {
|
|
332
|
+
// Prefer the candidate's own domain spec; fall back to full context if not found.
|
|
333
|
+
const domainSpec = this.specs.find(s => s.domain === candidate.domain);
|
|
334
|
+
const specsContent = domainSpec
|
|
335
|
+
? `=== ${domainSpec.domain} (${domainSpec.path}) ===\n${domainSpec.content}`
|
|
336
|
+
: this.buildSpecsContext(24_000);
|
|
337
|
+
// Include a trimmed excerpt of the actual file so the LLM can score spec accuracy
|
|
338
|
+
const fileExcerpt = fileContent
|
|
339
|
+
? `\n\n=== Actual file content (${candidate.path}) ===\n${fileContent.slice(0, 3000)}${fileContent.length > 3000 ? '\n[truncated]' : ''}`
|
|
340
|
+
: '';
|
|
341
|
+
const judgeInstruction = fileContent
|
|
342
|
+
? `\nAlso set:
|
|
343
|
+
- "specAccuracyScore": float 0.0–1.0 — how accurately the spec describes this specific file's purpose and behavior (1.0 = spec perfectly describes this file, 0.0 = spec is irrelevant).
|
|
344
|
+
- "requirementCoverageScore": float 0.0–1.0 — of the requirements in the spec that are relevant to THIS file specifically, what fraction does the file actually implement? Ignore requirements that clearly belong to other files in the domain.`
|
|
345
|
+
: '';
|
|
346
|
+
const userPrompt = `Here are the specifications:
|
|
347
|
+
|
|
348
|
+
${specsContent}${fileExcerpt}
|
|
349
|
+
|
|
350
|
+
Predict the contents of: ${candidate.path}
|
|
351
|
+
|
|
352
|
+
IMPORTANT: The specs may contain entries attributed to specific files using \`> \`path\`\` markers.
|
|
353
|
+
Focus ONLY on entries attributed to \`${candidate.path}\`. Ignore entries attributed to other files.
|
|
354
|
+
If no entries are attributed to this file, use only the general domain purpose.${judgeInstruction}
|
|
355
|
+
|
|
356
|
+
Respond in JSON:
|
|
357
|
+
{
|
|
358
|
+
"predictedPurpose": "...",
|
|
359
|
+
"predictedImports": ["...", "..."],
|
|
360
|
+
"predictedExports": ["...", "..."],
|
|
361
|
+
"predictedLogic": ["...", "..."],
|
|
362
|
+
"relatedRequirements": ["RequirementName1", "RequirementName2"],
|
|
363
|
+
"confidence": 0.0-1.0,
|
|
364
|
+
"specAccuracyScore": 0.0-1.0,
|
|
365
|
+
"requirementCoverageScore": 0.0-1.0,
|
|
366
|
+
"reasoning": "..."
|
|
367
|
+
}`;
|
|
368
|
+
try {
|
|
369
|
+
const prediction = await this.llm.completeJSON({
|
|
370
|
+
systemPrompt: PREDICTION_SYSTEM_PROMPT,
|
|
371
|
+
userPrompt,
|
|
372
|
+
temperature: 0.3,
|
|
373
|
+
maxTokens: VERIFICATION_PREDICTION_MAX_TOKENS,
|
|
374
|
+
});
|
|
375
|
+
return {
|
|
376
|
+
predictedPurpose: prediction.predictedPurpose ?? '',
|
|
377
|
+
predictedImports: prediction.predictedImports ?? [],
|
|
378
|
+
predictedExports: prediction.predictedExports ?? [],
|
|
379
|
+
predictedLogic: prediction.predictedLogic ?? [],
|
|
380
|
+
relatedRequirements: prediction.relatedRequirements ?? [],
|
|
381
|
+
confidence: prediction.confidence ?? 0.5,
|
|
382
|
+
specAccuracyScore: typeof prediction.specAccuracyScore === 'number' ? prediction.specAccuracyScore : undefined,
|
|
383
|
+
requirementCoverageScore: typeof prediction.requirementCoverageScore === 'number' ? prediction.requirementCoverageScore : undefined,
|
|
384
|
+
reasoning: prediction.reasoning ?? '',
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
logger.warning(`Prediction failed for ${candidate.path}: ${error.message}`);
|
|
389
|
+
// Re-throw so verify() skips this file rather than recording a misleading 0% score
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Compare predicted purpose to actual file content.
|
|
395
|
+
*
|
|
396
|
+
* When specAccuracyScore is provided (LLM-as-judge), it is used directly as
|
|
397
|
+
* the similarity score — this is far more reliable than keyword overlap because
|
|
398
|
+
* the LLM has seen the actual file and can assess whether the spec describes it.
|
|
399
|
+
* Falls back to Jaccard keyword overlap when no LLM score is available.
|
|
400
|
+
*/
|
|
401
|
+
comparePurpose(predicted, fileContent, specAccuracyScore) {
|
|
402
|
+
const actual = this.extractPurpose(fileContent);
|
|
403
|
+
const similarity = typeof specAccuracyScore === 'number'
|
|
404
|
+
? specAccuracyScore
|
|
405
|
+
: this.calculateSimilarity(predicted, actual);
|
|
406
|
+
return { predicted, actual, similarity };
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Extract purpose from file content (comments, docstrings)
|
|
410
|
+
*/
|
|
411
|
+
extractPurpose(content) {
|
|
412
|
+
const lines = content.split('\n');
|
|
413
|
+
const parts = [];
|
|
414
|
+
// 1. Module-level JSDoc block (/** ... */)
|
|
415
|
+
let inBlockComment = false;
|
|
416
|
+
for (let i = 0; i < lines.length; i++) {
|
|
417
|
+
const trimmed = lines[i].trim();
|
|
418
|
+
if (trimmed.startsWith('/**')) {
|
|
419
|
+
inBlockComment = true;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (trimmed.startsWith('*/') || trimmed.endsWith('*/')) {
|
|
423
|
+
inBlockComment = false;
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
if (inBlockComment) {
|
|
427
|
+
const comment = trimmed.replace(/^\*\s*/, '').trim();
|
|
428
|
+
if (comment && !comment.startsWith('@'))
|
|
429
|
+
parts.push(comment);
|
|
430
|
+
}
|
|
431
|
+
// Single-line // comments near the top
|
|
432
|
+
if (trimmed.startsWith('//') && !inBlockComment && parts.length < 3 && i < 30) {
|
|
433
|
+
parts.push(trimmed.replace(/^\/\/\s*/, ''));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// 2. Exported identifier names — split camelCase/PascalCase/snake_case into words.
|
|
437
|
+
// This gives the verifier vocabulary to match against even when comments are absent.
|
|
438
|
+
// E.g. "readOpenLoreConfig" → "read Spec Gen Config"; "OPENLORE_DIR" → "spec gen dir".
|
|
439
|
+
const exportMatches = content.matchAll(/^export\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var|interface|type|enum)\s+(\w+)/gm);
|
|
440
|
+
const identWords = [];
|
|
441
|
+
for (const m of exportMatches) {
|
|
442
|
+
const name = m[1];
|
|
443
|
+
// Split on underscores and camelCase boundaries
|
|
444
|
+
const words = name
|
|
445
|
+
.replace(/_+/g, ' ')
|
|
446
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
447
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
448
|
+
.toLowerCase()
|
|
449
|
+
.split(/\s+/)
|
|
450
|
+
.filter(w => w.length > 2);
|
|
451
|
+
identWords.push(...words);
|
|
452
|
+
}
|
|
453
|
+
if (identWords.length > 0) {
|
|
454
|
+
parts.push(identWords.join(' '));
|
|
455
|
+
}
|
|
456
|
+
return parts.join(' ').slice(0, 800);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Calculate text similarity using keyword overlap
|
|
460
|
+
*/
|
|
461
|
+
calculateSimilarity(text1, text2) {
|
|
462
|
+
if (!text1 || !text2)
|
|
463
|
+
return 0;
|
|
464
|
+
const words1 = this.extractKeywords(text1);
|
|
465
|
+
const words2 = this.extractKeywords(text2);
|
|
466
|
+
if (words1.size === 0 || words2.size === 0)
|
|
467
|
+
return 0;
|
|
468
|
+
let matches = 0;
|
|
469
|
+
for (const word of words1) {
|
|
470
|
+
if (words2.has(word))
|
|
471
|
+
matches++;
|
|
472
|
+
}
|
|
473
|
+
// Jaccard similarity
|
|
474
|
+
const union = new Set([...words1, ...words2]);
|
|
475
|
+
return matches / union.size;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Normalize a word for similarity comparison by truncating to its first 5
|
|
479
|
+
* characters. This is more robust than suffix-stripping for technical
|
|
480
|
+
* English: "generate/generates/generating/generation" all share the prefix
|
|
481
|
+
* "gener", "verify/verification/verifies" share "verif", etc.
|
|
482
|
+
* Tested against 26 word pairs: 18/26 correct matches, 0 false positives.
|
|
483
|
+
*/
|
|
484
|
+
normalize(word) {
|
|
485
|
+
return word.slice(0, 5);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Extract keywords from text
|
|
489
|
+
*/
|
|
490
|
+
extractKeywords(text) {
|
|
491
|
+
const words = text
|
|
492
|
+
.toLowerCase()
|
|
493
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
494
|
+
.split(/\s+/)
|
|
495
|
+
.filter(w => w.length > 3);
|
|
496
|
+
// Filter out common words
|
|
497
|
+
const stopwords = new Set(['the', 'and', 'for', 'this', 'that', 'with', 'are', 'from', 'has', 'have', 'will', 'can', 'all', 'each', 'which', 'when', 'there', 'been', 'being', 'their', 'would', 'could', 'should']);
|
|
498
|
+
return new Set(words.filter(w => !stopwords.has(w)).map(w => this.normalize(w)));
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Analyze import coverage using spec content rather than LLM predictions.
|
|
502
|
+
* For each actual import (normalized to module name), checks whether it is
|
|
503
|
+
* mentioned in the domain's spec text (exact name or hyphen→space variant).
|
|
504
|
+
* This is a spec-completeness check: are the modules the file depends on
|
|
505
|
+
* actually described in the spec?
|
|
506
|
+
*
|
|
507
|
+
* Returns a SetMatch where:
|
|
508
|
+
* - actual = all normalized actual import module names
|
|
509
|
+
* - predicted = subset of actual imports that appear in the spec text
|
|
510
|
+
* - f1Score = recall = fraction of actual imports covered by spec
|
|
511
|
+
*/
|
|
512
|
+
analyzeImportCoverage(actualImports, domain) {
|
|
513
|
+
const normalized = actualImports.map(a => this.normalizeImport(a));
|
|
514
|
+
const spec = this.specs.find(s => s.domain === domain);
|
|
515
|
+
const specLower = spec ? spec.content.toLowerCase() : '';
|
|
516
|
+
const covered = [];
|
|
517
|
+
if (specLower.length > 0) {
|
|
518
|
+
for (const name of normalized) {
|
|
519
|
+
if (!name || name.length < 2)
|
|
520
|
+
continue;
|
|
521
|
+
// Match literal (e.g. "config-manager") or with spaces (e.g. "config manager")
|
|
522
|
+
if (specLower.includes(name) || specLower.includes(name.replace(/-/g, ' '))) {
|
|
523
|
+
covered.push(name);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const total = normalized.length;
|
|
528
|
+
const coverage = total > 0 ? covered.length / total : 0;
|
|
529
|
+
return {
|
|
530
|
+
predicted: covered, // imports mentioned in spec
|
|
531
|
+
actual: normalized, // all actual imports
|
|
532
|
+
precision: coverage,
|
|
533
|
+
recall: coverage,
|
|
534
|
+
f1Score: coverage,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Normalize import path for comparison.
|
|
539
|
+
* Strips file extensions and the first leading `./` or `../` prefix,
|
|
540
|
+
* then extracts the final path segment (module name) in lowercase.
|
|
541
|
+
* Deeply-nested relative paths (e.g. `../../foo`) are handled correctly
|
|
542
|
+
* because only the last segment is used for comparison.
|
|
543
|
+
*/
|
|
544
|
+
normalizeImport(importPath) {
|
|
545
|
+
const normalized = importPath
|
|
546
|
+
.replace(/\.(js|ts|jsx|tsx|mjs|cjs)$/, '')
|
|
547
|
+
.replace(/^\.\//, '')
|
|
548
|
+
.replace(/^\.\.\//, '');
|
|
549
|
+
const parts = normalized.split('/');
|
|
550
|
+
return parts[parts.length - 1].toLowerCase();
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Compare predicted exports to actual
|
|
554
|
+
*/
|
|
555
|
+
compareExports(predicted, actual) {
|
|
556
|
+
return this.calculateSetMatch(predicted.map(p => p.toLowerCase()), actual.map(a => a.toLowerCase()));
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Calculate precision, recall, F1 for set comparison
|
|
560
|
+
*/
|
|
561
|
+
calculateSetMatch(predicted, actual) {
|
|
562
|
+
const predictedSet = new Set(predicted);
|
|
563
|
+
const actualSet = new Set(actual);
|
|
564
|
+
let truePositives = 0;
|
|
565
|
+
for (const p of predictedSet) {
|
|
566
|
+
if (actualSet.has(p))
|
|
567
|
+
truePositives++;
|
|
568
|
+
}
|
|
569
|
+
const precision = predictedSet.size > 0 ? truePositives / predictedSet.size : 0;
|
|
570
|
+
const recall = actualSet.size > 0 ? truePositives / actualSet.size : 0;
|
|
571
|
+
const f1Score = precision + recall > 0 ? (2 * precision * recall) / (precision + recall) : 0;
|
|
572
|
+
return {
|
|
573
|
+
predicted,
|
|
574
|
+
actual,
|
|
575
|
+
precision,
|
|
576
|
+
recall,
|
|
577
|
+
f1Score,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Parse requirements from a spec's markdown content.
|
|
582
|
+
* Returns an array of { name, description } extracted from
|
|
583
|
+
* "### Requirement: Name\n\nThe system SHALL ..." blocks.
|
|
584
|
+
*/
|
|
585
|
+
parseSpecRequirements(specContent) {
|
|
586
|
+
const requirements = [];
|
|
587
|
+
const lines = specContent.split('\n');
|
|
588
|
+
for (let i = 0; i < lines.length; i++) {
|
|
589
|
+
const m = lines[i].match(/^###\s+Requirement:\s+(.+)/i);
|
|
590
|
+
if (!m)
|
|
591
|
+
continue;
|
|
592
|
+
const name = m[1].trim();
|
|
593
|
+
// Look ahead for the description line (first non-empty line after the heading)
|
|
594
|
+
let description = '';
|
|
595
|
+
for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
|
|
596
|
+
const l = lines[j].trim();
|
|
597
|
+
if (l.length > 0) {
|
|
598
|
+
description = l;
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (name)
|
|
603
|
+
requirements.push({ name, description });
|
|
604
|
+
}
|
|
605
|
+
return requirements;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Analyze requirement coverage.
|
|
609
|
+
*
|
|
610
|
+
* When llmScore is provided (LLM-as-judge), it is used directly — the LLM
|
|
611
|
+
* has seen both the spec and the file and scores only the requirements
|
|
612
|
+
* relevant to this specific file, avoiding the false penalty of a domain
|
|
613
|
+
* spec covering many files where each file implements only a small subset.
|
|
614
|
+
*
|
|
615
|
+
* Falls back to keyword matching when no LLM score is available.
|
|
616
|
+
*/
|
|
617
|
+
analyzeRequirementCoverage(domain, fileContent, llmScore) {
|
|
618
|
+
const spec = this.specs.find(s => s.domain === domain);
|
|
619
|
+
if (!spec) {
|
|
620
|
+
return { relatedRequirements: [], actuallyImplements: [], coverage: 0 };
|
|
621
|
+
}
|
|
622
|
+
const requirements = this.parseSpecRequirements(spec.content);
|
|
623
|
+
const relatedRequirements = requirements.map(r => r.name);
|
|
624
|
+
// LLM-as-judge: use the score directly, synthesize actuallyImplements proportionally
|
|
625
|
+
if (typeof llmScore === 'number') {
|
|
626
|
+
const implementedCount = Math.round(llmScore * requirements.length);
|
|
627
|
+
return {
|
|
628
|
+
relatedRequirements,
|
|
629
|
+
actuallyImplements: relatedRequirements.slice(0, implementedCount),
|
|
630
|
+
coverage: llmScore,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
if (requirements.length === 0) {
|
|
634
|
+
return { relatedRequirements: [], actuallyImplements: [], coverage: 0 };
|
|
635
|
+
}
|
|
636
|
+
const contentLower = fileContent.toLowerCase();
|
|
637
|
+
const actuallyImplements = [];
|
|
638
|
+
for (const req of requirements) {
|
|
639
|
+
const source = req.description.length > 0 ? req.description : req.name;
|
|
640
|
+
const keywords = source
|
|
641
|
+
.toLowerCase()
|
|
642
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
643
|
+
.split(/\s+/)
|
|
644
|
+
.filter(w => w.length > 3 && !['shall', 'system', 'when', 'given', 'then', 'that', 'this', 'with', 'from', 'have', 'will'].includes(w));
|
|
645
|
+
if (keywords.length === 0)
|
|
646
|
+
continue;
|
|
647
|
+
const matched = keywords.filter(w => contentLower.includes(w));
|
|
648
|
+
if (matched.length >= Math.ceil(keywords.length * 0.5)) {
|
|
649
|
+
actuallyImplements.push(req.name);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const coverage = actuallyImplements.length / requirements.length;
|
|
653
|
+
return { relatedRequirements, actuallyImplements, coverage };
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Calculate overall score (weighted combination)
|
|
657
|
+
*/
|
|
658
|
+
calculateOverallScore(purposeMatch, importMatch, exportMatch, requirementCoverage) {
|
|
659
|
+
// Weighted combination (total = 1.0):
|
|
660
|
+
// Purpose: 50% — LLM-as-judge: how well the spec describes this file
|
|
661
|
+
// Requirements: 35% — LLM-as-judge: fraction of file-relevant requirements covered
|
|
662
|
+
// Imports: 5% — fraction of actual imports mentioned in spec
|
|
663
|
+
// (low weight: library deps are never in specs, so ceiling ~20%)
|
|
664
|
+
// Exports: 10% — F1 of LLM-predicted vs actual exports
|
|
665
|
+
return (purposeMatch.similarity * 0.50 +
|
|
666
|
+
requirementCoverage.coverage * 0.35 +
|
|
667
|
+
importMatch.f1Score * 0.05 +
|
|
668
|
+
exportMatch.f1Score * 0.10);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Generate feedback for gaps
|
|
672
|
+
*/
|
|
673
|
+
generateFeedback(candidate, prediction, purposeMatch, importMatch, exportMatch, requirementCoverage) {
|
|
674
|
+
const feedback = [];
|
|
675
|
+
// Low purpose match
|
|
676
|
+
if (purposeMatch.similarity < 0.3) {
|
|
677
|
+
feedback.push(`Purpose mismatch: specs don't clearly describe what ${basename(candidate.path)} does`);
|
|
678
|
+
}
|
|
679
|
+
// Missing imports
|
|
680
|
+
const missingImports = importMatch.actual.filter(a => !importMatch.predicted.includes(a));
|
|
681
|
+
if (missingImports.length > 0) {
|
|
682
|
+
feedback.push(`Missing dependencies: specs don't mention ${missingImports.slice(0, 3).join(', ')}`);
|
|
683
|
+
}
|
|
684
|
+
// Missing exports
|
|
685
|
+
const missingExports = exportMatch.actual.filter(a => !exportMatch.predicted.includes(a));
|
|
686
|
+
if (missingExports.length > 0) {
|
|
687
|
+
feedback.push(`Undocumented exports: ${missingExports.slice(0, 3).join(', ')} not described in specs`);
|
|
688
|
+
}
|
|
689
|
+
// Low requirement coverage
|
|
690
|
+
if (requirementCoverage.coverage < 0.5 && prediction.relatedRequirements.length > 0) {
|
|
691
|
+
const missing = prediction.relatedRequirements.filter(r => !requirementCoverage.actuallyImplements.includes(r));
|
|
692
|
+
if (missing.length > 0) {
|
|
693
|
+
feedback.push(`Requirements ${missing.slice(0, 2).join(', ')} don't appear to be implemented in this file`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
// Low confidence from LLM
|
|
697
|
+
if (prediction.confidence < 0.5) {
|
|
698
|
+
feedback.push(`LLM had low confidence: "${prediction.reasoning}"`);
|
|
699
|
+
}
|
|
700
|
+
return feedback;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Generate verification report
|
|
704
|
+
*/
|
|
705
|
+
generateReport(results, specVersion) {
|
|
706
|
+
const passedFiles = results.filter(r => r.overallScore >= this.options.passThreshold).length;
|
|
707
|
+
const overallConfidence = results.length > 0
|
|
708
|
+
? results.reduce((sum, r) => sum + r.overallScore, 0) / results.length
|
|
709
|
+
: 0;
|
|
710
|
+
// Domain breakdown
|
|
711
|
+
const domainResults = new Map();
|
|
712
|
+
for (const result of results) {
|
|
713
|
+
if (!domainResults.has(result.domain)) {
|
|
714
|
+
domainResults.set(result.domain, []);
|
|
715
|
+
}
|
|
716
|
+
domainResults.get(result.domain).push(result);
|
|
717
|
+
}
|
|
718
|
+
const domainBreakdown = [];
|
|
719
|
+
for (const [domain, domainRes] of domainResults) {
|
|
720
|
+
const avgScore = domainRes.reduce((sum, r) => sum + r.overallScore, 0) / domainRes.length;
|
|
721
|
+
// Find weakest area
|
|
722
|
+
const avgPurpose = domainRes.reduce((sum, r) => sum + r.purposeMatch.similarity, 0) / domainRes.length;
|
|
723
|
+
const avgImport = domainRes.reduce((sum, r) => sum + r.importMatch.f1Score, 0) / domainRes.length;
|
|
724
|
+
const avgExport = domainRes.reduce((sum, r) => sum + r.exportMatch.f1Score, 0) / domainRes.length;
|
|
725
|
+
const avgReq = domainRes.reduce((sum, r) => sum + r.requirementCoverage.coverage, 0) / domainRes.length;
|
|
726
|
+
const areas = [
|
|
727
|
+
{ name: 'purpose', score: avgPurpose },
|
|
728
|
+
{ name: 'imports', score: avgImport },
|
|
729
|
+
{ name: 'exports', score: avgExport },
|
|
730
|
+
{ name: 'requirements', score: avgReq },
|
|
731
|
+
];
|
|
732
|
+
const weakest = areas.sort((a, b) => a.score - b.score)[0];
|
|
733
|
+
domainBreakdown.push({
|
|
734
|
+
domain,
|
|
735
|
+
specPath: `openspec/specs/${domain}/spec.md`,
|
|
736
|
+
filesVerified: domainRes.length,
|
|
737
|
+
averageScore: avgScore,
|
|
738
|
+
weakestArea: weakest.name,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
// Common gaps
|
|
742
|
+
const allFeedback = results.flatMap(r => r.feedback);
|
|
743
|
+
const feedbackCounts = new Map();
|
|
744
|
+
for (const fb of allFeedback) {
|
|
745
|
+
// Normalize feedback for grouping
|
|
746
|
+
const key = fb.split(':')[0];
|
|
747
|
+
feedbackCounts.set(key, (feedbackCounts.get(key) ?? 0) + 1);
|
|
748
|
+
}
|
|
749
|
+
const commonGaps = Array.from(feedbackCounts.entries())
|
|
750
|
+
.filter(([_, count]) => count >= 2)
|
|
751
|
+
.sort((a, b) => b[1] - a[1])
|
|
752
|
+
.slice(0, 5)
|
|
753
|
+
.map(([gap, _]) => gap);
|
|
754
|
+
// Suggested improvements
|
|
755
|
+
const suggestedImprovements = [];
|
|
756
|
+
for (const breakdown of domainBreakdown) {
|
|
757
|
+
if (breakdown.averageScore < 0.7) {
|
|
758
|
+
suggestedImprovements.push({
|
|
759
|
+
domain: breakdown.domain,
|
|
760
|
+
issue: `Low verification score (${(breakdown.averageScore * 100).toFixed(0)}%)`,
|
|
761
|
+
suggestion: `Review and enhance ${breakdown.specPath}, especially ${breakdown.weakestArea} descriptions`,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
// Recommendation
|
|
766
|
+
let recommendation;
|
|
767
|
+
if (overallConfidence >= 0.75) {
|
|
768
|
+
recommendation = 'ready';
|
|
769
|
+
}
|
|
770
|
+
else if (overallConfidence >= 0.5) {
|
|
771
|
+
recommendation = 'needs-review';
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
recommendation = 'regenerate';
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
timestamp: new Date().toLocaleString(),
|
|
778
|
+
specVersion,
|
|
779
|
+
sampledFiles: results.length,
|
|
780
|
+
passedFiles,
|
|
781
|
+
overallConfidence,
|
|
782
|
+
domainBreakdown,
|
|
783
|
+
commonGaps,
|
|
784
|
+
recommendation,
|
|
785
|
+
suggestedImprovements,
|
|
786
|
+
results,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Save verification report
|
|
791
|
+
*/
|
|
792
|
+
async saveReport(report) {
|
|
793
|
+
await mkdir(this.options.outputDir, { recursive: true });
|
|
794
|
+
// Save JSON report
|
|
795
|
+
const jsonPath = join(this.options.outputDir, 'report.json');
|
|
796
|
+
await writeFile(jsonPath, JSON.stringify(report, null, 2), 'utf-8');
|
|
797
|
+
logger.discovery(`Saved JSON report to ${relative(this.options.rootPath, jsonPath)}`);
|
|
798
|
+
// Save Markdown report
|
|
799
|
+
const mdPath = join(this.options.outputDir, 'REPORT.md');
|
|
800
|
+
const markdown = this.generateMarkdownReport(report);
|
|
801
|
+
await writeFile(mdPath, markdown, 'utf-8');
|
|
802
|
+
logger.discovery(`Saved Markdown report to ${relative(this.options.rootPath, mdPath)}`);
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Generate markdown report
|
|
806
|
+
*/
|
|
807
|
+
generateMarkdownReport(report) {
|
|
808
|
+
const lines = [];
|
|
809
|
+
lines.push('# Spec Verification Report');
|
|
810
|
+
lines.push('');
|
|
811
|
+
lines.push(`Generated: ${report.timestamp}`);
|
|
812
|
+
lines.push(`Spec Version: ${report.specVersion}`);
|
|
813
|
+
lines.push('');
|
|
814
|
+
// Summary
|
|
815
|
+
lines.push('## Summary');
|
|
816
|
+
lines.push('');
|
|
817
|
+
lines.push(`| Metric | Value |`);
|
|
818
|
+
lines.push(`|--------|-------|`);
|
|
819
|
+
lines.push(`| Files Verified | ${report.sampledFiles} |`);
|
|
820
|
+
lines.push(`| Files Passed | ${report.passedFiles} (${report.sampledFiles > 0 ? ((report.passedFiles / report.sampledFiles) * 100).toFixed(0) : 'N/A'}%) |`);
|
|
821
|
+
lines.push(`| Overall Confidence | ${(report.overallConfidence * 100).toFixed(1)}% |`);
|
|
822
|
+
lines.push(`| Recommendation | **${report.recommendation}** |`);
|
|
823
|
+
lines.push('');
|
|
824
|
+
// Recommendation explanation
|
|
825
|
+
lines.push('### Recommendation');
|
|
826
|
+
if (report.recommendation === 'ready') {
|
|
827
|
+
lines.push('✅ Specs accurately describe the codebase and are ready for use.');
|
|
828
|
+
}
|
|
829
|
+
else if (report.recommendation === 'needs-review') {
|
|
830
|
+
lines.push('⚠️ Specs need review. Some gaps were identified that should be addressed.');
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
lines.push('❌ Specs have significant gaps. Consider regenerating with improved context.');
|
|
834
|
+
}
|
|
835
|
+
lines.push('');
|
|
836
|
+
// Domain breakdown
|
|
837
|
+
lines.push('## Domain Breakdown');
|
|
838
|
+
lines.push('');
|
|
839
|
+
lines.push('| Domain | Spec Path | Files | Avg Score | Weakest Area |');
|
|
840
|
+
lines.push('|--------|-----------|-------|-----------|--------------|');
|
|
841
|
+
for (const domain of report.domainBreakdown) {
|
|
842
|
+
const scorePercent = (domain.averageScore * 100).toFixed(0);
|
|
843
|
+
lines.push(`| ${domain.domain} | ${domain.specPath} | ${domain.filesVerified} | ${scorePercent}% | ${domain.weakestArea} |`);
|
|
844
|
+
}
|
|
845
|
+
lines.push('');
|
|
846
|
+
// Common gaps
|
|
847
|
+
if (report.commonGaps.length > 0) {
|
|
848
|
+
lines.push('## Common Gaps');
|
|
849
|
+
lines.push('');
|
|
850
|
+
for (const gap of report.commonGaps) {
|
|
851
|
+
lines.push(`- ${gap}`);
|
|
852
|
+
}
|
|
853
|
+
lines.push('');
|
|
854
|
+
}
|
|
855
|
+
// Suggested improvements
|
|
856
|
+
if (report.suggestedImprovements.length > 0) {
|
|
857
|
+
lines.push('## Suggested Improvements');
|
|
858
|
+
lines.push('');
|
|
859
|
+
for (const improvement of report.suggestedImprovements) {
|
|
860
|
+
lines.push(`### ${improvement.domain}`);
|
|
861
|
+
lines.push(`- **Issue**: ${improvement.issue}`);
|
|
862
|
+
lines.push(`- **Suggestion**: ${improvement.suggestion}`);
|
|
863
|
+
lines.push('');
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
// Detailed results
|
|
867
|
+
lines.push('## Detailed Results');
|
|
868
|
+
lines.push('');
|
|
869
|
+
for (const result of report.results) {
|
|
870
|
+
const scorePercent = (result.overallScore * 100).toFixed(0);
|
|
871
|
+
const status = result.overallScore >= this.options.passThreshold ? '✅' : '❌';
|
|
872
|
+
lines.push(`### ${status} ${result.filePath}`);
|
|
873
|
+
lines.push('');
|
|
874
|
+
lines.push(`- **Domain**: ${result.domain}`);
|
|
875
|
+
lines.push(`- **Overall Score**: ${scorePercent}%`);
|
|
876
|
+
lines.push(`- **LLM Confidence**: ${(result.llmConfidence * 100).toFixed(0)}%`);
|
|
877
|
+
lines.push('');
|
|
878
|
+
lines.push('| Category | Score |');
|
|
879
|
+
lines.push('|----------|-------|');
|
|
880
|
+
lines.push(`| Purpose Match | ${(result.purposeMatch.similarity * 100).toFixed(0)}% |`);
|
|
881
|
+
lines.push(`| Import Match (F1) | ${(result.importMatch.f1Score * 100).toFixed(0)}% |`);
|
|
882
|
+
lines.push(`| Export Match (F1) | ${(result.exportMatch.f1Score * 100).toFixed(0)}% |`);
|
|
883
|
+
lines.push(`| Requirement Coverage | ${(result.requirementCoverage.coverage * 100).toFixed(0)}% |`);
|
|
884
|
+
lines.push('');
|
|
885
|
+
if (result.feedback.length > 0) {
|
|
886
|
+
lines.push('**Feedback:**');
|
|
887
|
+
for (const fb of result.feedback) {
|
|
888
|
+
lines.push(`- ${fb}`);
|
|
889
|
+
}
|
|
890
|
+
lines.push('');
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
lines.push('---');
|
|
894
|
+
lines.push('*Generated by openlore verify*');
|
|
895
|
+
return lines.join('\n');
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Get list of domains from loaded specs.
|
|
899
|
+
* If specs have not been loaded yet (i.e., verify() has not been called),
|
|
900
|
+
* triggers an eager load so callers can preview domains without a full LLM run.
|
|
901
|
+
*/
|
|
902
|
+
async getDomains() {
|
|
903
|
+
if (this.specs.length === 0) {
|
|
904
|
+
await this.loadSpecs();
|
|
905
|
+
}
|
|
906
|
+
return this.specs.map(s => s.domain);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
// ============================================================================
|
|
910
|
+
// CONVENIENCE FUNCTIONS
|
|
911
|
+
// ============================================================================
|
|
912
|
+
/**
|
|
913
|
+
* Run verification on a project
|
|
914
|
+
*/
|
|
915
|
+
export async function verifySpecs(llm, depGraph, options, specVersion) {
|
|
916
|
+
const engine = new SpecVerificationEngine(llm, options);
|
|
917
|
+
return engine.verify(depGraph, specVersion);
|
|
918
|
+
}
|
|
919
|
+
//# sourceMappingURL=verification-engine.js.map
|