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,864 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* openlore decisions command
|
|
3
|
+
*
|
|
4
|
+
* Agent-recorded architectural decision workflow:
|
|
5
|
+
* record (via MCP) → consolidate → verify → approve → sync → spec.md
|
|
6
|
+
*
|
|
7
|
+
* Can be installed as a pre-commit hook that gates commits until decisions
|
|
8
|
+
* are reviewed.
|
|
9
|
+
*/
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { readFile, writeFile, mkdir, chmod } from 'node:fs/promises';
|
|
12
|
+
import { execFile } from 'node:child_process';
|
|
13
|
+
import { promisify } from 'node:util';
|
|
14
|
+
const execFileAsync = promisify(execFile);
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { logger } from '../../utils/logger.js';
|
|
17
|
+
import { fileExists, resolveLLMProvider } from '../../utils/command-helpers.js';
|
|
18
|
+
import { readOpenLoreConfig } from '../../core/services/config-manager.js';
|
|
19
|
+
import { createLLMService } from '../../core/services/llm-service.js';
|
|
20
|
+
import { isGitRepository, getChangedFiles, getFileDiff, getCommitMessages, resolveBaseRef, buildSpecMap } from '../../core/drift/index.js';
|
|
21
|
+
import { loadDecisionStore, saveDecisionStore, replaceDecisions, patchDecision, getDecisionsByStatus, INACTIVE_STATUSES, } from '../../core/decisions/store.js';
|
|
22
|
+
import { consolidateDrafts } from '../../core/decisions/consolidator.js';
|
|
23
|
+
import { extractFromDiff } from '../../core/decisions/extractor.js';
|
|
24
|
+
import { verifyDecisions } from '../../core/decisions/verifier.js';
|
|
25
|
+
import { syncApprovedDecisions } from '../../core/decisions/syncer.js';
|
|
26
|
+
import { OPENLORE_DIR, OPENLORE_LOGS_SUBDIR, OPENSPEC_DIR, OPENSPEC_SPECS_SUBDIR, DECISIONS_EXTRACTION_MAX_FILES, DECISIONS_DIFF_MAX_CHARS, CONSOLIDATION_GRACE_PERIOD_MS, GATE_REASONS, } from '../../constants.js';
|
|
27
|
+
import { runTuiApproval } from '../tui-approval.js';
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// AGENT INSTRUCTION FILES
|
|
30
|
+
// ============================================================================
|
|
31
|
+
const AGENT_INSTRUCTIONS_MARKER = '<!-- openlore-decisions-instructions -->';
|
|
32
|
+
const AGENT_INSTRUCTIONS_BLOCK = `${AGENT_INSTRUCTIONS_MARKER}
|
|
33
|
+
## Architectural decisions
|
|
34
|
+
|
|
35
|
+
When making a significant design choice, call \`record_decision\` **before** writing the code.
|
|
36
|
+
|
|
37
|
+
Significant choices: data structure, library/dependency, API contract, auth strategy,
|
|
38
|
+
module boundary, database schema, caching approach, error handling pattern.
|
|
39
|
+
|
|
40
|
+
\`\`\`
|
|
41
|
+
record_decision({
|
|
42
|
+
title: "Use JWTs for stateless auth",
|
|
43
|
+
rationale: "Avoids session store in infra",
|
|
44
|
+
consequences: "Tokens can't be revoked early",
|
|
45
|
+
affectedFiles: ["src/auth/middleware.ts"],
|
|
46
|
+
supersedes: "a1b2c3d4" // 8-char ID of prior decision being reversed
|
|
47
|
+
})
|
|
48
|
+
\`\`\`
|
|
49
|
+
|
|
50
|
+
Decisions are consolidated in the background immediately after \`record_decision\` is called — the pre-commit gate reads the already-consolidated store and adds no LLM latency.
|
|
51
|
+
|
|
52
|
+
**Performance note**: if you skip \`record_decision\`, the gate detects unrecorded source changes at commit time and triggers a slow LLM extraction on the *next* commit (~10-30s). Calling \`record_decision\` proactively keeps every commit instant.
|
|
53
|
+
|
|
54
|
+
## When git commit is blocked by the decisions gate
|
|
55
|
+
|
|
56
|
+
If \`git commit\` fails and the output is JSON with \`"gated": true\`, do NOT retry silently.
|
|
57
|
+
Check the \`reason\` field and act accordingly:
|
|
58
|
+
|
|
59
|
+
**\`reason: "verified"\` — decisions await review:**
|
|
60
|
+
Present each decision to the user:
|
|
61
|
+
> "The commit is blocked — I found N architectural decision(s) to validate:
|
|
62
|
+
> 1. **[id]** Title — rationale
|
|
63
|
+
Do you approve? (yes/no)"
|
|
64
|
+
For each approval call \`approve_decision\`, for rejections call \`reject_decision\`.
|
|
65
|
+
Then run \`openlore decisions --sync\` and retry \`git commit\`.
|
|
66
|
+
|
|
67
|
+
**\`reason: "approved_not_synced"\` — decisions approved but not written to specs:**
|
|
68
|
+
Run \`openlore decisions --sync\` then retry \`git commit\`. Do not skip this step.
|
|
69
|
+
|
|
70
|
+
**\`reason: "drafts_pending_consolidation"\` — drafts were recorded but not yet consolidated:**
|
|
71
|
+
Present to the user:
|
|
72
|
+
> "N decision draft(s) were recorded but never consolidated. Run consolidation now? (~10-30s)"
|
|
73
|
+
If yes: run \`openlore decisions --consolidate --gate\` and handle the result.
|
|
74
|
+
If no: retry with \`git commit --no-verify\` to skip the gate.
|
|
75
|
+
|
|
76
|
+
**\`reason: "no_decisions_recorded"\` — source files staged but nothing recorded:**
|
|
77
|
+
Present to the user:
|
|
78
|
+
> "Source files are staged but no architectural decisions were recorded. Run fallback extraction to check for undocumented decisions? (~10-30s)"
|
|
79
|
+
If yes: run \`openlore decisions --consolidate --gate\` and handle the result.
|
|
80
|
+
If no: retry with \`git commit --no-verify\` to skip the gate.
|
|
81
|
+
<!-- end-openlore-decisions-instructions -->
|
|
82
|
+
`;
|
|
83
|
+
/** Inject decisions instructions into an existing agent file, idempotently. */
|
|
84
|
+
async function injectAgentInstructions(filePath) {
|
|
85
|
+
if (!(await fileExists(filePath)))
|
|
86
|
+
return 'missing';
|
|
87
|
+
const content = await readFile(filePath, 'utf-8');
|
|
88
|
+
if (content.includes(AGENT_INSTRUCTIONS_MARKER))
|
|
89
|
+
return 'already';
|
|
90
|
+
await writeFile(filePath, content.trimEnd() + '\n\n' + AGENT_INSTRUCTIONS_BLOCK, 'utf-8');
|
|
91
|
+
return 'injected';
|
|
92
|
+
}
|
|
93
|
+
/** Remove decisions instructions block from an agent file. */
|
|
94
|
+
async function removeAgentInstructions(filePath) {
|
|
95
|
+
if (!(await fileExists(filePath)))
|
|
96
|
+
return;
|
|
97
|
+
const content = await readFile(filePath, 'utf-8');
|
|
98
|
+
if (!content.includes(AGENT_INSTRUCTIONS_MARKER))
|
|
99
|
+
return;
|
|
100
|
+
const cleaned = content
|
|
101
|
+
.replace(/\n*<!-- openlore-decisions-instructions -->[\s\S]*?<!-- end-openlore-decisions-instructions -->\n*/g, '')
|
|
102
|
+
.trim();
|
|
103
|
+
await writeFile(filePath, cleaned + '\n', 'utf-8');
|
|
104
|
+
}
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// HOOK MANAGEMENT
|
|
107
|
+
// ============================================================================
|
|
108
|
+
const HOOK_MARKER = '# openlore-decisions-hook';
|
|
109
|
+
const HOOK_CONTENT = `${HOOK_MARKER}
|
|
110
|
+
# Gate commits until architectural decisions are reviewed.
|
|
111
|
+
# Installed by: openlore setup --tools claude
|
|
112
|
+
|
|
113
|
+
# Prefer local build over global install.
|
|
114
|
+
if [ -f "./node_modules/.bin/openlore" ]; then
|
|
115
|
+
./node_modules/.bin/openlore decisions --gate 2>&1
|
|
116
|
+
DECISIONS_EXIT=$?
|
|
117
|
+
elif [ -f "./dist/cli/index.js" ]; then
|
|
118
|
+
node ./dist/cli/index.js decisions --gate 2>&1
|
|
119
|
+
DECISIONS_EXIT=$?
|
|
120
|
+
else
|
|
121
|
+
OPENLORE=$(command -v openlore 2>/dev/null)
|
|
122
|
+
if [ -n "$OPENLORE" ] && "$OPENLORE" decisions --help 2>&1 | grep -q -- '--gate'; then
|
|
123
|
+
"$OPENLORE" decisions --gate 2>&1
|
|
124
|
+
DECISIONS_EXIT=$?
|
|
125
|
+
else
|
|
126
|
+
DECISIONS_EXIT=0
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
if [ "$DECISIONS_EXIT" -ne 0 ]; then
|
|
130
|
+
exit "$DECISIONS_EXIT"
|
|
131
|
+
fi
|
|
132
|
+
# Sentinel written on successful gate pass. Post-commit checks for its absence to detect --no-verify bypass.
|
|
133
|
+
touch "$(git rev-parse --git-dir 2>/dev/null || echo .git)/OPENLORE_GATE_RAN" 2>/dev/null || true
|
|
134
|
+
# end-openlore-decisions-hook
|
|
135
|
+
`;
|
|
136
|
+
const POST_COMMIT_HOOK_MARKER = '# openlore-decisions-post-hook';
|
|
137
|
+
const POST_COMMIT_HOOK_CONTENT = `${POST_COMMIT_HOOK_MARKER}
|
|
138
|
+
# Warn when the pre-commit gate was bypassed via --no-verify.
|
|
139
|
+
# post-commit is NOT skipped by --no-verify (only pre-commit and commit-msg are).
|
|
140
|
+
SENTINEL="$(git rev-parse --git-dir 2>/dev/null || echo .git)/OPENLORE_GATE_RAN"
|
|
141
|
+
if [ -f "$SENTINEL" ]; then
|
|
142
|
+
rm -f "$SENTINEL"
|
|
143
|
+
else
|
|
144
|
+
echo "" >&2
|
|
145
|
+
echo "⚠️ openlore: pre-commit gate was bypassed (--no-verify)." >&2
|
|
146
|
+
echo " Architectural decisions were NOT reviewed for this commit." >&2
|
|
147
|
+
echo " Run: openlore decisions --consolidate --gate" >&2
|
|
148
|
+
echo "" >&2
|
|
149
|
+
fi
|
|
150
|
+
# end-openlore-decisions-post-hook
|
|
151
|
+
`;
|
|
152
|
+
async function ensureGitignored(rootPath, entry) {
|
|
153
|
+
const gitignorePath = join(rootPath, '.gitignore');
|
|
154
|
+
let content = '';
|
|
155
|
+
if (await fileExists(gitignorePath)) {
|
|
156
|
+
content = await readFile(gitignorePath, 'utf-8');
|
|
157
|
+
if (content.split('\n').some((l) => l.trim() === entry))
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
await writeFile(gitignorePath, content.trimEnd() + '\n' + entry + '\n', 'utf-8');
|
|
161
|
+
logger.discovery(` → added ${entry} to .gitignore`);
|
|
162
|
+
}
|
|
163
|
+
export async function installPreCommitHook(rootPath) {
|
|
164
|
+
const hooksDir = join(rootPath, '.git', 'hooks');
|
|
165
|
+
const hookPath = join(hooksDir, 'pre-commit');
|
|
166
|
+
if (!(await fileExists(join(rootPath, '.git')))) {
|
|
167
|
+
logger.error('Not a git repository. Cannot install hook.');
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
await mkdir(hooksDir, { recursive: true });
|
|
172
|
+
let existingContent = '';
|
|
173
|
+
if (await fileExists(hookPath)) {
|
|
174
|
+
existingContent = await readFile(hookPath, 'utf-8');
|
|
175
|
+
if (existingContent.includes(HOOK_MARKER)) {
|
|
176
|
+
logger.success('Pre-commit hook already installed.');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
logger.discovery('Existing pre-commit hook found. Appending decisions gate.');
|
|
180
|
+
// Strip a trailing `exit 0` so our block is not unreachable.
|
|
181
|
+
const stripped = existingContent.trimEnd().replace(/\n*\nexit 0\s*$/, '');
|
|
182
|
+
await writeFile(hookPath, stripped + '\n\n' + HOOK_CONTENT, 'utf-8');
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
await writeFile(hookPath, '#!/bin/sh\n\n' + HOOK_CONTENT, 'utf-8');
|
|
186
|
+
}
|
|
187
|
+
await chmod(hookPath, 0o755);
|
|
188
|
+
logger.success('Pre-commit hook installed at .git/hooks/pre-commit');
|
|
189
|
+
logger.discovery('Commits will be gated until decisions are approved. Use --no-verify to skip.');
|
|
190
|
+
// Install post-commit hook to detect --no-verify bypass
|
|
191
|
+
const postCommitPath = join(hooksDir, 'post-commit');
|
|
192
|
+
let existingPostContent = '';
|
|
193
|
+
if (await fileExists(postCommitPath)) {
|
|
194
|
+
existingPostContent = await readFile(postCommitPath, 'utf-8');
|
|
195
|
+
if (!existingPostContent.includes(POST_COMMIT_HOOK_MARKER)) {
|
|
196
|
+
const strippedPost = existingPostContent.trimEnd().replace(/\n*\nexit 0\s*$/, '');
|
|
197
|
+
await writeFile(postCommitPath, strippedPost + '\n\n' + POST_COMMIT_HOOK_CONTENT, 'utf-8');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
await writeFile(postCommitPath, '#!/bin/sh\n\n' + POST_COMMIT_HOOK_CONTENT, 'utf-8');
|
|
202
|
+
}
|
|
203
|
+
await chmod(postCommitPath, 0o755);
|
|
204
|
+
logger.success('Post-commit hook installed at .git/hooks/post-commit (bypass detector)');
|
|
205
|
+
// Ensure pending decisions store is not accidentally committed
|
|
206
|
+
await ensureGitignored(rootPath, '.openlore/decisions/');
|
|
207
|
+
// Inject record_decision instructions into existing agent context files
|
|
208
|
+
const agentFiles = [
|
|
209
|
+
{ path: join(rootPath, 'CLAUDE.md'), label: 'CLAUDE.md' },
|
|
210
|
+
{ path: join(rootPath, 'AGENTS.md'), label: 'AGENTS.md' },
|
|
211
|
+
{ path: join(rootPath, '.cursorrules'), label: '.cursorrules' },
|
|
212
|
+
{ path: join(rootPath, '.clinerules', 'openlore.md'), label: '.clinerules/openlore.md' },
|
|
213
|
+
{ path: join(rootPath, '.github', 'copilot-instructions.md'), label: '.github/copilot-instructions.md' },
|
|
214
|
+
{ path: join(rootPath, '.windsurf', 'rules.md'), label: '.windsurf/rules.md' },
|
|
215
|
+
{ path: join(rootPath, '.vibe', 'skills', 'openlore.md'), label: '.vibe/skills/openlore.md' },
|
|
216
|
+
];
|
|
217
|
+
for (const { path: filePath, label } of agentFiles) {
|
|
218
|
+
const result = await injectAgentInstructions(filePath);
|
|
219
|
+
if (result === 'injected')
|
|
220
|
+
logger.discovery(` → record_decision instructions added to ${label}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
export async function uninstallPreCommitHook(rootPath) {
|
|
224
|
+
const hookPath = join(rootPath, '.git', 'hooks', 'pre-commit');
|
|
225
|
+
if (!(await fileExists(hookPath))) {
|
|
226
|
+
logger.warning('No pre-commit hook found.');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const content = await readFile(hookPath, 'utf-8');
|
|
230
|
+
if (!content.includes(HOOK_MARKER)) {
|
|
231
|
+
logger.warning('Pre-commit hook does not contain openlore decisions gate.');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const newContent = content
|
|
235
|
+
.replace(/\n*# openlore-decisions-hook[\s\S]*?# end-openlore-decisions-hook\n*/g, '')
|
|
236
|
+
.trim();
|
|
237
|
+
if (!newContent || newContent === '#!/bin/sh') {
|
|
238
|
+
const { unlink } = await import('node:fs/promises');
|
|
239
|
+
await unlink(hookPath);
|
|
240
|
+
logger.success('Pre-commit hook removed (file deleted — was only openlore).');
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
await writeFile(hookPath, newContent + '\n', 'utf-8');
|
|
244
|
+
logger.success('OpenLore decisions gate removed from pre-commit hook.');
|
|
245
|
+
}
|
|
246
|
+
// Remove post-commit bypass detector
|
|
247
|
+
const postCommitPath = join(rootPath, '.git', 'hooks', 'post-commit');
|
|
248
|
+
if (await fileExists(postCommitPath)) {
|
|
249
|
+
const postContent = await readFile(postCommitPath, 'utf-8');
|
|
250
|
+
if (postContent.includes(POST_COMMIT_HOOK_MARKER)) {
|
|
251
|
+
const newPostContent = postContent
|
|
252
|
+
.replace(/\n*# openlore-decisions-post-hook[\s\S]*?# end-openlore-decisions-post-hook\n*/g, '')
|
|
253
|
+
.trim();
|
|
254
|
+
if (!newPostContent || newPostContent === '#!/bin/sh') {
|
|
255
|
+
const { unlink } = await import('node:fs/promises');
|
|
256
|
+
await unlink(postCommitPath);
|
|
257
|
+
logger.success('Post-commit hook removed.');
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
await writeFile(postCommitPath, newPostContent + '\n', 'utf-8');
|
|
261
|
+
logger.success('OpenLore bypass detector removed from post-commit hook.');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Remove record_decision instructions from agent context files
|
|
266
|
+
const agentFiles = [
|
|
267
|
+
join(rootPath, 'CLAUDE.md'),
|
|
268
|
+
join(rootPath, 'AGENTS.md'),
|
|
269
|
+
join(rootPath, '.cursorrules'),
|
|
270
|
+
join(rootPath, '.clinerules', 'openlore.md'),
|
|
271
|
+
join(rootPath, '.github', 'copilot-instructions.md'),
|
|
272
|
+
join(rootPath, '.windsurf', 'rules.md'),
|
|
273
|
+
join(rootPath, '.vibe', 'skills', 'openlore.md'),
|
|
274
|
+
];
|
|
275
|
+
for (const filePath of agentFiles)
|
|
276
|
+
await removeAgentInstructions(filePath);
|
|
277
|
+
}
|
|
278
|
+
const ANALYZE_HOOK_MARKER = 'openlore analyze';
|
|
279
|
+
const ANALYZE_HOOK_ENTRY = {
|
|
280
|
+
_comment: 'openlore: keep call graph fresh after every file edit (debounced 10s)',
|
|
281
|
+
type: 'command',
|
|
282
|
+
command: [
|
|
283
|
+
'LOCK=.openlore/.analyze.lock;',
|
|
284
|
+
'if [ ! -f "$LOCK" ] || [ $(( $(date +%s) - $(cat "$LOCK" 2>/dev/null || echo 0) )) -gt 10 ]; then',
|
|
285
|
+
' echo $(date +%s) > "$LOCK";',
|
|
286
|
+
' openlore analyze --output .openlore/analysis 2>/dev/null & true;',
|
|
287
|
+
'fi',
|
|
288
|
+
].join(' '),
|
|
289
|
+
};
|
|
290
|
+
export async function installClaudeHook(rootPath) {
|
|
291
|
+
const settingsPath = join(rootPath, '.claude', 'settings.json');
|
|
292
|
+
let settings = {};
|
|
293
|
+
try {
|
|
294
|
+
settings = JSON.parse(await readFile(settingsPath, 'utf-8'));
|
|
295
|
+
}
|
|
296
|
+
catch { /* file missing or corrupt — start fresh */ }
|
|
297
|
+
const hooks = settings.hooks?.PostToolUse ?? [];
|
|
298
|
+
if (hooks.some((h) => JSON.stringify(h).includes(ANALYZE_HOOK_MARKER))) {
|
|
299
|
+
logger.success('Claude Code analyze hook already present in .claude/settings.json');
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
settings.hooks ??= {};
|
|
303
|
+
settings.hooks.PostToolUse = [...hooks, ANALYZE_HOOK_ENTRY];
|
|
304
|
+
await mkdir(join(rootPath, '.claude'), { recursive: true });
|
|
305
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
306
|
+
logger.success('Claude Code PostToolUse hook added to .claude/settings.json');
|
|
307
|
+
}
|
|
308
|
+
export async function uninstallClaudeHook(rootPath) {
|
|
309
|
+
const settingsPath = join(rootPath, '.claude', 'settings.json');
|
|
310
|
+
if (!(await fileExists(settingsPath)))
|
|
311
|
+
return;
|
|
312
|
+
try {
|
|
313
|
+
const settings = JSON.parse(await readFile(settingsPath, 'utf-8'));
|
|
314
|
+
const hooks = settings.hooks?.PostToolUse ?? [];
|
|
315
|
+
const filtered = hooks.filter((h) => !JSON.stringify(h).includes('openlore-mine-last') && !JSON.stringify(h).includes(ANALYZE_HOOK_MARKER));
|
|
316
|
+
if (filtered.length === hooks.length)
|
|
317
|
+
return;
|
|
318
|
+
if (filtered.length === 0)
|
|
319
|
+
delete settings.hooks.PostToolUse;
|
|
320
|
+
else
|
|
321
|
+
settings.hooks.PostToolUse = filtered;
|
|
322
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
323
|
+
logger.success('Claude Code PostToolUse hook removed from .claude/settings.json');
|
|
324
|
+
}
|
|
325
|
+
catch { /* settings corrupt — skip */ }
|
|
326
|
+
}
|
|
327
|
+
// ============================================================================
|
|
328
|
+
// DISPLAY HELPERS
|
|
329
|
+
// ============================================================================
|
|
330
|
+
function displayDecision(d, verbose = false) {
|
|
331
|
+
const icon = d.status === 'verified' ? '✓' :
|
|
332
|
+
d.status === 'phantom' ? '↗' :
|
|
333
|
+
d.status === 'approved' ? '●' :
|
|
334
|
+
d.status === 'synced' ? '✔' :
|
|
335
|
+
d.status === 'rejected' ? '✗' : '○';
|
|
336
|
+
const confidence = d.confidence === 'high' ? '\x1b[32mhigh\x1b[0m' :
|
|
337
|
+
d.confidence === 'medium' ? '\x1b[33mmedium\x1b[0m' :
|
|
338
|
+
'\x1b[31mlow\x1b[0m';
|
|
339
|
+
const scopeLabel = d.scope ?? 'component';
|
|
340
|
+
const scopeBadge = scopeLabel === 'system' ? `\x1b[31m[${scopeLabel}]\x1b[0m` :
|
|
341
|
+
scopeLabel === 'cross-domain' ? `\x1b[33m[${scopeLabel}]\x1b[0m` :
|
|
342
|
+
scopeLabel === 'component' ? `\x1b[34m[${scopeLabel}]\x1b[0m` :
|
|
343
|
+
`\x1b[90m[${scopeLabel}]\x1b[0m`;
|
|
344
|
+
console.log(`${icon} [${d.id}] ${scopeBadge} ${d.title}`);
|
|
345
|
+
if (verbose) {
|
|
346
|
+
console.log(` Status : ${d.status} Confidence: ${confidence} Scope: ${scopeLabel}`);
|
|
347
|
+
console.log(` Rationale : ${d.rationale}`);
|
|
348
|
+
if (d.affectedDomains.length)
|
|
349
|
+
console.log(` Domains : ${d.affectedDomains.join(', ')}`);
|
|
350
|
+
if (d.proposedRequirement)
|
|
351
|
+
console.log(` Requirement: ${d.proposedRequirement}`);
|
|
352
|
+
if (d.evidenceFile)
|
|
353
|
+
console.log(` Evidence : ${d.evidenceFile}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function displayMissing(missing) {
|
|
357
|
+
if (missing.length === 0)
|
|
358
|
+
return;
|
|
359
|
+
logger.section('Unrecorded Changes Detected');
|
|
360
|
+
for (const m of missing) {
|
|
361
|
+
logger.warning(`⚠ ${m.file}: ${m.description}`);
|
|
362
|
+
}
|
|
363
|
+
console.log('These changes were not recorded as decisions. Consider adding them with record_decision.');
|
|
364
|
+
}
|
|
365
|
+
// ============================================================================
|
|
366
|
+
// COMMAND
|
|
367
|
+
// ============================================================================
|
|
368
|
+
export const decisionsCommand = new Command('decisions')
|
|
369
|
+
.description('Record, consolidate, and sync architectural decisions to OpenSpec')
|
|
370
|
+
.option('--consolidate', 'Consolidate drafts + verify against diff', false)
|
|
371
|
+
.option('--gate', 'Exit non-zero if decisions await review (for use in hooks)', false)
|
|
372
|
+
.option('--approve <id>', 'Approve a decision by ID')
|
|
373
|
+
.option('--reject <id>', 'Reject a decision by ID')
|
|
374
|
+
.option('--note <text>', 'Note to attach to approve/reject action')
|
|
375
|
+
.option('--reason <text>', 'Alias for --note')
|
|
376
|
+
.option('--sync', 'Sync all approved decisions to spec.md files', false)
|
|
377
|
+
.option('--dry-run', 'Preview sync without writing', false)
|
|
378
|
+
.option('--list', 'List decisions (default action when no other flag given)', false)
|
|
379
|
+
.option('--status <status>', 'Filter list by status (draft|consolidated|verified|approved|rejected|synced)')
|
|
380
|
+
.option('--uninstall-hook', 'Remove pre-commit hook', false)
|
|
381
|
+
.option('--verbose', 'Show detailed decision info', false)
|
|
382
|
+
.option('--json', 'Output as JSON', false)
|
|
383
|
+
.addHelpText('after', `
|
|
384
|
+
Workflow:
|
|
385
|
+
1. Install once: openlore setup --tools claude (hooks + skills)
|
|
386
|
+
2. During dev: agent calls record_decision MCP tool
|
|
387
|
+
3. At commit: openlore decisions --consolidate (or via hook)
|
|
388
|
+
4. Review: openlore decisions --approve <id>
|
|
389
|
+
5. Write to spec: openlore decisions --sync
|
|
390
|
+
|
|
391
|
+
Examples:
|
|
392
|
+
$ openlore decisions List pending decisions
|
|
393
|
+
$ openlore decisions --consolidate Consolidate + verify drafts
|
|
394
|
+
$ openlore decisions --approve a1b2c3d4 Approve decision a1b2c3d4
|
|
395
|
+
$ openlore decisions --sync Sync approved decisions
|
|
396
|
+
$ openlore decisions --status verified --json Machine-readable output
|
|
397
|
+
`)
|
|
398
|
+
.action(async function (options) {
|
|
399
|
+
const globalOpts = this.parent?.opts() ?? {};
|
|
400
|
+
const rootPath = process.cwd();
|
|
401
|
+
// ── Hook management ──────────────────────────────────────────────────────
|
|
402
|
+
if (options.uninstallHook) {
|
|
403
|
+
await uninstallPreCommitHook(rootPath);
|
|
404
|
+
await uninstallClaudeHook(rootPath); // cleans up any previously installed PostToolUse hook
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
// ── Load store (always needed) ───────────────────────────────────────────
|
|
408
|
+
const store = await loadDecisionStore(rootPath);
|
|
409
|
+
// ── Approve ──────────────────────────────────────────────────────────────
|
|
410
|
+
if (options.approve) {
|
|
411
|
+
const id = options.approve;
|
|
412
|
+
const decision = store.decisions.find((d) => d.id === id);
|
|
413
|
+
if (!decision) {
|
|
414
|
+
logger.error(`Decision ${id} not found.`);
|
|
415
|
+
process.exitCode = 1;
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (decision.status === 'synced') {
|
|
419
|
+
logger.error(`Decision ${id} is already synced to spec files — re-approval not allowed.`);
|
|
420
|
+
process.exitCode = 1;
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const updated = patchDecision(store, id, {
|
|
424
|
+
status: 'approved',
|
|
425
|
+
reviewedAt: new Date().toISOString(),
|
|
426
|
+
reviewNote: options.note ?? options.reason,
|
|
427
|
+
});
|
|
428
|
+
await saveDecisionStore(rootPath, updated);
|
|
429
|
+
logger.success(`Decision ${id} approved.`);
|
|
430
|
+
if (!options.json)
|
|
431
|
+
displayDecision({ ...decision, status: 'approved' }, true);
|
|
432
|
+
// Show a dry-run preview of what would land in the spec
|
|
433
|
+
if (!options.json) {
|
|
434
|
+
const openloreConfig = await readOpenLoreConfig(rootPath);
|
|
435
|
+
if (openloreConfig) {
|
|
436
|
+
const openspecPath = join(rootPath, openloreConfig.openspecPath ?? OPENSPEC_DIR);
|
|
437
|
+
const specsExist = await fileExists(join(openspecPath, OPENSPEC_SPECS_SUBDIR));
|
|
438
|
+
if (specsExist) {
|
|
439
|
+
const specMap = await buildSpecMap({ rootPath, openspecPath }).catch(() => undefined);
|
|
440
|
+
if (specMap) {
|
|
441
|
+
const { result } = await syncApprovedDecisions(updated, {
|
|
442
|
+
rootPath, openspecPath, specMap, dryRun: true,
|
|
443
|
+
});
|
|
444
|
+
if (result.modifiedSpecs.length > 0) {
|
|
445
|
+
console.log(`\nWould write to: ${result.modifiedSpecs.join(', ')}`);
|
|
446
|
+
console.log('Run "openlore decisions --sync" to apply.');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
// ── Reject ───────────────────────────────────────────────────────────────
|
|
455
|
+
if (options.reject) {
|
|
456
|
+
const id = options.reject;
|
|
457
|
+
const decision = store.decisions.find((d) => d.id === id);
|
|
458
|
+
if (!decision) {
|
|
459
|
+
logger.error(`Decision ${id} not found.`);
|
|
460
|
+
process.exitCode = 1;
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const updated = patchDecision(store, id, {
|
|
464
|
+
status: 'rejected',
|
|
465
|
+
reviewedAt: new Date().toISOString(),
|
|
466
|
+
reviewNote: options.note ?? options.reason,
|
|
467
|
+
});
|
|
468
|
+
await saveDecisionStore(rootPath, updated);
|
|
469
|
+
logger.success(`Decision ${id} rejected.`);
|
|
470
|
+
if (!options.json && decision.affectedFiles.length > 0) {
|
|
471
|
+
console.log('\nIf this change should not be committed, revert it manually:');
|
|
472
|
+
for (const f of decision.affectedFiles) {
|
|
473
|
+
console.log(` git restore ${f}`);
|
|
474
|
+
}
|
|
475
|
+
console.log('\nOr to document why this approach was rejected:');
|
|
476
|
+
console.log(' openlore decisions --record');
|
|
477
|
+
console.log(' (then re-run --consolidate before committing)');
|
|
478
|
+
}
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
// ── Consolidate + Verify ─────────────────────────────────────────────────
|
|
482
|
+
if (options.consolidate) {
|
|
483
|
+
const openloreConfig = await readOpenLoreConfig(rootPath);
|
|
484
|
+
if (!openloreConfig) {
|
|
485
|
+
logger.error('No openlore configuration found. Run "openlore init" first.');
|
|
486
|
+
process.exitCode = 1;
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const drafts = getDecisionsByStatus(store, 'draft');
|
|
490
|
+
const hasDrafts = drafts.length > 0;
|
|
491
|
+
const resolved = resolveLLMProvider(openloreConfig);
|
|
492
|
+
if (!resolved) {
|
|
493
|
+
logger.error('No LLM provider configured. Consolidation requires an LLM.');
|
|
494
|
+
logger.discovery('Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, or configure llm in .openlore/config.json');
|
|
495
|
+
process.exitCode = 1;
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const llm = createLLMService({
|
|
499
|
+
provider: resolved.provider,
|
|
500
|
+
model: openloreConfig.generation?.model,
|
|
501
|
+
openaiCompatBaseUrl: resolved.openaiCompatBaseUrl,
|
|
502
|
+
apiBase: globalOpts.apiBase ?? openloreConfig.llm?.apiBase,
|
|
503
|
+
sslVerify: globalOpts.insecure != null ? !globalOpts.insecure : (openloreConfig.llm?.sslVerify ?? true),
|
|
504
|
+
enableLogging: true,
|
|
505
|
+
logDir: join(rootPath, OPENLORE_DIR, OPENLORE_LOGS_SUBDIR),
|
|
506
|
+
});
|
|
507
|
+
// Step 1 — Consolidate drafts OR extract from diff as fallback
|
|
508
|
+
const openspecPath = join(rootPath, openloreConfig.openspecPath ?? OPENSPEC_DIR);
|
|
509
|
+
const specMapResult = await buildSpecMap({ rootPath, openspecPath }).catch(() => undefined);
|
|
510
|
+
let consolidated;
|
|
511
|
+
let supersededIds = [];
|
|
512
|
+
if (hasDrafts) {
|
|
513
|
+
if (!options.json)
|
|
514
|
+
logger.discovery(`Consolidating ${drafts.length} draft decision(s) via ${resolved.provider}...`);
|
|
515
|
+
const result = await consolidateDrafts(store, llm, specMapResult);
|
|
516
|
+
consolidated = result.decisions;
|
|
517
|
+
supersededIds = result.supersededIds;
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
if (!options.json)
|
|
521
|
+
logger.discovery(`No drafts found — extracting decisions from diff via ${resolved.provider}...`);
|
|
522
|
+
const specMap = specMapResult ?? await buildSpecMap({ rootPath, openspecPath });
|
|
523
|
+
// Use staged-only scope so the fallback only sees what's actually being committed.
|
|
524
|
+
consolidated = await extractFromDiff({ rootPath, stagedOnly: true, specMap, sessionId: store.sessionId, llm });
|
|
525
|
+
}
|
|
526
|
+
if (consolidated.length === 0) {
|
|
527
|
+
if (!options.json)
|
|
528
|
+
console.log('No architectural decisions found in drafts.');
|
|
529
|
+
if (options.gate)
|
|
530
|
+
process.exitCode = 0;
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
// Step 2 — Build diff + commit messages for verification
|
|
534
|
+
let combinedDiff = '';
|
|
535
|
+
let commitMessages = '';
|
|
536
|
+
try {
|
|
537
|
+
if (await isGitRepository(rootPath)) {
|
|
538
|
+
const baseRef = await resolveBaseRef(rootPath, 'auto');
|
|
539
|
+
const gitResult = await getChangedFiles({ rootPath, baseRef, includeUnstaged: false });
|
|
540
|
+
const relevant = gitResult.files.slice(0, DECISIONS_EXTRACTION_MAX_FILES);
|
|
541
|
+
const diffs = await Promise.all(relevant.map((f) => getFileDiff(rootPath, f.path, baseRef, DECISIONS_DIFF_MAX_CHARS)));
|
|
542
|
+
combinedDiff = diffs.join('\n\n');
|
|
543
|
+
commitMessages = await getCommitMessages(rootPath, baseRef).catch(() => '');
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
catch (err) {
|
|
547
|
+
logger.warning(`Could not build git diff for verification: ${err.message}`);
|
|
548
|
+
}
|
|
549
|
+
// Step 3 — Verify
|
|
550
|
+
const { verified, phantom, missing } = combinedDiff
|
|
551
|
+
? await verifyDecisions(consolidated, combinedDiff, llm, commitMessages)
|
|
552
|
+
: { verified: consolidated.map((d) => ({ ...d, status: 'verified', confidence: 'medium' })), phantom: [], missing: [] };
|
|
553
|
+
// Step 4 — Persist
|
|
554
|
+
let updatedStore = { ...store };
|
|
555
|
+
// Reject all original drafts — they've been replaced by consolidated decisions.
|
|
556
|
+
// Also reject any explicitly superseded IDs from prior sessions.
|
|
557
|
+
const originalDraftIds = new Set(drafts.map((d) => d.id));
|
|
558
|
+
const originalById = new Map(store.decisions.map((d) => [d.id, d]));
|
|
559
|
+
for (const id of [...originalDraftIds, ...supersededIds]) {
|
|
560
|
+
updatedStore = patchDecision(updatedStore, id, { status: 'rejected' });
|
|
561
|
+
}
|
|
562
|
+
// Preserve recordedAt provenance:
|
|
563
|
+
// - Direct match: consolidated decision ID matches original draft → use its recordedAt.
|
|
564
|
+
// - Merged decision (new ID, no match): use earliest recordedAt across all superseded
|
|
565
|
+
// drafts so the audit trail reflects when the underlying work was first captured.
|
|
566
|
+
const earliestSupersededAt = supersededIds
|
|
567
|
+
.map((id) => originalById.get(id)?.recordedAt)
|
|
568
|
+
.filter((t) => t !== undefined)
|
|
569
|
+
.sort()[0];
|
|
570
|
+
const withProvenance = [...verified, ...phantom].map((d) => {
|
|
571
|
+
const original = originalById.get(d.id);
|
|
572
|
+
if (original)
|
|
573
|
+
return { ...d, recordedAt: original.recordedAt };
|
|
574
|
+
// Merged decision — anchor to earliest superseded draft's recordedAt
|
|
575
|
+
if (earliestSupersededAt)
|
|
576
|
+
return { ...d, recordedAt: earliestSupersededAt };
|
|
577
|
+
return d;
|
|
578
|
+
});
|
|
579
|
+
// replaceDecisions (not upsertDecisions) — consolidated decisions share IDs
|
|
580
|
+
// with their original drafts; upsert would silently no-op after the reject above.
|
|
581
|
+
updatedStore = replaceDecisions(updatedStore, withProvenance);
|
|
582
|
+
updatedStore = { ...updatedStore, lastConsolidatedAt: new Date().toISOString() };
|
|
583
|
+
await saveDecisionStore(rootPath, updatedStore);
|
|
584
|
+
if (options.json) {
|
|
585
|
+
process.stdout.write(JSON.stringify({ verified, phantom, missing }, null, 2) + '\n');
|
|
586
|
+
if (options.gate && missing.length > 0)
|
|
587
|
+
process.exitCode = 1;
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
// Interactive TUI approval when running in a terminal
|
|
591
|
+
if (options.gate && process.stdin.isTTY && process.stdout.isTTY && verified.length > 0) {
|
|
592
|
+
const results = await runTuiApproval(verified);
|
|
593
|
+
let gateStore = updatedStore;
|
|
594
|
+
for (const [id, decision] of results) {
|
|
595
|
+
if (decision === 'approved' || decision === 'rejected') {
|
|
596
|
+
gateStore = patchDecision(gateStore, id, {
|
|
597
|
+
status: decision,
|
|
598
|
+
reviewedAt: new Date().toISOString(),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
await saveDecisionStore(rootPath, gateStore);
|
|
603
|
+
const stillPending = verified.filter((d) => !results.has(d.id) || results.get(d.id) === 'skipped');
|
|
604
|
+
const approved = verified.filter((d) => results.get(d.id) === 'approved');
|
|
605
|
+
const rejected = verified.filter((d) => results.get(d.id) === 'rejected');
|
|
606
|
+
if (approved.length > 0) {
|
|
607
|
+
console.log(`\n${approved.length} decision(s) approved. Run "openlore decisions --sync" to write to spec.md.`);
|
|
608
|
+
}
|
|
609
|
+
if (rejected.length > 0) {
|
|
610
|
+
console.log(`${rejected.length} decision(s) rejected.`);
|
|
611
|
+
}
|
|
612
|
+
if (stillPending.length > 0) {
|
|
613
|
+
logger.warning(`${stillPending.length} decision(s) still pending — commit blocked.`);
|
|
614
|
+
process.exitCode = 1;
|
|
615
|
+
}
|
|
616
|
+
displayMissing(missing);
|
|
617
|
+
if (missing.length > 0)
|
|
618
|
+
process.exitCode = 1;
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
// Non-TTY (agent/IDE context): structured JSON for ACP consumption
|
|
622
|
+
if (options.gate && !process.stdout.isTTY) {
|
|
623
|
+
const payload = {
|
|
624
|
+
gated: verified.length > 0 || missing.length > 0,
|
|
625
|
+
verified: verified.map((d) => ({
|
|
626
|
+
id: d.id,
|
|
627
|
+
title: d.title,
|
|
628
|
+
rationale: d.rationale,
|
|
629
|
+
consequences: d.consequences,
|
|
630
|
+
proposedRequirement: d.proposedRequirement,
|
|
631
|
+
affectedDomains: d.affectedDomains,
|
|
632
|
+
affectedFiles: d.affectedFiles,
|
|
633
|
+
confidence: d.confidence,
|
|
634
|
+
})),
|
|
635
|
+
phantom: phantom.map((d) => ({ id: d.id, title: d.title })),
|
|
636
|
+
missing: missing.map((m) => ({ file: m.file, description: m.description })),
|
|
637
|
+
actions: {
|
|
638
|
+
approve: 'openlore decisions --approve <id>',
|
|
639
|
+
reject: 'openlore decisions --reject <id>',
|
|
640
|
+
sync: 'openlore decisions --sync',
|
|
641
|
+
},
|
|
642
|
+
};
|
|
643
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
644
|
+
if (payload.gated)
|
|
645
|
+
process.exitCode = 1;
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
// Plain text recap (non-gate or explicit --list context)
|
|
649
|
+
logger.section('Architectural Decisions — Review Required');
|
|
650
|
+
if (verified.length > 0) {
|
|
651
|
+
console.log('\nVerified decisions (found in code):');
|
|
652
|
+
for (const d of verified)
|
|
653
|
+
displayDecision(d, options.verbose);
|
|
654
|
+
}
|
|
655
|
+
if (phantom.length > 0) {
|
|
656
|
+
console.log('\nPhantom decisions (recorded but not found in diff — may have been rolled back):');
|
|
657
|
+
for (const d of phantom)
|
|
658
|
+
displayDecision(d, options.verbose);
|
|
659
|
+
}
|
|
660
|
+
displayMissing(missing);
|
|
661
|
+
console.log('\nApprove with: openlore decisions --approve <id>');
|
|
662
|
+
console.log('Reject with: openlore decisions --reject <id>');
|
|
663
|
+
console.log('Sync all approved: openlore decisions --sync');
|
|
664
|
+
if (options.gate && missing.length > 0) {
|
|
665
|
+
logger.warning(`\nCommit gated — ${missing.length} undocumented change(s) require a decision. Record with: openlore decisions --record or record_decision MCP tool.`);
|
|
666
|
+
process.exitCode = 1;
|
|
667
|
+
}
|
|
668
|
+
else if (options.gate && verified.length > 0) {
|
|
669
|
+
logger.warning('\nDecisions verified — approve them before syncing: openlore decisions --approve <id>');
|
|
670
|
+
process.exitCode = 1;
|
|
671
|
+
}
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
// ── Gate only (no consolidation — consolidation happens on record_decision) ──
|
|
675
|
+
if (options.gate && !options.consolidate) {
|
|
676
|
+
// Block if approved decisions haven't been synced to spec files yet.
|
|
677
|
+
const approved = getDecisionsByStatus(store, 'approved');
|
|
678
|
+
if (approved.length > 0) {
|
|
679
|
+
const payload = {
|
|
680
|
+
gated: true,
|
|
681
|
+
reason: GATE_REASONS.APPROVED_NOT_SYNCED,
|
|
682
|
+
message: `${approved.length} approved decision(s) must be synced to spec files before committing.`,
|
|
683
|
+
approved: approved.map((d) => ({ id: d.id, title: d.title })),
|
|
684
|
+
actions: { sync: 'openlore decisions --sync' },
|
|
685
|
+
};
|
|
686
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
687
|
+
process.exitCode = 1;
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
const verified = getDecisionsByStatus(store, 'verified');
|
|
691
|
+
const missing = [];
|
|
692
|
+
if (verified.length === 0) {
|
|
693
|
+
const drafts = getDecisionsByStatus(store, 'draft');
|
|
694
|
+
if (drafts.length > 0) {
|
|
695
|
+
// Drafts recorded but consolidation never completed.
|
|
696
|
+
// Output structured JSON so the agent can relay to the user and act on the answer.
|
|
697
|
+
const payload = {
|
|
698
|
+
gated: true,
|
|
699
|
+
reason: GATE_REASONS.DRAFTS_PENDING_CONSOLIDATION,
|
|
700
|
+
message: `${drafts.length} draft decision(s) were recorded but never consolidated.`,
|
|
701
|
+
drafts: drafts.map((d) => ({ id: d.id, title: d.title, recordedAt: d.recordedAt })),
|
|
702
|
+
actions: {
|
|
703
|
+
consolidate: 'openlore decisions --consolidate',
|
|
704
|
+
consolidateAndGate: 'openlore decisions --consolidate --gate',
|
|
705
|
+
skip: 'git commit --no-verify',
|
|
706
|
+
},
|
|
707
|
+
};
|
|
708
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
709
|
+
process.exitCode = 1;
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
// If consolidation already ran recently, trust it found nothing — skip the warning.
|
|
713
|
+
const consolidatedRecently = store.lastConsolidatedAt
|
|
714
|
+
&& (Date.now() - new Date(store.lastConsolidatedAt).getTime()) < CONSOLIDATION_GRACE_PERIOD_MS;
|
|
715
|
+
if (consolidatedRecently) {
|
|
716
|
+
process.exitCode = 0;
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
// If source files are staged but nothing was recorded, offer to run the fallback extractor.
|
|
720
|
+
// Phantom decisions ("recorded but no code evidence") are excluded — stale phantoms from
|
|
721
|
+
// previous sessions would otherwise silently bypass the gate for all future commits.
|
|
722
|
+
const activeDecisions = store.decisions.filter((d) => !INACTIVE_STATUSES.has(d.status));
|
|
723
|
+
if (activeDecisions.length === 0 && await isGitRepository(rootPath)) {
|
|
724
|
+
try {
|
|
725
|
+
const { stdout } = await execFileAsync('git', ['diff', '--cached', '--name-only', '--diff-filter=ACDMR'], { cwd: rootPath });
|
|
726
|
+
const stagedFiles = stdout.trim().split('\n').filter(Boolean);
|
|
727
|
+
const SOURCE_EXTS = /\.(ts|js|tsx|jsx|py|go|rs|rb|java|cpp|cc|swift)$/;
|
|
728
|
+
const hasSourceChanges = stagedFiles.some((f) => SOURCE_EXTS.test(f));
|
|
729
|
+
if (hasSourceChanges) {
|
|
730
|
+
// Source files staged but nothing recorded — output JSON for agent to relay.
|
|
731
|
+
const payload = {
|
|
732
|
+
gated: true,
|
|
733
|
+
reason: GATE_REASONS.NO_DECISIONS_RECORDED,
|
|
734
|
+
message: 'Source files are staged but no architectural decisions were recorded.',
|
|
735
|
+
actions: {
|
|
736
|
+
consolidateAndGate: 'openlore decisions --consolidate --gate',
|
|
737
|
+
skip: 'git commit --no-verify',
|
|
738
|
+
},
|
|
739
|
+
};
|
|
740
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
741
|
+
process.exitCode = 1;
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
catch { /* git unavailable — skip */ }
|
|
746
|
+
}
|
|
747
|
+
process.exitCode = 0;
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
// TTY: interactive TUI
|
|
751
|
+
if (process.stdin.isTTY && process.stdout.isTTY && verified.length > 0) {
|
|
752
|
+
const results = await runTuiApproval(verified);
|
|
753
|
+
let gateStore = store;
|
|
754
|
+
for (const [id, decision] of results) {
|
|
755
|
+
if (decision === 'approved' || decision === 'rejected') {
|
|
756
|
+
gateStore = patchDecision(gateStore, id, {
|
|
757
|
+
status: decision,
|
|
758
|
+
reviewedAt: new Date().toISOString(),
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
await saveDecisionStore(rootPath, gateStore);
|
|
763
|
+
const stillPending = verified.filter((d) => !results.has(d.id) || results.get(d.id) === 'skipped');
|
|
764
|
+
if (stillPending.length > 0)
|
|
765
|
+
process.exitCode = 1;
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
// Non-TTY: JSON for ACP/agent consumption
|
|
769
|
+
const payload = {
|
|
770
|
+
gated: true,
|
|
771
|
+
reason: GATE_REASONS.VERIFIED,
|
|
772
|
+
verified: verified.map((d) => ({
|
|
773
|
+
id: d.id,
|
|
774
|
+
title: d.title,
|
|
775
|
+
rationale: d.rationale,
|
|
776
|
+
consequences: d.consequences,
|
|
777
|
+
proposedRequirement: d.proposedRequirement,
|
|
778
|
+
affectedDomains: d.affectedDomains,
|
|
779
|
+
affectedFiles: d.affectedFiles,
|
|
780
|
+
confidence: d.confidence,
|
|
781
|
+
})),
|
|
782
|
+
phantom: [],
|
|
783
|
+
missing,
|
|
784
|
+
actions: {
|
|
785
|
+
approve: 'openlore decisions --approve <id>',
|
|
786
|
+
reject: 'openlore decisions --reject <id>',
|
|
787
|
+
sync: 'openlore decisions --sync',
|
|
788
|
+
},
|
|
789
|
+
};
|
|
790
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
791
|
+
process.exitCode = 1;
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
// ── Sync ─────────────────────────────────────────────────────────────────
|
|
795
|
+
if (options.sync) {
|
|
796
|
+
const openloreConfig = await readOpenLoreConfig(rootPath);
|
|
797
|
+
if (!openloreConfig) {
|
|
798
|
+
logger.error('No openlore configuration found.');
|
|
799
|
+
process.exitCode = 1;
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const openspecPath = join(rootPath, openloreConfig.openspecPath ?? OPENSPEC_DIR);
|
|
803
|
+
const specsPath = join(openspecPath, OPENSPEC_SPECS_SUBDIR);
|
|
804
|
+
if (!(await fileExists(specsPath))) {
|
|
805
|
+
logger.error('No specs found. Run "openlore generate" first.');
|
|
806
|
+
process.exitCode = 1;
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const specMap = await buildSpecMap({ rootPath, openspecPath });
|
|
810
|
+
const approved = getDecisionsByStatus(store, 'approved');
|
|
811
|
+
if (approved.length === 0 && !options.json) {
|
|
812
|
+
console.log('No approved decisions to sync. Use --approve <id> first.');
|
|
813
|
+
}
|
|
814
|
+
if (approved.length > 0 && !options.json) {
|
|
815
|
+
logger.discovery(`Syncing ${approved.length} approved decision(s)...`);
|
|
816
|
+
}
|
|
817
|
+
// Always call syncApprovedDecisions so purgeInactiveDecisions runs on the store
|
|
818
|
+
// even when there are no approved decisions to sync.
|
|
819
|
+
const { result } = await syncApprovedDecisions(store, {
|
|
820
|
+
rootPath,
|
|
821
|
+
openspecPath,
|
|
822
|
+
specMap,
|
|
823
|
+
dryRun: options.dryRun,
|
|
824
|
+
});
|
|
825
|
+
if (options.json) {
|
|
826
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
for (const d of result.synced) {
|
|
830
|
+
logger.success(`✔ Synced [${d.id}] ${d.title}`);
|
|
831
|
+
for (const p of d.syncedToSpecs)
|
|
832
|
+
console.log(` → ${p}`);
|
|
833
|
+
}
|
|
834
|
+
for (const e of result.errors) {
|
|
835
|
+
logger.error(`✗ [${e.id}] ${e.error}`);
|
|
836
|
+
}
|
|
837
|
+
if (options.dryRun)
|
|
838
|
+
console.log('\n(dry-run — no files were written)');
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
// ── Default: list ────────────────────────────────────────────────────────
|
|
842
|
+
const VALID_STATUSES = new Set(['draft', 'consolidated', 'verified', 'phantom', 'approved', 'rejected', 'synced']);
|
|
843
|
+
if (options.status && !VALID_STATUSES.has(options.status)) {
|
|
844
|
+
logger.error(`Invalid status "${options.status}". Valid values: ${[...VALID_STATUSES].join('|')}`);
|
|
845
|
+
process.exitCode = 1;
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
const all = options.status
|
|
849
|
+
? store.decisions.filter((d) => d.status === options.status)
|
|
850
|
+
: store.decisions;
|
|
851
|
+
if (options.json) {
|
|
852
|
+
process.stdout.write(JSON.stringify(all, null, 2) + '\n');
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (all.length === 0) {
|
|
856
|
+
console.log('No decisions recorded yet. Agents can call the record_decision MCP tool during development.');
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
logger.section('Architectural Decisions');
|
|
860
|
+
for (const d of all)
|
|
861
|
+
displayDecision(d, options.verbose);
|
|
862
|
+
console.log(`\nTotal: ${all.length}`);
|
|
863
|
+
});
|
|
864
|
+
//# sourceMappingURL=decisions.js.map
|