@tyroneross/navgator 0.2.2 → 0.9.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/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +21 -7
- package/.claude-plugin/plugin.json +16 -11
- package/.codex-plugin/plugin.json +31 -0
- package/.mcp.json +8 -0
- package/CLAUDE.md +197 -23
- package/LICENSE +202 -21
- package/README.md +220 -33
- package/agents/architecture-advisor.md +6 -2
- package/agents/architecture-investigator.md +163 -0
- package/agents/architecture-planner.md +160 -0
- package/agents/external-resolver.md +97 -0
- package/dist/__tests__/agent-output.test.d.ts +5 -0
- package/dist/__tests__/agent-output.test.d.ts.map +1 -0
- package/dist/__tests__/agent-output.test.js +233 -0
- package/dist/__tests__/agent-output.test.js.map +1 -0
- package/dist/__tests__/architecture-insights-stack.test.d.ts +21 -0
- package/dist/__tests__/architecture-insights-stack.test.d.ts.map +1 -0
- package/dist/__tests__/architecture-insights-stack.test.js +86 -0
- package/dist/__tests__/architecture-insights-stack.test.js.map +1 -0
- package/dist/__tests__/architecture-insights.test.d.ts +2 -0
- package/dist/__tests__/architecture-insights.test.d.ts.map +1 -0
- package/dist/__tests__/architecture-insights.test.js +46 -0
- package/dist/__tests__/architecture-insights.test.js.map +1 -0
- package/dist/__tests__/audit-sampler.test.d.ts +10 -0
- package/dist/__tests__/audit-sampler.test.d.ts.map +1 -0
- package/dist/__tests__/audit-sampler.test.js +172 -0
- package/dist/__tests__/audit-sampler.test.js.map +1 -0
- package/dist/__tests__/audit-spc.test.d.ts +5 -0
- package/dist/__tests__/audit-spc.test.d.ts.map +1 -0
- package/dist/__tests__/audit-spc.test.js +94 -0
- package/dist/__tests__/audit-spc.test.js.map +1 -0
- package/dist/__tests__/audit-verifiers.test.d.ts +5 -0
- package/dist/__tests__/audit-verifiers.test.d.ts.map +1 -0
- package/dist/__tests__/audit-verifiers.test.js +248 -0
- package/dist/__tests__/audit-verifiers.test.js.map +1 -0
- package/dist/__tests__/auto-refresh.test.d.ts +12 -0
- package/dist/__tests__/auto-refresh.test.d.ts.map +1 -0
- package/dist/__tests__/auto-refresh.test.js +236 -0
- package/dist/__tests__/auto-refresh.test.js.map +1 -0
- package/dist/__tests__/bare-imports.test.d.ts +8 -0
- package/dist/__tests__/bare-imports.test.d.ts.map +1 -0
- package/dist/__tests__/bare-imports.test.js +176 -0
- package/dist/__tests__/bare-imports.test.js.map +1 -0
- package/dist/__tests__/classify.test.d.ts +5 -0
- package/dist/__tests__/classify.test.d.ts.map +1 -0
- package/dist/__tests__/classify.test.js +158 -0
- package/dist/__tests__/classify.test.js.map +1 -0
- package/dist/__tests__/cli-commands.test.d.ts +8 -0
- package/dist/__tests__/cli-commands.test.d.ts.map +1 -0
- package/dist/__tests__/cli-commands.test.js +207 -0
- package/dist/__tests__/cli-commands.test.js.map +1 -0
- package/dist/__tests__/consolidated-readers.test.d.ts +23 -0
- package/dist/__tests__/consolidated-readers.test.d.ts.map +1 -0
- package/dist/__tests__/consolidated-readers.test.js +200 -0
- package/dist/__tests__/consolidated-readers.test.js.map +1 -0
- package/dist/__tests__/coverage.test.d.ts +5 -0
- package/dist/__tests__/coverage.test.d.ts.map +1 -0
- package/dist/__tests__/coverage.test.js +120 -0
- package/dist/__tests__/coverage.test.js.map +1 -0
- package/dist/__tests__/deploy-scanner-runtime.test.d.ts +6 -0
- package/dist/__tests__/deploy-scanner-runtime.test.d.ts.map +1 -0
- package/dist/__tests__/deploy-scanner-runtime.test.js +168 -0
- package/dist/__tests__/deploy-scanner-runtime.test.js.map +1 -0
- package/dist/__tests__/env-scanner.test.d.ts +2 -0
- package/dist/__tests__/env-scanner.test.d.ts.map +1 -0
- package/dist/__tests__/env-scanner.test.js +191 -0
- package/dist/__tests__/env-scanner.test.js.map +1 -0
- package/dist/__tests__/freshness/cli-freshness.test.d.ts +2 -0
- package/dist/__tests__/freshness/cli-freshness.test.d.ts.map +1 -0
- package/dist/__tests__/freshness/cli-freshness.test.js +26 -0
- package/dist/__tests__/freshness/cli-freshness.test.js.map +1 -0
- package/dist/__tests__/freshness/dirty-ledger.test.d.ts +2 -0
- package/dist/__tests__/freshness/dirty-ledger.test.d.ts.map +1 -0
- package/dist/__tests__/freshness/dirty-ledger.test.js +39 -0
- package/dist/__tests__/freshness/dirty-ledger.test.js.map +1 -0
- package/dist/__tests__/freshness/drainer.test.d.ts +2 -0
- package/dist/__tests__/freshness/drainer.test.d.ts.map +1 -0
- package/dist/__tests__/freshness/drainer.test.js +103 -0
- package/dist/__tests__/freshness/drainer.test.js.map +1 -0
- package/dist/__tests__/freshness/paths.test.d.ts +2 -0
- package/dist/__tests__/freshness/paths.test.d.ts.map +1 -0
- package/dist/__tests__/freshness/paths.test.js +19 -0
- package/dist/__tests__/freshness/paths.test.js.map +1 -0
- package/dist/__tests__/freshness/scan-lock.test.d.ts +2 -0
- package/dist/__tests__/freshness/scan-lock.test.d.ts.map +1 -0
- package/dist/__tests__/freshness/scan-lock.test.js +40 -0
- package/dist/__tests__/freshness/scan-lock.test.js.map +1 -0
- package/dist/__tests__/freshness/stamp.test.d.ts +2 -0
- package/dist/__tests__/freshness/stamp.test.d.ts.map +1 -0
- package/dist/__tests__/freshness/stamp.test.js +36 -0
- package/dist/__tests__/freshness/stamp.test.js.map +1 -0
- package/dist/__tests__/gitignore-safety.test.d.ts +2 -0
- package/dist/__tests__/gitignore-safety.test.d.ts.map +1 -0
- package/dist/__tests__/gitignore-safety.test.js +110 -0
- package/dist/__tests__/gitignore-safety.test.js.map +1 -0
- package/dist/__tests__/helpers.d.ts +37 -0
- package/dist/__tests__/helpers.d.ts.map +1 -0
- package/dist/__tests__/helpers.js +134 -0
- package/dist/__tests__/helpers.js.map +1 -0
- package/dist/__tests__/impact.test.d.ts +5 -0
- package/dist/__tests__/impact.test.d.ts.map +1 -0
- package/dist/__tests__/impact.test.js +221 -0
- package/dist/__tests__/impact.test.js.map +1 -0
- package/dist/__tests__/lessons-store.test.d.ts +8 -0
- package/dist/__tests__/lessons-store.test.d.ts.map +1 -0
- package/dist/__tests__/lessons-store.test.js +232 -0
- package/dist/__tests__/lessons-store.test.js.map +1 -0
- package/dist/__tests__/llm-dedup.test.d.ts +2 -0
- package/dist/__tests__/llm-dedup.test.d.ts.map +1 -0
- package/dist/__tests__/llm-dedup.test.js +155 -0
- package/dist/__tests__/llm-dedup.test.js.map +1 -0
- package/dist/__tests__/mjs-frontend-fetch.test.d.ts +19 -0
- package/dist/__tests__/mjs-frontend-fetch.test.d.ts.map +1 -0
- package/dist/__tests__/mjs-frontend-fetch.test.js +179 -0
- package/dist/__tests__/mjs-frontend-fetch.test.js.map +1 -0
- package/dist/__tests__/multi-stack-discovery.test.d.ts +11 -0
- package/dist/__tests__/multi-stack-discovery.test.d.ts.map +1 -0
- package/dist/__tests__/multi-stack-discovery.test.js +75 -0
- package/dist/__tests__/multi-stack-discovery.test.js.map +1 -0
- package/dist/__tests__/per-entity-files-gate.test.d.ts +22 -0
- package/dist/__tests__/per-entity-files-gate.test.d.ts.map +1 -0
- package/dist/__tests__/per-entity-files-gate.test.js +160 -0
- package/dist/__tests__/per-entity-files-gate.test.js.map +1 -0
- package/dist/__tests__/prisma-calls.test.d.ts +2 -0
- package/dist/__tests__/prisma-calls.test.d.ts.map +1 -0
- package/dist/__tests__/prisma-calls.test.js +125 -0
- package/dist/__tests__/prisma-calls.test.js.map +1 -0
- package/dist/__tests__/prisma-parser.test.d.ts +2 -0
- package/dist/__tests__/prisma-parser.test.d.ts.map +1 -0
- package/dist/__tests__/prisma-parser.test.js +252 -0
- package/dist/__tests__/prisma-parser.test.js.map +1 -0
- package/dist/__tests__/prompt-detector.test.d.ts +5 -0
- package/dist/__tests__/prompt-detector.test.d.ts.map +1 -0
- package/dist/__tests__/prompt-detector.test.js +75 -0
- package/dist/__tests__/prompt-detector.test.js.map +1 -0
- package/dist/__tests__/queue-scanner.test.d.ts +5 -0
- package/dist/__tests__/queue-scanner.test.d.ts.map +1 -0
- package/dist/__tests__/queue-scanner.test.js +85 -0
- package/dist/__tests__/queue-scanner.test.js.map +1 -0
- package/dist/__tests__/resolve.test.d.ts +5 -0
- package/dist/__tests__/resolve.test.d.ts.map +1 -0
- package/dist/__tests__/resolve.test.js +196 -0
- package/dist/__tests__/resolve.test.js.map +1 -0
- package/dist/__tests__/rules.test.d.ts +2 -0
- package/dist/__tests__/rules.test.d.ts.map +1 -0
- package/dist/__tests__/rules.test.js +343 -0
- package/dist/__tests__/rules.test.js.map +1 -0
- package/dist/__tests__/sandbox.test.d.ts +5 -0
- package/dist/__tests__/sandbox.test.d.ts.map +1 -0
- package/dist/__tests__/sandbox.test.js +189 -0
- package/dist/__tests__/sandbox.test.js.map +1 -0
- package/dist/__tests__/scanner-audit.test.d.ts +9 -0
- package/dist/__tests__/scanner-audit.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-audit.test.js +64 -0
- package/dist/__tests__/scanner-audit.test.js.map +1 -0
- package/dist/__tests__/scanner-characterization.test.d.ts +16 -0
- package/dist/__tests__/scanner-characterization.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-characterization.test.js +167 -0
- package/dist/__tests__/scanner-characterization.test.js.map +1 -0
- package/dist/__tests__/scanner-incremental.test.d.ts +13 -0
- package/dist/__tests__/scanner-incremental.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-incremental.test.js +725 -0
- package/dist/__tests__/scanner-incremental.test.js.map +1 -0
- package/dist/__tests__/scanner-integration.test.d.ts +7 -0
- package/dist/__tests__/scanner-integration.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-integration.test.js +211 -0
- package/dist/__tests__/scanner-integration.test.js.map +1 -0
- package/dist/__tests__/scip-new-catches.test.d.ts +19 -0
- package/dist/__tests__/scip-new-catches.test.d.ts.map +1 -0
- package/dist/__tests__/scip-new-catches.test.js +90 -0
- package/dist/__tests__/scip-new-catches.test.js.map +1 -0
- package/dist/__tests__/subgraph.test.d.ts +5 -0
- package/dist/__tests__/subgraph.test.d.ts.map +1 -0
- package/dist/__tests__/subgraph.test.js +145 -0
- package/dist/__tests__/subgraph.test.js.map +1 -0
- package/dist/__tests__/trace.test.d.ts +5 -0
- package/dist/__tests__/trace.test.d.ts.map +1 -0
- package/dist/__tests__/trace.test.js +221 -0
- package/dist/__tests__/trace.test.js.map +1 -0
- package/dist/agent-output.d.ts +16 -0
- package/dist/agent-output.d.ts.map +1 -0
- package/dist/agent-output.js +142 -0
- package/dist/agent-output.js.map +1 -0
- package/dist/architecture-insights.d.ts +17 -0
- package/dist/architecture-insights.d.ts.map +1 -0
- package/dist/architecture-insights.js +178 -0
- package/dist/architecture-insights.js.map +1 -0
- package/dist/audit/index.d.ts +69 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +255 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/sampler.d.ts +98 -0
- package/dist/audit/sampler.d.ts.map +1 -0
- package/dist/audit/sampler.js +298 -0
- package/dist/audit/sampler.js.map +1 -0
- package/dist/audit/spc.d.ts +62 -0
- package/dist/audit/spc.d.ts.map +1 -0
- package/dist/audit/spc.js +81 -0
- package/dist/audit/spc.js.map +1 -0
- package/dist/audit/verifiers.d.ts +81 -0
- package/dist/audit/verifiers.d.ts.map +1 -0
- package/dist/audit/verifiers.js +366 -0
- package/dist/audit/verifiers.js.map +1 -0
- package/dist/classify.d.ts +19 -0
- package/dist/classify.d.ts.map +1 -0
- package/dist/classify.js +124 -0
- package/dist/classify.js.map +1 -0
- package/dist/cli/commands/connections.d.ts +3 -0
- package/dist/cli/commands/connections.d.ts.map +1 -0
- package/dist/cli/commands/connections.js +125 -0
- package/dist/cli/commands/connections.js.map +1 -0
- package/dist/cli/commands/coverage.d.ts +3 -0
- package/dist/cli/commands/coverage.d.ts.map +1 -0
- package/dist/cli/commands/coverage.js +94 -0
- package/dist/cli/commands/coverage.js.map +1 -0
- package/dist/cli/commands/dead.d.ts +3 -0
- package/dist/cli/commands/dead.d.ts.map +1 -0
- package/dist/cli/commands/dead.js +80 -0
- package/dist/cli/commands/dead.js.map +1 -0
- package/dist/cli/commands/diagram.d.ts +3 -0
- package/dist/cli/commands/diagram.d.ts.map +1 -0
- package/dist/cli/commands/diagram.js +102 -0
- package/dist/cli/commands/diagram.js.map +1 -0
- package/dist/cli/commands/find.d.ts +3 -0
- package/dist/cli/commands/find.d.ts.map +1 -0
- package/dist/cli/commands/find.js +128 -0
- package/dist/cli/commands/find.js.map +1 -0
- package/dist/cli/commands/freshness.d.ts +20 -0
- package/dist/cli/commands/freshness.d.ts.map +1 -0
- package/dist/cli/commands/freshness.js +90 -0
- package/dist/cli/commands/freshness.js.map +1 -0
- package/dist/cli/commands/helpers.d.ts +7 -0
- package/dist/cli/commands/helpers.d.ts.map +1 -0
- package/dist/cli/commands/helpers.js +30 -0
- package/dist/cli/commands/helpers.js.map +1 -0
- package/dist/cli/commands/impact.d.ts +3 -0
- package/dist/cli/commands/impact.d.ts.map +1 -0
- package/dist/cli/commands/impact.js +172 -0
- package/dist/cli/commands/impact.js.map +1 -0
- package/dist/cli/commands/lessons.d.ts +6 -0
- package/dist/cli/commands/lessons.d.ts.map +1 -0
- package/dist/cli/commands/lessons.js +279 -0
- package/dist/cli/commands/lessons.js.map +1 -0
- package/dist/cli/commands/list.d.ts +3 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +91 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/llm-map.d.ts +3 -0
- package/dist/cli/commands/llm-map.d.ts.map +1 -0
- package/dist/cli/commands/llm-map.js +121 -0
- package/dist/cli/commands/llm-map.js.map +1 -0
- package/dist/cli/commands/misc.d.ts +17 -0
- package/dist/cli/commands/misc.d.ts.map +1 -0
- package/dist/cli/commands/misc.js +495 -0
- package/dist/cli/commands/misc.js.map +1 -0
- package/dist/cli/commands/prompts.d.ts +3 -0
- package/dist/cli/commands/prompts.d.ts.map +1 -0
- package/dist/cli/commands/prompts.js +74 -0
- package/dist/cli/commands/prompts.js.map +1 -0
- package/dist/cli/commands/rules.d.ts +3 -0
- package/dist/cli/commands/rules.d.ts.map +1 -0
- package/dist/cli/commands/rules.js +61 -0
- package/dist/cli/commands/rules.js.map +1 -0
- package/dist/cli/commands/scan.d.ts +3 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +177 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/commands/schema.d.ts +3 -0
- package/dist/cli/commands/schema.d.ts.map +1 -0
- package/dist/cli/commands/schema.js +126 -0
- package/dist/cli/commands/schema.js.map +1 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +340 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/subgraph.d.ts +3 -0
- package/dist/cli/commands/subgraph.d.ts.map +1 -0
- package/dist/cli/commands/subgraph.js +55 -0
- package/dist/cli/commands/subgraph.js.map +1 -0
- package/dist/cli/commands/temporal.d.ts +3 -0
- package/dist/cli/commands/temporal.d.ts.map +1 -0
- package/dist/cli/commands/temporal.js +112 -0
- package/dist/cli/commands/temporal.js.map +1 -0
- package/dist/cli/commands/trace.d.ts +3 -0
- package/dist/cli/commands/trace.d.ts.map +1 -0
- package/dist/cli/commands/trace.js +65 -0
- package/dist/cli/commands/trace.js.map +1 -0
- package/dist/cli/index.js +88 -825
- package/dist/cli/index.js.map +1 -1
- package/dist/config.d.ts +13 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +106 -12
- package/dist/config.js.map +1 -1
- package/dist/coverage.d.ts +37 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +177 -0
- package/dist/coverage.js.map +1 -0
- package/dist/diagram.d.ts.map +1 -1
- package/dist/diagram.js +41 -0
- package/dist/diagram.js.map +1 -1
- package/dist/diff.d.ts +57 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +527 -0
- package/dist/diff.js.map +1 -0
- package/dist/enrich/cache.d.ts +41 -0
- package/dist/enrich/cache.d.ts.map +1 -0
- package/dist/enrich/cache.js +97 -0
- package/dist/enrich/cache.js.map +1 -0
- package/dist/enrich/external-enrichment.types.d.ts +91 -0
- package/dist/enrich/external-enrichment.types.d.ts.map +1 -0
- package/dist/enrich/external-enrichment.types.js +38 -0
- package/dist/enrich/external-enrichment.types.js.map +1 -0
- package/dist/enrich/external-resolver.d.ts +95 -0
- package/dist/enrich/external-resolver.d.ts.map +1 -0
- package/dist/enrich/external-resolver.js +222 -0
- package/dist/enrich/external-resolver.js.map +1 -0
- package/dist/enrich/fetchers.d.ts +30 -0
- package/dist/enrich/fetchers.d.ts.map +1 -0
- package/dist/enrich/fetchers.js +89 -0
- package/dist/enrich/fetchers.js.map +1 -0
- package/dist/file-resolve.d.ts +35 -0
- package/dist/file-resolve.d.ts.map +1 -0
- package/dist/file-resolve.js +159 -0
- package/dist/file-resolve.js.map +1 -0
- package/dist/freshness/dirty-ledger.d.ts +7 -0
- package/dist/freshness/dirty-ledger.d.ts.map +1 -0
- package/dist/freshness/dirty-ledger.js +56 -0
- package/dist/freshness/dirty-ledger.js.map +1 -0
- package/dist/freshness/drainer.d.ts +38 -0
- package/dist/freshness/drainer.d.ts.map +1 -0
- package/dist/freshness/drainer.js +88 -0
- package/dist/freshness/drainer.js.map +1 -0
- package/dist/freshness/paths.d.ts +9 -0
- package/dist/freshness/paths.d.ts.map +1 -0
- package/dist/freshness/paths.js +24 -0
- package/dist/freshness/paths.js.map +1 -0
- package/dist/freshness/scan-lock.d.ts +8 -0
- package/dist/freshness/scan-lock.d.ts.map +1 -0
- package/dist/freshness/scan-lock.js +68 -0
- package/dist/freshness/scan-lock.js.map +1 -0
- package/dist/freshness/stamp.d.ts +25 -0
- package/dist/freshness/stamp.d.ts.map +1 -0
- package/dist/freshness/stamp.js +50 -0
- package/dist/freshness/stamp.js.map +1 -0
- package/dist/git.d.ts +12 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +42 -0
- package/dist/git.js.map +1 -0
- package/dist/gitignore-safety.d.ts +41 -0
- package/dist/gitignore-safety.d.ts.map +1 -0
- package/dist/gitignore-safety.js +107 -0
- package/dist/gitignore-safety.js.map +1 -0
- package/dist/impact.d.ts +20 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +89 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +24 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -1
- package/dist/index.js.map +1 -1
- package/dist/lessons-store.d.ts +93 -0
- package/dist/lessons-store.d.ts.map +1 -0
- package/dist/lessons-store.js +265 -0
- package/dist/lessons-store.js.map +1 -0
- package/dist/llm-dedup.d.ts +40 -0
- package/dist/llm-dedup.d.ts.map +1 -0
- package/dist/llm-dedup.js +373 -0
- package/dist/llm-dedup.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +87 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +198 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +744 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/metrics/pagerank-louvain.d.ts +44 -0
- package/dist/metrics/pagerank-louvain.d.ts.map +1 -0
- package/dist/metrics/pagerank-louvain.js +128 -0
- package/dist/metrics/pagerank-louvain.js.map +1 -0
- package/dist/parsers/scip-runner.d.ts +63 -0
- package/dist/parsers/scip-runner.d.ts.map +1 -0
- package/dist/parsers/scip-runner.js +179 -0
- package/dist/parsers/scip-runner.js.map +1 -0
- package/dist/projects.d.ts +54 -0
- package/dist/projects.d.ts.map +1 -0
- package/dist/projects.js +153 -0
- package/dist/projects.js.map +1 -0
- package/dist/resolve.d.ts +22 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +128 -0
- package/dist/resolve.js.map +1 -0
- package/dist/rules.d.ts +36 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +484 -0
- package/dist/rules.js.map +1 -0
- package/dist/sandbox.d.ts +33 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +91 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/scan-lock.d.ts +37 -0
- package/dist/scan-lock.d.ts.map +1 -0
- package/dist/scan-lock.js +145 -0
- package/dist/scan-lock.js.map +1 -0
- package/dist/scanner.d.ts +126 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +1711 -235
- package/dist/scanner.js.map +1 -1
- package/dist/scanners/connections/ast-scanner.d.ts +9 -2
- package/dist/scanners/connections/ast-scanner.d.ts.map +1 -1
- package/dist/scanners/connections/ast-scanner.js +19 -4
- package/dist/scanners/connections/ast-scanner.js.map +1 -1
- package/dist/scanners/connections/import-scanner.d.ts +27 -0
- package/dist/scanners/connections/import-scanner.d.ts.map +1 -0
- package/dist/scanners/connections/import-scanner.js +537 -0
- package/dist/scanners/connections/import-scanner.js.map +1 -0
- package/dist/scanners/connections/llm-call-tracer.d.ts +1 -1
- package/dist/scanners/connections/llm-call-tracer.d.ts.map +1 -1
- package/dist/scanners/connections/llm-call-tracer.js +6 -2
- package/dist/scanners/connections/llm-call-tracer.js.map +1 -1
- package/dist/scanners/connections/prisma-calls.d.ts +11 -0
- package/dist/scanners/connections/prisma-calls.d.ts.map +1 -0
- package/dist/scanners/connections/prisma-calls.js +237 -0
- package/dist/scanners/connections/prisma-calls.js.map +1 -0
- package/dist/scanners/connections/service-calls.d.ts +1 -1
- package/dist/scanners/connections/service-calls.d.ts.map +1 -1
- package/dist/scanners/connections/service-calls.js +35 -3
- package/dist/scanners/connections/service-calls.js.map +1 -1
- package/dist/scanners/infrastructure/cron-scanner.d.ts +14 -0
- package/dist/scanners/infrastructure/cron-scanner.d.ts.map +1 -0
- package/dist/scanners/infrastructure/cron-scanner.js +383 -0
- package/dist/scanners/infrastructure/cron-scanner.js.map +1 -0
- package/dist/scanners/infrastructure/deploy-scanner.d.ts +11 -0
- package/dist/scanners/infrastructure/deploy-scanner.d.ts.map +1 -0
- package/dist/scanners/infrastructure/deploy-scanner.js +508 -0
- package/dist/scanners/infrastructure/deploy-scanner.js.map +1 -0
- package/dist/scanners/infrastructure/env-scanner.d.ts +55 -0
- package/dist/scanners/infrastructure/env-scanner.d.ts.map +1 -0
- package/dist/scanners/infrastructure/env-scanner.js +431 -0
- package/dist/scanners/infrastructure/env-scanner.js.map +1 -0
- package/dist/scanners/infrastructure/field-usage-analyzer.d.ts +52 -0
- package/dist/scanners/infrastructure/field-usage-analyzer.d.ts.map +1 -0
- package/dist/scanners/infrastructure/field-usage-analyzer.js +480 -0
- package/dist/scanners/infrastructure/field-usage-analyzer.js.map +1 -0
- package/dist/scanners/infrastructure/prisma-parser.d.ts +21 -0
- package/dist/scanners/infrastructure/prisma-parser.d.ts.map +1 -0
- package/dist/scanners/infrastructure/prisma-parser.js +58 -0
- package/dist/scanners/infrastructure/prisma-parser.js.map +1 -0
- package/dist/scanners/infrastructure/prisma-scanner.d.ts +30 -0
- package/dist/scanners/infrastructure/prisma-scanner.d.ts.map +1 -0
- package/dist/scanners/infrastructure/prisma-scanner.js +329 -0
- package/dist/scanners/infrastructure/prisma-scanner.js.map +1 -0
- package/dist/scanners/infrastructure/queue-scanner.d.ts +14 -0
- package/dist/scanners/infrastructure/queue-scanner.d.ts.map +1 -0
- package/dist/scanners/infrastructure/queue-scanner.js +455 -0
- package/dist/scanners/infrastructure/queue-scanner.js.map +1 -0
- package/dist/scanners/infrastructure/typespec-validator.d.ts +50 -0
- package/dist/scanners/infrastructure/typespec-validator.d.ts.map +1 -0
- package/dist/scanners/infrastructure/typespec-validator.js +407 -0
- package/dist/scanners/infrastructure/typespec-validator.js.map +1 -0
- package/dist/scanners/packages/swift.d.ts +5 -0
- package/dist/scanners/packages/swift.d.ts.map +1 -1
- package/dist/scanners/packages/swift.js +23 -0
- package/dist/scanners/packages/swift.js.map +1 -1
- package/dist/scanners/prompts/detector.d.ts +13 -2
- package/dist/scanners/prompts/detector.d.ts.map +1 -1
- package/dist/scanners/prompts/detector.js +97 -46
- package/dist/scanners/prompts/detector.js.map +1 -1
- package/dist/scanners/prompts/index.d.ts +1 -1
- package/dist/scanners/prompts/index.d.ts.map +1 -1
- package/dist/scanners/prompts/index.js +2 -2
- package/dist/scanners/prompts/index.js.map +1 -1
- package/dist/scanners/swift/code-scanner.d.ts +1 -1
- package/dist/scanners/swift/code-scanner.d.ts.map +1 -1
- package/dist/scanners/swift/code-scanner.js +216 -2
- package/dist/scanners/swift/code-scanner.js.map +1 -1
- package/dist/scanners/swift/swiftui-scanner.d.ts +45 -0
- package/dist/scanners/swift/swiftui-scanner.d.ts.map +1 -0
- package/dist/scanners/swift/swiftui-scanner.js +606 -0
- package/dist/scanners/swift/swiftui-scanner.js.map +1 -0
- package/dist/scanners/xcode/pbxproj-parser.d.ts +32 -0
- package/dist/scanners/xcode/pbxproj-parser.d.ts.map +1 -0
- package/dist/scanners/xcode/pbxproj-parser.js +407 -0
- package/dist/scanners/xcode/pbxproj-parser.js.map +1 -0
- package/dist/scanners/xcode/storyboard-scanner.d.ts +13 -0
- package/dist/scanners/xcode/storyboard-scanner.d.ts.map +1 -0
- package/dist/scanners/xcode/storyboard-scanner.js +236 -0
- package/dist/scanners/xcode/storyboard-scanner.js.map +1 -0
- package/dist/storage/markdown-view.d.ts +49 -0
- package/dist/storage/markdown-view.d.ts.map +1 -0
- package/dist/storage/markdown-view.js +233 -0
- package/dist/storage/markdown-view.js.map +1 -0
- package/dist/storage.d.ts +225 -9
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +945 -86
- package/dist/storage.js.map +1 -1
- package/dist/subgraph.d.ts +30 -0
- package/dist/subgraph.d.ts.map +1 -0
- package/dist/subgraph.js +106 -0
- package/dist/subgraph.js.map +1 -0
- package/dist/temporal/git-store.d.ts +65 -0
- package/dist/temporal/git-store.d.ts.map +1 -0
- package/dist/temporal/git-store.js +166 -0
- package/dist/temporal/git-store.js.map +1 -0
- package/dist/trace.d.ts +38 -0
- package/dist/trace.d.ts.map +1 -0
- package/dist/trace.js +292 -0
- package/dist/trace.js.map +1 -0
- package/dist/types.d.ts +322 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +53 -0
- package/dist/types.js.map +1 -1
- package/hooks/hooks.json +1 -55
- package/hooks/mark-dirty.sh +20 -0
- package/hooks/post-bash-suggest.sh +30 -0
- package/hooks/post-edit-suggest.sh +29 -0
- package/hooks/pre-edit-warn.sh +28 -0
- package/hooks/session-start.sh +19 -0
- package/hooks/stop-suggest.sh +49 -0
- package/package.json +30 -11
- package/scripts/install-codex-plugin.sh +119 -0
- package/scripts/install-plugin.sh +29 -24
- package/skills/architecture-export/SKILL.md +79 -0
- package/skills/architecture-scan/SKILL.md +64 -0
- package/skills/code-review/SKILL.md +368 -0
- package/skills/impact-analysis/SKILL.md +80 -0
- package/skills/infrastructure-scanning.md +42 -0
- package/skills/navgator-setup/SKILL.md +108 -0
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/app-path-routes-manifest.json +3 -0
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/required-server-files.json +4 -4
- package/web/.next/standalone/web/.next/routes-manifest.json +18 -0
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page/next-font-manifest.json +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/api/prompts/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/api/rules/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/api/rules/route/build-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/api/rules/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/api/rules/route.js +6 -0
- package/web/.next/standalone/web/.next/server/app/api/rules/route.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/api/rules/route.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/api/rules/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/api/subgraph/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/api/subgraph/route/build-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/api/subgraph/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/api/subgraph/route.js +6 -0
- package/web/.next/standalone/web/.next/server/app/api/subgraph/route.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/api/subgraph/route.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/api/subgraph/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/api/trace/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/api/trace/route/build-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/api/trace/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/api/trace/route.js +6 -0
- package/web/.next/standalone/web/.next/server/app/api/trace/route.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/api/trace/route.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/api/trace/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +5 -5
- package/web/.next/standalone/web/.next/server/app/page/next-font-manifest.json +2 -2
- package/web/.next/standalone/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/chunks/2374f_next_dist_esm_build_templates_app-route_0bb4e66a.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__006b837d._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__0426efe8._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__2e09fec9._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__38d0390f._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__594bcf20._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__b888fadf._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__cd5f36ce._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__ee6fc95f._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__fa2ec862._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_171de0df._.js +9 -4
- package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_rules_route_actions_3de01bd5.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_subgraph_route_actions_d8b5a63f.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_trace_route_actions_b0703ae2.js +3 -0
- package/web/.next/standalone/web/.next/server/next-font-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/next-font-manifest.json +4 -4
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/22a09ecf6ba35cfd.js +17 -0
- package/web/.next/standalone/web/.next/static/chunks/9857ba86ce4e82d8.css +2 -0
- package/web/.next/standalone/web/.next/static/chunks/f899547f99ef4b76.css +1 -0
- package/web/.next/standalone/web/.next/static/media/4fa387ec64143e14-s.c36e1862.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/53b9e256198e5412-s.853d50a3.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/5ce348bf30bf5439-s.ebceb24d.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/6306c77e7c8268e4-s.ff4a2084.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/7178b3e590c64307-s.55554cd0.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/797e433ab948586e-s.p.479bea2b.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/7d817b4c03b0c5f1-s.f377b9c4.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/8a480f0b521d4e75-s.ea323500.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/bbc41e54d2fcbd21-s.d1207556.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/fef07dbb0973bf53-s.518e079e.woff2 +0 -0
- package/web/.next/standalone/web/app/api/components/route.ts +1 -1
- package/web/.next/standalone/web/app/api/connections/route.ts +3 -1
- package/web/.next/standalone/web/app/api/graph/route.ts +3 -3
- package/web/.next/standalone/web/app/api/projects/route.ts +1 -1
- package/web/.next/standalone/web/app/api/prompts/route.ts +2 -2
- package/web/.next/standalone/web/app/api/rules/route.ts +213 -0
- package/web/.next/standalone/web/app/api/settings/route.ts +2 -2
- package/web/.next/standalone/web/app/api/status/route.ts +1 -1
- package/web/.next/standalone/web/app/api/subgraph/route.ts +267 -0
- package/web/.next/standalone/web/app/api/trace/route.ts +321 -0
- package/web/.next/standalone/web/app/page.tsx +9 -1
- package/web/.next/standalone/web/components/connections-panel.tsx +23 -6
- package/web/.next/standalone/web/components/coverage-panel.tsx +309 -0
- package/web/.next/standalone/web/components/rules-panel.tsx +156 -0
- package/web/.next/standalone/web/components/sidebar.tsx +8 -0
- package/web/.next/standalone/web/components/status-overview.tsx +24 -1
- package/web/.next/standalone/web/components/subgraph-panel.tsx +382 -0
- package/web/.next/standalone/web/components/trace-panel.tsx +325 -0
- package/web/.next/standalone/web/lib/hooks/index.ts +3 -0
- package/web/.next/standalone/web/lib/hooks/use-coverage.ts +68 -0
- package/web/.next/standalone/web/lib/hooks/use-subgraph.ts +85 -0
- package/web/.next/standalone/web/lib/hooks/use-trace.ts +84 -0
- package/web/.next/standalone/web/lib/types.ts +108 -0
- package/web/.next/standalone/web/package-lock.json +218 -0
- package/web/.next/standalone/web/package.json +4 -2
- package/web/.next/standalone/web/server.js +1 -1
- package/web/.next/static/chunks/22a09ecf6ba35cfd.js +17 -0
- package/web/.next/static/chunks/9857ba86ce4e82d8.css +2 -0
- package/web/.next/static/chunks/f899547f99ef4b76.css +1 -0
- package/web/.next/static/media/4fa387ec64143e14-s.c36e1862.woff2 +0 -0
- package/web/.next/static/media/53b9e256198e5412-s.853d50a3.woff2 +0 -0
- package/web/.next/static/media/5ce348bf30bf5439-s.ebceb24d.woff2 +0 -0
- package/web/.next/static/media/6306c77e7c8268e4-s.ff4a2084.woff2 +0 -0
- package/web/.next/static/media/7178b3e590c64307-s.55554cd0.woff2 +0 -0
- package/web/.next/static/media/797e433ab948586e-s.p.479bea2b.woff2 +0 -0
- package/web/.next/static/media/7d817b4c03b0c5f1-s.f377b9c4.woff2 +0 -0
- package/web/.next/static/media/8a480f0b521d4e75-s.ea323500.woff2 +0 -0
- package/web/.next/static/media/bbc41e54d2fcbd21-s.d1207556.woff2 +0 -0
- package/web/.next/static/media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2 +0 -0
- package/web/.next/static/media/fef07dbb0973bf53-s.518e079e.woff2 +0 -0
- package/skills/check/SKILL.md +0 -64
- package/skills/connections/SKILL.md +0 -54
- package/skills/diagram/SKILL.md +0 -64
- package/skills/export/SKILL.md +0 -49
- package/skills/impact/SKILL.md +0 -58
- package/skills/install/SKILL.md +0 -94
- package/skills/scan/SKILL.md +0 -75
- package/skills/status/SKILL.md +0 -37
- package/skills/ui/SKILL.md +0 -43
- package/skills/update/SKILL.md +0 -43
- package/web/.next/standalone/web/.next/static/chunks/8a80e7184ad3a13f.css +0 -2
- package/web/.next/standalone/web/.next/static/chunks/c056475f5f4424b6.css +0 -1
- package/web/.next/standalone/web/.next/static/chunks/cb3513192b63e480.js +0 -12
- package/web/.next/standalone/web/.next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- package/web/.next/static/chunks/8a80e7184ad3a13f.css +0 -2
- package/web/.next/static/chunks/c056475f5f4424b6.css +0 -1
- package/web/.next/static/chunks/cb3513192b63e480.js +0 -12
- package/web/.next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- package/web/.next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- package/web/.next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- package/web/.next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- package/web/.next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- package/web/.next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- /package/web/.next/standalone/web/.next/static/{P-ZMQO7_Wnj487ks3guqa → qZVrJ4kmwXfw4Ikgj1oXR}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{P-ZMQO7_Wnj487ks3guqa → qZVrJ4kmwXfw4Ikgj1oXR}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{P-ZMQO7_Wnj487ks3guqa → qZVrJ4kmwXfw4Ikgj1oXR}/_ssgManifest.js +0 -0
- /package/web/.next/static/{P-ZMQO7_Wnj487ks3guqa → qZVrJ4kmwXfw4Ikgj1oXR}/_buildManifest.js +0 -0
- /package/web/.next/static/{P-ZMQO7_Wnj487ks3guqa → qZVrJ4kmwXfw4Ikgj1oXR}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{P-ZMQO7_Wnj487ks3guqa → qZVrJ4kmwXfw4Ikgj1oXR}/_ssgManifest.js +0 -0
package/dist/storage.js
CHANGED
|
@@ -5,7 +5,68 @@
|
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import * as crypto from 'crypto';
|
|
8
|
-
import {
|
|
8
|
+
import { generateStableId, } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Pick the most stable path-like identifier from a component.
|
|
11
|
+
* Prefer the first config_file (e.g. "prisma/schema.prisma" for Prisma models),
|
|
12
|
+
* else fall back to a documentation/repository URL component.
|
|
13
|
+
* Returns undefined if nothing path-like is available.
|
|
14
|
+
*
|
|
15
|
+
* RENAME BEHAVIOR (Run 1.6 — item #6):
|
|
16
|
+
* For path-disambiguated types (api-endpoint, db-table, prompt, worker,
|
|
17
|
+
* component, cron) the resulting stable_id includes the canonical path. This
|
|
18
|
+
* prevents collisions when two components share a name in different files
|
|
19
|
+
* (e.g. `src/utils/index.ts` vs `src/lib/index.ts` — both named `index` for
|
|
20
|
+
* the `component` type). The tradeoff: when a file is RENAMED or MOVED its
|
|
21
|
+
* stable_id changes, so the merge step treats the renamed file as a brand-new
|
|
22
|
+
* component and the old stable_id falls out of the surviving set.
|
|
23
|
+
*
|
|
24
|
+
* Correctness is preserved by the integrity check (`runIntegrityCheck`):
|
|
25
|
+
* after merge, missing connection endpoints or orphan components trigger
|
|
26
|
+
* `scan_type='incremental→full'` (full rebuild). Renames thus stay correct
|
|
27
|
+
* but pay the full-scan cost. Not optimal, but keeping path in stable_id is
|
|
28
|
+
* the simpler and safer default.
|
|
29
|
+
*/
|
|
30
|
+
function pickCanonicalPath(c) {
|
|
31
|
+
if (c.source?.config_files?.length) {
|
|
32
|
+
return c.source.config_files[0];
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Backfill stable_id on a component if missing.
|
|
38
|
+
* Idempotent — returns the same component reference (mutated in place).
|
|
39
|
+
* Path-disambiguation is opt-in per-type: types where (type,name) is
|
|
40
|
+
* naturally unique (npm/pip packages, frameworks, services, llm providers,
|
|
41
|
+
* databases, infra, queues, configs) use name-only. Types that can repeat
|
|
42
|
+
* the same name across different files (api-endpoint, db-table, prompt,
|
|
43
|
+
* worker, component, cron) include canonical_path.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Public re-export of ensureStableId. Callers (e.g. the scanner during
|
|
47
|
+
* incremental merge) need to populate stable_ids on freshly-scanned
|
|
48
|
+
* in-memory components BEFORE merging with disk-loaded survivors.
|
|
49
|
+
*/
|
|
50
|
+
export function ensureStableIdPublic(c) {
|
|
51
|
+
return ensureStableId(c);
|
|
52
|
+
}
|
|
53
|
+
function ensureStableId(c) {
|
|
54
|
+
if (c.stable_id)
|
|
55
|
+
return c;
|
|
56
|
+
const PATH_DISAMBIGUATED = new Set([
|
|
57
|
+
'api-endpoint',
|
|
58
|
+
'db-table',
|
|
59
|
+
'prompt',
|
|
60
|
+
'worker',
|
|
61
|
+
'component',
|
|
62
|
+
'cron',
|
|
63
|
+
]);
|
|
64
|
+
const canonical = PATH_DISAMBIGUATED.has(c.type) ? pickCanonicalPath(c) : undefined;
|
|
65
|
+
c.stable_id = generateStableId(c.type, c.name, canonical);
|
|
66
|
+
return c;
|
|
67
|
+
}
|
|
68
|
+
import { getConfig, getComponentsPath, getConnectionsPath, getIndexPath, getGraphPath, getSnapshotsPath, getHashesPath, getStoragePath, getSummaryPath, getSummaryFullPath, getFileMapPath, getPromptsPath, ensureStorageDirectories, isValidComponentId, isValidConnectionId, SCHEMA_VERSION, } from './config.js';
|
|
69
|
+
import { detectImportCycles, detectLayerViolations, getTopFanOut, getTopHotspots, } from './architecture-insights.js';
|
|
9
70
|
// =============================================================================
|
|
10
71
|
// COMPONENT STORAGE
|
|
11
72
|
// =============================================================================
|
|
@@ -16,8 +77,9 @@ export async function storeComponent(component, config, projectRoot) {
|
|
|
16
77
|
const cfg = config || getConfig();
|
|
17
78
|
ensureStorageDirectories(cfg, projectRoot);
|
|
18
79
|
const componentsPath = getComponentsPath(cfg, projectRoot);
|
|
80
|
+
ensureStableId(component);
|
|
19
81
|
const filePath = path.join(componentsPath, `${component.component_id}.json`);
|
|
20
|
-
await
|
|
82
|
+
await atomicWriteJSON(filePath, component);
|
|
21
83
|
return {
|
|
22
84
|
component_id: component.component_id,
|
|
23
85
|
file_path: filePath,
|
|
@@ -38,35 +100,67 @@ export async function loadComponent(componentId, config, projectRoot) {
|
|
|
38
100
|
}
|
|
39
101
|
try {
|
|
40
102
|
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
41
|
-
return JSON.parse(content);
|
|
103
|
+
return ensureStableId(JSON.parse(content));
|
|
42
104
|
}
|
|
43
105
|
catch {
|
|
44
106
|
return null;
|
|
45
107
|
}
|
|
46
108
|
}
|
|
47
109
|
/**
|
|
48
|
-
* Load all components
|
|
110
|
+
* Load all components.
|
|
111
|
+
*
|
|
112
|
+
* R6: per-entity files are opt-in (default off). When they're absent or
|
|
113
|
+
* empty, falls back to the consolidated `components.full.jsonl` written
|
|
114
|
+
* by the scanner. This keeps every existing reader (MCP tools, CLI
|
|
115
|
+
* commands, audit, summary) working unchanged.
|
|
116
|
+
*
|
|
117
|
+
* Read priority: components/ dir → components.full.jsonl → [].
|
|
49
118
|
*/
|
|
50
119
|
export async function loadAllComponents(config, projectRoot) {
|
|
51
120
|
const cfg = config || getConfig();
|
|
52
121
|
const componentsPath = getComponentsPath(cfg, projectRoot);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
122
|
+
// Primary path: per-entity dir (legacy + opt-in mode).
|
|
123
|
+
if (fs.existsSync(componentsPath)) {
|
|
124
|
+
const files = await fs.promises.readdir(componentsPath);
|
|
125
|
+
const jsonFiles = files.filter((f) => f.endsWith('.json'));
|
|
126
|
+
if (jsonFiles.length > 0) {
|
|
127
|
+
const results = await Promise.all(jsonFiles.map(async (file) => {
|
|
128
|
+
try {
|
|
129
|
+
const filePath = path.join(componentsPath, file);
|
|
130
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
131
|
+
return ensureStableId(JSON.parse(content));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}));
|
|
137
|
+
return results.filter((c) => c !== null);
|
|
64
138
|
}
|
|
65
|
-
|
|
66
|
-
|
|
139
|
+
}
|
|
140
|
+
// R6 fallback: consolidated full-shape JSONL.
|
|
141
|
+
const storeDir = getStoragePath(cfg, projectRoot);
|
|
142
|
+
const fullJsonlPath = path.join(storeDir, 'components.full.jsonl');
|
|
143
|
+
if (!fs.existsSync(fullJsonlPath))
|
|
144
|
+
return [];
|
|
145
|
+
try {
|
|
146
|
+
const raw = await fs.promises.readFile(fullJsonlPath, 'utf-8');
|
|
147
|
+
const out = [];
|
|
148
|
+
for (const line of raw.split('\n')) {
|
|
149
|
+
const trimmed = line.trim();
|
|
150
|
+
if (!trimmed)
|
|
151
|
+
continue;
|
|
152
|
+
try {
|
|
153
|
+
out.push(ensureStableId(JSON.parse(trimmed)));
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Skip malformed lines — best-effort read.
|
|
157
|
+
}
|
|
67
158
|
}
|
|
68
|
-
|
|
69
|
-
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
70
164
|
}
|
|
71
165
|
/**
|
|
72
166
|
* Delete a component by ID
|
|
@@ -95,7 +189,7 @@ export async function storeConnection(connection, config, projectRoot) {
|
|
|
95
189
|
ensureStorageDirectories(cfg, projectRoot);
|
|
96
190
|
const connectionsPath = getConnectionsPath(cfg, projectRoot);
|
|
97
191
|
const filePath = path.join(connectionsPath, `${connection.connection_id}.json`);
|
|
98
|
-
await
|
|
192
|
+
await atomicWriteJSON(filePath, connection);
|
|
99
193
|
return {
|
|
100
194
|
connection_id: connection.connection_id,
|
|
101
195
|
file_path: filePath,
|
|
@@ -123,28 +217,57 @@ export async function loadConnection(connectionId, config, projectRoot) {
|
|
|
123
217
|
}
|
|
124
218
|
}
|
|
125
219
|
/**
|
|
126
|
-
* Load all connections
|
|
220
|
+
* Load all connections.
|
|
221
|
+
*
|
|
222
|
+
* R6: per-entity files are opt-in (default off). When they're absent or
|
|
223
|
+
* empty, falls back to the consolidated `connections.full.jsonl` written
|
|
224
|
+
* by the scanner. Same read-priority as loadAllComponents.
|
|
127
225
|
*/
|
|
128
226
|
export async function loadAllConnections(config, projectRoot) {
|
|
129
227
|
const cfg = config || getConfig();
|
|
130
228
|
const connectionsPath = getConnectionsPath(cfg, projectRoot);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
229
|
+
// Primary path: per-entity dir (legacy + opt-in mode).
|
|
230
|
+
if (fs.existsSync(connectionsPath)) {
|
|
231
|
+
const files = await fs.promises.readdir(connectionsPath);
|
|
232
|
+
const jsonFiles = files.filter((f) => f.endsWith('.json'));
|
|
233
|
+
if (jsonFiles.length > 0) {
|
|
234
|
+
const results = await Promise.all(jsonFiles.map(async (file) => {
|
|
235
|
+
try {
|
|
236
|
+
const filePath = path.join(connectionsPath, file);
|
|
237
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
238
|
+
return JSON.parse(content);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}));
|
|
244
|
+
return results.filter((c) => c !== null);
|
|
142
245
|
}
|
|
143
|
-
|
|
144
|
-
|
|
246
|
+
}
|
|
247
|
+
// R6 fallback: consolidated full-shape JSONL.
|
|
248
|
+
const storeDir = getStoragePath(cfg, projectRoot);
|
|
249
|
+
const fullJsonlPath = path.join(storeDir, 'connections.full.jsonl');
|
|
250
|
+
if (!fs.existsSync(fullJsonlPath))
|
|
251
|
+
return [];
|
|
252
|
+
try {
|
|
253
|
+
const raw = await fs.promises.readFile(fullJsonlPath, 'utf-8');
|
|
254
|
+
const out = [];
|
|
255
|
+
for (const line of raw.split('\n')) {
|
|
256
|
+
const trimmed = line.trim();
|
|
257
|
+
if (!trimmed)
|
|
258
|
+
continue;
|
|
259
|
+
try {
|
|
260
|
+
out.push(JSON.parse(trimmed));
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// Skip malformed lines.
|
|
264
|
+
}
|
|
145
265
|
}
|
|
146
|
-
|
|
147
|
-
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
148
271
|
}
|
|
149
272
|
/**
|
|
150
273
|
* Delete a connection by ID
|
|
@@ -168,11 +291,15 @@ export async function deleteConnection(connectionId, config, projectRoot) {
|
|
|
168
291
|
/**
|
|
169
292
|
* Build and save the index from current components and connections
|
|
170
293
|
*/
|
|
171
|
-
export async function buildIndex(config, projectRoot, projectMetadata) {
|
|
294
|
+
export async function buildIndex(config, projectRoot, projectMetadata, data) {
|
|
172
295
|
const cfg = config || getConfig();
|
|
173
|
-
|
|
174
|
-
|
|
296
|
+
// R6: when the caller hands us the in-memory final state (the scanner does
|
|
297
|
+
// this), use it directly — required since per-entity files are now opt-in
|
|
298
|
+
// (default off) and loadAllComponents/Connections would otherwise return [].
|
|
299
|
+
const components = data?.components ?? (await loadAllComponents(cfg, projectRoot));
|
|
300
|
+
const connections = data?.connections ?? (await loadAllConnections(cfg, projectRoot));
|
|
175
301
|
const index = {
|
|
302
|
+
schema_version: SCHEMA_VERSION,
|
|
176
303
|
version: '1.0.0',
|
|
177
304
|
last_scan: Date.now(),
|
|
178
305
|
project_path: projectRoot || process.cwd(),
|
|
@@ -248,9 +375,9 @@ export async function buildIndex(config, projectRoot, projectMetadata) {
|
|
|
248
375
|
index.stats.connections_by_type[connection.connection_type] =
|
|
249
376
|
(index.stats.connections_by_type[connection.connection_type] || 0) + 1;
|
|
250
377
|
}
|
|
251
|
-
// Save index
|
|
378
|
+
// Save index (atomic: write to .tmp, then rename)
|
|
252
379
|
const indexPath = getIndexPath(cfg, projectRoot);
|
|
253
|
-
await
|
|
380
|
+
await atomicWriteJSON(indexPath, index);
|
|
254
381
|
return index;
|
|
255
382
|
}
|
|
256
383
|
/**
|
|
@@ -264,7 +391,24 @@ export async function loadIndex(config, projectRoot) {
|
|
|
264
391
|
}
|
|
265
392
|
try {
|
|
266
393
|
const content = await fs.promises.readFile(indexPath, 'utf-8');
|
|
267
|
-
|
|
394
|
+
const parsed = JSON.parse(content);
|
|
395
|
+
// Read-time defaults for back-compat with 1.0.0 archives.
|
|
396
|
+
// 1.0.0 archives have no schema_version, last_full_scan, or
|
|
397
|
+
// incrementals_since_full. Synthesize sensible defaults so callers
|
|
398
|
+
// (especially selectScanMode) can treat 1.0.0 + 1.1.0 uniformly.
|
|
399
|
+
if (!parsed.schema_version) {
|
|
400
|
+
parsed.schema_version = '1.0.0';
|
|
401
|
+
}
|
|
402
|
+
if (parsed.last_full_scan === undefined) {
|
|
403
|
+
// Treat the existing last_scan as if it had been a full scan.
|
|
404
|
+
// This is conservative: it ensures the 7-day staleness rule
|
|
405
|
+
// doesn't immediately demand a full scan on the first 1.1.0 run.
|
|
406
|
+
parsed.last_full_scan = parsed.last_scan ?? 0;
|
|
407
|
+
}
|
|
408
|
+
if (parsed.incrementals_since_full === undefined) {
|
|
409
|
+
parsed.incrementals_since_full = 0;
|
|
410
|
+
}
|
|
411
|
+
return parsed;
|
|
268
412
|
}
|
|
269
413
|
catch {
|
|
270
414
|
return null;
|
|
@@ -276,12 +420,14 @@ export async function loadIndex(config, projectRoot) {
|
|
|
276
420
|
/**
|
|
277
421
|
* Build the connection graph
|
|
278
422
|
*/
|
|
279
|
-
export async function buildGraph(config, projectRoot) {
|
|
423
|
+
export async function buildGraph(config, projectRoot, data) {
|
|
280
424
|
const cfg = config || getConfig();
|
|
281
|
-
|
|
282
|
-
const
|
|
425
|
+
// R6: prefer in-memory data when provided (scanner path). See buildIndex.
|
|
426
|
+
const components = data?.components ?? (await loadAllComponents(cfg, projectRoot));
|
|
427
|
+
const connections = data?.connections ?? (await loadAllConnections(cfg, projectRoot));
|
|
283
428
|
const nodes = components.map((c) => ({
|
|
284
429
|
id: c.component_id,
|
|
430
|
+
stable_id: c.stable_id ?? ensureStableId(c).stable_id,
|
|
285
431
|
name: c.name,
|
|
286
432
|
type: c.type,
|
|
287
433
|
layer: c.role.layer,
|
|
@@ -294,6 +440,7 @@ export async function buildGraph(config, projectRoot) {
|
|
|
294
440
|
label: c.description,
|
|
295
441
|
}));
|
|
296
442
|
const graph = {
|
|
443
|
+
schema_version: SCHEMA_VERSION,
|
|
297
444
|
nodes,
|
|
298
445
|
edges,
|
|
299
446
|
metadata: {
|
|
@@ -302,9 +449,9 @@ export async function buildGraph(config, projectRoot) {
|
|
|
302
449
|
connection_count: edges.length,
|
|
303
450
|
},
|
|
304
451
|
};
|
|
305
|
-
// Save graph
|
|
452
|
+
// Save graph (atomic)
|
|
306
453
|
const graphPath = getGraphPath(cfg, projectRoot);
|
|
307
|
-
await
|
|
454
|
+
await atomicWriteJSON(graphPath, graph);
|
|
308
455
|
return graph;
|
|
309
456
|
}
|
|
310
457
|
// =============================================================================
|
|
@@ -314,11 +461,12 @@ export async function buildGraph(config, projectRoot) {
|
|
|
314
461
|
* Build a map of file paths → component IDs for fast lookup in hooks.
|
|
315
462
|
* Sources: component config_files + connection code_reference files + connection locations.
|
|
316
463
|
*/
|
|
317
|
-
export async function buildFileMap(config, projectRoot) {
|
|
464
|
+
export async function buildFileMap(config, projectRoot, data) {
|
|
318
465
|
const cfg = config || getConfig();
|
|
319
466
|
const root = projectRoot || process.cwd();
|
|
320
|
-
|
|
321
|
-
const
|
|
467
|
+
// R6: prefer in-memory data when provided (scanner path). See buildIndex.
|
|
468
|
+
const components = data?.components ?? (await loadAllComponents(cfg, root));
|
|
469
|
+
const connections = data?.connections ?? (await loadAllConnections(cfg, root));
|
|
322
470
|
const fileMap = {};
|
|
323
471
|
// Index config files from components
|
|
324
472
|
for (const c of components) {
|
|
@@ -339,10 +487,34 @@ export async function buildFileMap(config, projectRoot) {
|
|
|
339
487
|
fileMap[conn.to.location.file] = conn.to.component_id;
|
|
340
488
|
}
|
|
341
489
|
}
|
|
490
|
+
const wrapped = {
|
|
491
|
+
schema_version: SCHEMA_VERSION,
|
|
492
|
+
generated_at: Date.now(),
|
|
493
|
+
files: fileMap,
|
|
494
|
+
};
|
|
342
495
|
const fileMapPath = getFileMapPath(cfg, root);
|
|
343
|
-
await
|
|
496
|
+
await atomicWriteJSON(fileMapPath, wrapped);
|
|
344
497
|
return fileMap;
|
|
345
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* Load the file map (file path → component ID)
|
|
501
|
+
*/
|
|
502
|
+
export async function loadFileMap(config, projectRoot) {
|
|
503
|
+
const cfg = config || getConfig();
|
|
504
|
+
const root = projectRoot || process.cwd();
|
|
505
|
+
const fileMapPath = getFileMapPath(cfg, root);
|
|
506
|
+
if (!fs.existsSync(fileMapPath)) {
|
|
507
|
+
return {};
|
|
508
|
+
}
|
|
509
|
+
try {
|
|
510
|
+
const content = await fs.promises.readFile(fileMapPath, 'utf-8');
|
|
511
|
+
const parsed = JSON.parse(content);
|
|
512
|
+
return parsed.files || {};
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
return {};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
346
518
|
// =============================================================================
|
|
347
519
|
// PROMPT STORAGE (Tier 2 - Full prompt content for on-demand loading)
|
|
348
520
|
// =============================================================================
|
|
@@ -354,7 +526,11 @@ export async function savePromptScan(promptData, config, projectRoot) {
|
|
|
354
526
|
const root = projectRoot || process.cwd();
|
|
355
527
|
ensureStorageDirectories(cfg, root);
|
|
356
528
|
const promptsPath = getPromptsPath(cfg, root);
|
|
357
|
-
|
|
529
|
+
// Spread schema_version into prompt output
|
|
530
|
+
const output = typeof promptData === 'object' && promptData !== null
|
|
531
|
+
? { schema_version: SCHEMA_VERSION, ...promptData }
|
|
532
|
+
: promptData;
|
|
533
|
+
await atomicWriteJSON(promptsPath, output);
|
|
358
534
|
}
|
|
359
535
|
// =============================================================================
|
|
360
536
|
// SUMMARY GENERATION (Tier 1 - Hot Context for LLMs)
|
|
@@ -369,11 +545,12 @@ const AI_PROVIDER_NAMES = new Set([
|
|
|
369
545
|
* Build a concise markdown summary with pointers to detail files.
|
|
370
546
|
* This is the "hot context" an LLM reads first on cold start.
|
|
371
547
|
*/
|
|
372
|
-
export async function buildSummary(config, projectRoot, promptScan, projectMetadata) {
|
|
548
|
+
export async function buildSummary(config, projectRoot, promptScan, projectMetadata, latestDiff, gitInfo, data) {
|
|
373
549
|
const cfg = config || getConfig();
|
|
374
550
|
const root = projectRoot || process.cwd();
|
|
375
|
-
|
|
376
|
-
const
|
|
551
|
+
// R6: prefer in-memory data when provided (scanner path). See buildIndex.
|
|
552
|
+
const components = data?.components ?? (await loadAllComponents(cfg, root));
|
|
553
|
+
const connections = data?.connections ?? (await loadAllConnections(cfg, root));
|
|
377
554
|
const now = new Date().toISOString();
|
|
378
555
|
const aiComponents = components.filter((c) => AI_PROVIDER_NAMES.has(c.name) || c.type === 'llm' || c.type === 'service');
|
|
379
556
|
// Group components by layer
|
|
@@ -384,11 +561,38 @@ export async function buildSummary(config, projectRoot, promptScan, projectMetad
|
|
|
384
561
|
byLayer.set(layer, []);
|
|
385
562
|
byLayer.get(layer).push(c);
|
|
386
563
|
}
|
|
564
|
+
// Sort each layer by architectural importance (production-critical first, noise last)
|
|
565
|
+
const criticalTypes = new Set(['database', 'queue', 'llm', 'framework', 'infra', 'service', 'cron']);
|
|
566
|
+
const isNoiseComponent = (c) => {
|
|
567
|
+
const file = c.source.config_files[0]?.toLowerCase() || c.name.toLowerCase();
|
|
568
|
+
return /(_archive|__tests__|\.test\.|\.spec\.|\/tests\/|\/scripts\/|\/examples?\/|\/dist\/|\/mock|\.example\.)/.test(file) ||
|
|
569
|
+
c.name.startsWith('_archive') || c.name.endsWith('.test') || c.name.endsWith('.spec');
|
|
570
|
+
};
|
|
571
|
+
for (const [, group] of byLayer) {
|
|
572
|
+
group.sort((a, b) => {
|
|
573
|
+
const aIsCritical = criticalTypes.has(a.type) ? 0 : 1;
|
|
574
|
+
const bIsCritical = criticalTypes.has(b.type) ? 0 : 1;
|
|
575
|
+
if (aIsCritical !== bIsCritical)
|
|
576
|
+
return aIsCritical - bIsCritical;
|
|
577
|
+
const aIsNoise = isNoiseComponent(a) ? 1 : 0;
|
|
578
|
+
const bIsNoise = isNoiseComponent(b) ? 1 : 0;
|
|
579
|
+
if (aIsNoise !== bIsNoise)
|
|
580
|
+
return aIsNoise - bIsNoise;
|
|
581
|
+
const aConns = a.connects_to.length + a.connected_from.length;
|
|
582
|
+
const bConns = b.connects_to.length + b.connected_from.length;
|
|
583
|
+
if (bConns !== aConns)
|
|
584
|
+
return bConns - aConns;
|
|
585
|
+
return a.name.localeCompare(b.name);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
387
588
|
// Build markdown
|
|
388
589
|
const lines = [];
|
|
389
590
|
lines.push('# Architecture Summary');
|
|
390
591
|
lines.push(`> NavGator auto-generated | Scanned: ${now}`);
|
|
391
592
|
lines.push(`> ${components.length} components | ${connections.length} connections | ${aiComponents.length} AI providers`);
|
|
593
|
+
if (gitInfo) {
|
|
594
|
+
lines.push(`> Branch: **${gitInfo.branch}** @ \`${gitInfo.commit}\``);
|
|
595
|
+
}
|
|
392
596
|
lines.push('');
|
|
393
597
|
// Project metadata (agent orientation)
|
|
394
598
|
if (projectMetadata && projectMetadata.type) {
|
|
@@ -440,6 +644,63 @@ export async function buildSummary(config, projectRoot, promptScan, projectMetad
|
|
|
440
644
|
}
|
|
441
645
|
lines.push('');
|
|
442
646
|
}
|
|
647
|
+
// Top by PageRank + Mermaid cluster diagram (T6)
|
|
648
|
+
// Reads metrics.json produced by computeAndStoreMetrics during scan.
|
|
649
|
+
try {
|
|
650
|
+
const metricsPath = path.join(getStoragePath(cfg, root), 'metrics.json');
|
|
651
|
+
if (fs.existsSync(metricsPath)) {
|
|
652
|
+
const raw = await fs.promises.readFile(metricsPath, 'utf-8');
|
|
653
|
+
const report = JSON.parse(raw);
|
|
654
|
+
if (!report.suppressed && report.metrics.length > 0) {
|
|
655
|
+
lines.push('## Top by PageRank');
|
|
656
|
+
lines.push(`> ${report.community_count} communities · modularity ${report.modularity?.toFixed(3) ?? 'n/a'}`);
|
|
657
|
+
lines.push('');
|
|
658
|
+
lines.push('| # | Component | PageRank | Community |');
|
|
659
|
+
lines.push('|---|-----------|---------:|----------:|');
|
|
660
|
+
const top = report.metrics.slice(0, 10);
|
|
661
|
+
top.forEach((m, i) => {
|
|
662
|
+
lines.push(`| ${i + 1} | \`${m.name}\` | ${m.pagerank_score.toFixed(4)} | ${m.community_id} |`);
|
|
663
|
+
});
|
|
664
|
+
lines.push('');
|
|
665
|
+
// Inline Mermaid cluster diagram — top 5 communities by PageRank-weighted size.
|
|
666
|
+
const byCommunity = new Map();
|
|
667
|
+
for (const m of report.metrics) {
|
|
668
|
+
if (!byCommunity.has(m.community_id))
|
|
669
|
+
byCommunity.set(m.community_id, []);
|
|
670
|
+
byCommunity.get(m.community_id).push(m);
|
|
671
|
+
}
|
|
672
|
+
const ranked = [...byCommunity.entries()]
|
|
673
|
+
.map(([id, members]) => ({
|
|
674
|
+
id,
|
|
675
|
+
members,
|
|
676
|
+
score: members.reduce((sum, x) => sum + x.pagerank_score, 0),
|
|
677
|
+
}))
|
|
678
|
+
.sort((a, b) => b.score - a.score)
|
|
679
|
+
.slice(0, 5);
|
|
680
|
+
if (ranked.length > 0) {
|
|
681
|
+
const sanitize = (s) => s.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 40);
|
|
682
|
+
lines.push('## Cluster Diagram');
|
|
683
|
+
lines.push('```mermaid');
|
|
684
|
+
lines.push('flowchart LR');
|
|
685
|
+
for (const cluster of ranked) {
|
|
686
|
+
const top3 = cluster.members
|
|
687
|
+
.sort((a, b) => b.pagerank_score - a.pagerank_score)
|
|
688
|
+
.slice(0, 3);
|
|
689
|
+
lines.push(` subgraph C${cluster.id}["Community ${cluster.id} (${cluster.members.length} nodes)"]`);
|
|
690
|
+
for (const m of top3) {
|
|
691
|
+
lines.push(` ${sanitize(m.component_id)}["${m.name}"]`);
|
|
692
|
+
}
|
|
693
|
+
lines.push(' end');
|
|
694
|
+
}
|
|
695
|
+
lines.push('```');
|
|
696
|
+
lines.push('');
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
catch {
|
|
702
|
+
// metrics.json missing or malformed — silent skip; legacy scans may not have it.
|
|
703
|
+
}
|
|
443
704
|
// AI/LLM routing table
|
|
444
705
|
if (aiComponents.length > 0 || connections.some((c) => c.connection_type === 'service-call')) {
|
|
445
706
|
lines.push('## AI/LLM Routing');
|
|
@@ -480,31 +741,88 @@ export async function buildSummary(config, projectRoot, promptScan, projectMetad
|
|
|
480
741
|
}
|
|
481
742
|
lines.push('');
|
|
482
743
|
}
|
|
483
|
-
|
|
744
|
+
const hotspots = getTopHotspots(components, connections, 5);
|
|
745
|
+
if (hotspots.length > 0) {
|
|
746
|
+
lines.push('## Hotspots');
|
|
747
|
+
lines.push('> Highest fan-in internal modules. Changes here ripple broadly.');
|
|
748
|
+
lines.push('');
|
|
749
|
+
for (const hotspot of hotspots) {
|
|
750
|
+
const file = hotspot.component.source.config_files?.[0];
|
|
751
|
+
lines.push(`- **${hotspot.component.name}** — ${hotspot.count} dependents${file ? ` (${file})` : ''}`);
|
|
752
|
+
}
|
|
753
|
+
lines.push('');
|
|
754
|
+
}
|
|
755
|
+
const fanOut = getTopFanOut(components, connections, 5).filter((entry) => entry.count >= 5);
|
|
756
|
+
if (fanOut.length > 0) {
|
|
757
|
+
lines.push('## Fan-Out Risks');
|
|
758
|
+
lines.push('> High fan-out modules often accumulate too many responsibilities.');
|
|
759
|
+
lines.push('');
|
|
760
|
+
for (const entry of fanOut) {
|
|
761
|
+
const file = entry.component.source.config_files?.[0];
|
|
762
|
+
lines.push(`- **${entry.component.name}** — imports ${entry.count} modules${file ? ` (${file})` : ''}`);
|
|
763
|
+
}
|
|
764
|
+
lines.push('');
|
|
765
|
+
}
|
|
766
|
+
const layerViolations = detectLayerViolations(components, connections);
|
|
767
|
+
lines.push('## Layer Health');
|
|
768
|
+
if (layerViolations.length === 0) {
|
|
769
|
+
lines.push('- No upward import violations detected from inferred internal layers.');
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
for (const violation of layerViolations.slice(0, 5)) {
|
|
773
|
+
const file = violation.connection.code_reference?.file || violation.from.source.config_files?.[0] || '';
|
|
774
|
+
const line = violation.connection.code_reference?.line_start ? `:${violation.connection.code_reference.line_start}` : '';
|
|
775
|
+
lines.push(`- ${violation.from.name} → ${violation.to.name} (${file}${line}) crosses from tier ${violation.fromTier} to ${violation.toTier}`);
|
|
776
|
+
}
|
|
777
|
+
if (layerViolations.length > 5) {
|
|
778
|
+
lines.push(`- ... and ${layerViolations.length - 5} more`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
lines.push('');
|
|
782
|
+
const cycles = detectImportCycles(components, connections, 5);
|
|
783
|
+
lines.push('## Circular Dependencies');
|
|
784
|
+
if (cycles.length === 0) {
|
|
785
|
+
lines.push('- No import cycles detected.');
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
788
|
+
for (const cycle of cycles) {
|
|
789
|
+
lines.push(`- ${cycle.join(' → ')}`);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
lines.push('');
|
|
793
|
+
// Delta — use structured diff from timeline if available, else fall back to naive comparison
|
|
484
794
|
const summaryPath = getSummaryPath(cfg, root);
|
|
485
|
-
if (
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
lines.push(`- Added: \`${c.name}\` (${c.role.layer})`);
|
|
795
|
+
if (latestDiff && latestDiff.diff.stats.total_changes > 0) {
|
|
796
|
+
const { formatDiffForSummary } = await import('./diff.js');
|
|
797
|
+
const diffLines = formatDiffForSummary(latestDiff);
|
|
798
|
+
lines.push(...diffLines);
|
|
799
|
+
}
|
|
800
|
+
else if (!latestDiff) {
|
|
801
|
+
// Fallback: naive text-based delta for backwards compatibility (no timeline entry provided)
|
|
802
|
+
if (fs.existsSync(summaryPath)) {
|
|
803
|
+
try {
|
|
804
|
+
const prev = await fs.promises.readFile(summaryPath, 'utf-8');
|
|
805
|
+
const prevNames = new Set();
|
|
806
|
+
for (const match of prev.matchAll(/^- \*\*(.+?)\*\*/gm)) {
|
|
807
|
+
prevNames.add(match[1]);
|
|
499
808
|
}
|
|
500
|
-
|
|
501
|
-
|
|
809
|
+
const currentNames = new Set(components.map((c) => c.name));
|
|
810
|
+
const added = components.filter((c) => !prevNames.has(c.name));
|
|
811
|
+
const removed = [...prevNames].filter((n) => !currentNames.has(n));
|
|
812
|
+
if (added.length > 0 || removed.length > 0) {
|
|
813
|
+
lines.push('## Changes Since Last Scan');
|
|
814
|
+
for (const c of added) {
|
|
815
|
+
lines.push(`- Added: \`${c.name}\` (${c.role.layer})`);
|
|
816
|
+
}
|
|
817
|
+
for (const name of removed) {
|
|
818
|
+
lines.push(`- Removed: \`${name}\``);
|
|
819
|
+
}
|
|
820
|
+
lines.push('');
|
|
502
821
|
}
|
|
503
|
-
lines.push('');
|
|
504
822
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
823
|
+
catch {
|
|
824
|
+
// First scan or parse error — skip delta
|
|
825
|
+
}
|
|
508
826
|
}
|
|
509
827
|
}
|
|
510
828
|
// Prompts section (pointers only — full content in prompts.json)
|
|
@@ -530,6 +848,7 @@ export async function buildSummary(config, projectRoot, promptScan, projectMetad
|
|
|
530
848
|
lines.push(`- Full index: \`index.json\``);
|
|
531
849
|
lines.push(`- Connection graph: \`graph.json\``);
|
|
532
850
|
lines.push(`- File map: \`file_map.json\``);
|
|
851
|
+
lines.push(`- Architecture timeline: \`timeline.json\``);
|
|
533
852
|
if (promptScan && promptScan.prompts.length > 0) {
|
|
534
853
|
lines.push(`- Prompts: \`prompts.json\` (${promptScan.prompts.length} prompts, full content)`);
|
|
535
854
|
}
|
|
@@ -540,14 +859,14 @@ export async function buildSummary(config, projectRoot, promptScan, projectMetad
|
|
|
540
859
|
const lineCount = lines.length;
|
|
541
860
|
const COMPRESSION_THRESHOLD = 150;
|
|
542
861
|
if (lineCount > COMPRESSION_THRESHOLD) {
|
|
543
|
-
// Write full version to
|
|
862
|
+
// Write full version to NAVSUMMARY_FULL.md
|
|
544
863
|
const fullPath = getSummaryFullPath(cfg, root);
|
|
545
864
|
await fs.promises.writeFile(fullPath, fullContent, 'utf-8');
|
|
546
865
|
// Build compressed version: top 10 per layer, AI routing, top 10 connections
|
|
547
866
|
const compressed = [];
|
|
548
867
|
compressed.push('# Architecture Summary (Compressed)');
|
|
549
868
|
compressed.push('');
|
|
550
|
-
compressed.push('> **This is a compressed summary.** Full version: `
|
|
869
|
+
compressed.push('> **This is a compressed summary.** Full version: `NAVSUMMARY_FULL.md`');
|
|
551
870
|
compressed.push('');
|
|
552
871
|
compressed.push(`> NavGator auto-generated | Scanned: ${now}`);
|
|
553
872
|
compressed.push(`> ${components.length} components | ${connections.length} connections | ${aiComponents.length} AI providers`);
|
|
@@ -568,11 +887,33 @@ export async function buildSummary(config, projectRoot, promptScan, projectMetad
|
|
|
568
887
|
compressed.push(`- **${c.name}**${ver} — ${c.role.purpose} \`components/${c.component_id}.json\``);
|
|
569
888
|
}
|
|
570
889
|
if (group.length > 10) {
|
|
571
|
-
compressed.push(`- ... and ${group.length - 10} more (see
|
|
890
|
+
compressed.push(`- ... and ${group.length - 10} more (see NAVSUMMARY_FULL.md)`);
|
|
572
891
|
}
|
|
573
892
|
compressed.push('');
|
|
574
893
|
}
|
|
575
894
|
}
|
|
895
|
+
// Top by PageRank (compressed: top 5 + 1 Mermaid block)
|
|
896
|
+
try {
|
|
897
|
+
const metricsPath = path.join(getStoragePath(cfg, root), 'metrics.json');
|
|
898
|
+
if (fs.existsSync(metricsPath)) {
|
|
899
|
+
const raw = await fs.promises.readFile(metricsPath, 'utf-8');
|
|
900
|
+
const report = JSON.parse(raw);
|
|
901
|
+
if (!report.suppressed && report.metrics.length > 0) {
|
|
902
|
+
compressed.push('## Top by PageRank');
|
|
903
|
+
compressed.push(`> ${report.community_count} communities · modularity ${report.modularity?.toFixed(3) ?? 'n/a'}`);
|
|
904
|
+
compressed.push('');
|
|
905
|
+
compressed.push('| # | Component | PageRank | Community |');
|
|
906
|
+
compressed.push('|---|-----------|---------:|----------:|');
|
|
907
|
+
report.metrics.slice(0, 5).forEach((m, i) => {
|
|
908
|
+
compressed.push(`| ${i + 1} | \`${m.name}\` | ${m.pagerank_score.toFixed(4)} | ${m.community_id} |`);
|
|
909
|
+
});
|
|
910
|
+
compressed.push('');
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
catch {
|
|
915
|
+
// metrics.json missing or malformed — silent skip.
|
|
916
|
+
}
|
|
576
917
|
// AI/LLM routing table (preserved in compressed version)
|
|
577
918
|
if (aiComponents.length > 0 || connections.some((c) => c.connection_type === 'service-call')) {
|
|
578
919
|
compressed.push('## AI/LLM Routing');
|
|
@@ -592,7 +933,7 @@ export async function buildSummary(config, projectRoot, promptScan, projectMetad
|
|
|
592
933
|
compressed.push(`| ${target?.name || '?'} | ${file} | ${line} | ${purpose} | \`connections/${conn.connection_id}.json\` |`);
|
|
593
934
|
}
|
|
594
935
|
if (aiConnections.length > 10) {
|
|
595
|
-
compressed.push(`| ... | | | ${aiConnections.length - 10} more (see
|
|
936
|
+
compressed.push(`| ... | | | ${aiConnections.length - 10} more (see NAVSUMMARY_FULL.md) | |`);
|
|
596
937
|
}
|
|
597
938
|
// AI components with no connections
|
|
598
939
|
for (const c of aiComponents) {
|
|
@@ -604,6 +945,33 @@ export async function buildSummary(config, projectRoot, promptScan, projectMetad
|
|
|
604
945
|
}
|
|
605
946
|
compressed.push('');
|
|
606
947
|
}
|
|
948
|
+
// Runtime Topology section
|
|
949
|
+
const withRuntime = components.filter(c => c.runtime?.resource_type);
|
|
950
|
+
if (withRuntime.length > 0) {
|
|
951
|
+
compressed.push('## Runtime Topology');
|
|
952
|
+
const rtSeen = new Set();
|
|
953
|
+
for (const c of withRuntime) {
|
|
954
|
+
const r = c.runtime;
|
|
955
|
+
if (r.resource_type === 'api')
|
|
956
|
+
continue; // skip noisy env var URLs
|
|
957
|
+
const engine = r.engine || c.name;
|
|
958
|
+
const host = r.endpoint?.host ? ` @ ${r.endpoint.host}${r.endpoint.port ? ':' + r.endpoint.port : ''}` : '';
|
|
959
|
+
const env = r.connection_env_var ? ` (via ${r.connection_env_var})` : '';
|
|
960
|
+
const rtLine = `- **${r.resource_type}**: ${engine}${host}${env}`;
|
|
961
|
+
if (!rtSeen.has(rtLine)) {
|
|
962
|
+
rtSeen.add(rtLine);
|
|
963
|
+
compressed.push(rtLine);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
// Queue names as a group
|
|
967
|
+
const queueComps = withRuntime.filter(c => c.runtime?.resource_type === 'queue');
|
|
968
|
+
if (queueComps.length > 0) {
|
|
969
|
+
const queueNames = queueComps.map(c => c.runtime?.service_name || c.name).join(', ');
|
|
970
|
+
const engine = queueComps[0].runtime?.engine || 'queue';
|
|
971
|
+
compressed.push(`- **queues**: ${queueNames} (${engine})`);
|
|
972
|
+
}
|
|
973
|
+
compressed.push('');
|
|
974
|
+
}
|
|
607
975
|
// Connections (top 10)
|
|
608
976
|
if (connections.length > 0) {
|
|
609
977
|
const maxConns = Math.min(connections.length, 10);
|
|
@@ -618,6 +986,40 @@ export async function buildSummary(config, projectRoot, promptScan, projectMetad
|
|
|
618
986
|
}
|
|
619
987
|
compressed.push('');
|
|
620
988
|
}
|
|
989
|
+
if (hotspots.length > 0) {
|
|
990
|
+
compressed.push('## Hotspots');
|
|
991
|
+
for (const hotspot of hotspots) {
|
|
992
|
+
compressed.push(`- **${hotspot.component.name}** — ${hotspot.count} dependents`);
|
|
993
|
+
}
|
|
994
|
+
compressed.push('');
|
|
995
|
+
}
|
|
996
|
+
if (fanOut.length > 0) {
|
|
997
|
+
compressed.push('## Fan-Out Risks');
|
|
998
|
+
for (const entry of fanOut) {
|
|
999
|
+
compressed.push(`- **${entry.component.name}** — imports ${entry.count} modules`);
|
|
1000
|
+
}
|
|
1001
|
+
compressed.push('');
|
|
1002
|
+
}
|
|
1003
|
+
compressed.push('## Layer Health');
|
|
1004
|
+
if (layerViolations.length === 0) {
|
|
1005
|
+
compressed.push('- No upward import violations detected.');
|
|
1006
|
+
}
|
|
1007
|
+
else {
|
|
1008
|
+
for (const violation of layerViolations.slice(0, 5)) {
|
|
1009
|
+
compressed.push(`- ${violation.from.name} → ${violation.to.name} crosses from tier ${violation.fromTier} to ${violation.toTier}`);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
compressed.push('');
|
|
1013
|
+
compressed.push('## Circular Dependencies');
|
|
1014
|
+
if (cycles.length === 0) {
|
|
1015
|
+
compressed.push('- No import cycles detected.');
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
for (const cycle of cycles) {
|
|
1019
|
+
compressed.push(`- ${cycle.join(' → ')}`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
compressed.push('');
|
|
621
1023
|
// Add prompts pointer if available
|
|
622
1024
|
if (promptScan && promptScan.prompts.length > 0) {
|
|
623
1025
|
compressed.push(`## Prompts (${promptScan.prompts.length}) — full content: \`prompts.json\``);
|
|
@@ -637,7 +1039,7 @@ export async function buildSummary(config, projectRoot, promptScan, projectMetad
|
|
|
637
1039
|
compressed.push('');
|
|
638
1040
|
}
|
|
639
1041
|
compressed.push('## Detail Pointers');
|
|
640
|
-
compressed.push('- **Full summary**: `
|
|
1042
|
+
compressed.push('- **Full summary**: `NAVSUMMARY_FULL.md`');
|
|
641
1043
|
compressed.push(`- Full index: \`index.json\``);
|
|
642
1044
|
compressed.push(`- Connection graph: \`graph.json\``);
|
|
643
1045
|
compressed.push(`- File map: \`file_map.json\``);
|
|
@@ -681,10 +1083,16 @@ export async function createSnapshot(reason, config, projectRoot) {
|
|
|
681
1083
|
ensureStorageDirectories(cfg, projectRoot);
|
|
682
1084
|
const components = await loadAllComponents(cfg, projectRoot);
|
|
683
1085
|
const connections = await loadAllConnections(cfg, projectRoot);
|
|
1086
|
+
// Build component_id → name lookup for connection name resolution
|
|
1087
|
+
const componentIdToName = new Map();
|
|
1088
|
+
for (const c of components) {
|
|
1089
|
+
componentIdToName.set(c.component_id, c.name);
|
|
1090
|
+
}
|
|
684
1091
|
const timestamp = Date.now();
|
|
685
1092
|
const snapshotId = `SNAP_${new Date(timestamp).toISOString().replace(/[-:T.Z]/g, '').slice(0, 14)}`;
|
|
686
1093
|
const snapshot = {
|
|
687
1094
|
snapshot_id: snapshotId,
|
|
1095
|
+
snapshot_version: '2.0',
|
|
688
1096
|
timestamp,
|
|
689
1097
|
reason,
|
|
690
1098
|
components: components.map((c) => ({
|
|
@@ -693,12 +1101,17 @@ export async function createSnapshot(reason, config, projectRoot) {
|
|
|
693
1101
|
type: c.type,
|
|
694
1102
|
version: c.version,
|
|
695
1103
|
status: c.status,
|
|
1104
|
+
layer: c.role.layer,
|
|
1105
|
+
critical: c.role.critical,
|
|
696
1106
|
})),
|
|
697
1107
|
connections: connections.map((c) => ({
|
|
698
1108
|
connection_id: c.connection_id,
|
|
699
1109
|
from: c.from.component_id,
|
|
700
1110
|
to: c.to.component_id,
|
|
701
1111
|
type: c.connection_type,
|
|
1112
|
+
from_name: componentIdToName.get(c.from.component_id) || '?',
|
|
1113
|
+
to_name: componentIdToName.get(c.to.component_id) || '?',
|
|
1114
|
+
file: c.code_reference?.file,
|
|
702
1115
|
})),
|
|
703
1116
|
stats: {
|
|
704
1117
|
total_components: components.length,
|
|
@@ -717,10 +1130,24 @@ export async function createSnapshot(reason, config, projectRoot) {
|
|
|
717
1130
|
// BULK OPERATIONS
|
|
718
1131
|
// =============================================================================
|
|
719
1132
|
/**
|
|
720
|
-
* Store multiple components at once (parallelized for efficiency)
|
|
1133
|
+
* Store multiple components at once (parallelized for efficiency).
|
|
1134
|
+
*
|
|
1135
|
+
* R6 footprint fix: writes are gated on `config.perEntityFiles` (default
|
|
1136
|
+
* false). When disabled, the consolidated `graph.json`, `index.json`,
|
|
1137
|
+
* `file_map.json`, and `connections.jsonl` are the source of truth and we
|
|
1138
|
+
* skip the per-entity file explosion (~2,475 files × ~3KB each on
|
|
1139
|
+
* atomize-ai). Component IDs are still stamped onto the in-memory objects
|
|
1140
|
+
* so callers (graph builder, index writer) get stable IDs.
|
|
721
1141
|
*/
|
|
722
1142
|
export async function storeComponents(components, config, projectRoot) {
|
|
723
1143
|
const cfg = config || getConfig();
|
|
1144
|
+
// Stamp stable IDs regardless of write mode — downstream consumers
|
|
1145
|
+
// (graph.json, index.json, file_map.json) depend on these.
|
|
1146
|
+
for (const component of components) {
|
|
1147
|
+
ensureStableId(component);
|
|
1148
|
+
}
|
|
1149
|
+
if (!cfg.perEntityFiles)
|
|
1150
|
+
return;
|
|
724
1151
|
ensureStorageDirectories(cfg, projectRoot);
|
|
725
1152
|
const componentsPath = getComponentsPath(cfg, projectRoot);
|
|
726
1153
|
// Parallelize writes in batches to avoid overwhelming the filesystem
|
|
@@ -729,15 +1156,22 @@ export async function storeComponents(components, config, projectRoot) {
|
|
|
729
1156
|
const batch = components.slice(i, i + batchSize);
|
|
730
1157
|
await Promise.all(batch.map(async (component) => {
|
|
731
1158
|
const filePath = path.join(componentsPath, `${component.component_id}.json`);
|
|
732
|
-
await
|
|
1159
|
+
await atomicWriteJSON(filePath, component);
|
|
733
1160
|
}));
|
|
734
1161
|
}
|
|
735
1162
|
}
|
|
736
1163
|
/**
|
|
737
|
-
* Store multiple connections at once (parallelized for efficiency)
|
|
1164
|
+
* Store multiple connections at once (parallelized for efficiency).
|
|
1165
|
+
*
|
|
1166
|
+
* R6 footprint fix: writes are gated on `config.perEntityFiles` (default
|
|
1167
|
+
* false). When disabled, `connections.jsonl` + `reverse-deps.json` are the
|
|
1168
|
+
* source of truth and we skip the per-edge file explosion (~6,737 files
|
|
1169
|
+
* on atomize-ai).
|
|
738
1170
|
*/
|
|
739
1171
|
export async function storeConnections(connections, config, projectRoot) {
|
|
740
1172
|
const cfg = config || getConfig();
|
|
1173
|
+
if (!cfg.perEntityFiles)
|
|
1174
|
+
return;
|
|
741
1175
|
ensureStorageDirectories(cfg, projectRoot);
|
|
742
1176
|
const connectionsPath = getConnectionsPath(cfg, projectRoot);
|
|
743
1177
|
// Parallelize writes in batches to avoid overwhelming the filesystem
|
|
@@ -746,10 +1180,59 @@ export async function storeConnections(connections, config, projectRoot) {
|
|
|
746
1180
|
const batch = connections.slice(i, i + batchSize);
|
|
747
1181
|
await Promise.all(batch.map(async (connection) => {
|
|
748
1182
|
const filePath = path.join(connectionsPath, `${connection.connection_id}.json`);
|
|
749
|
-
await
|
|
1183
|
+
await atomicWriteJSON(filePath, connection);
|
|
750
1184
|
}));
|
|
751
1185
|
}
|
|
752
1186
|
}
|
|
1187
|
+
/**
|
|
1188
|
+
* R6 footprint fix: idempotent migration that removes legacy per-entity
|
|
1189
|
+
* JSON files when `perEntityFiles` is disabled (the default).
|
|
1190
|
+
*
|
|
1191
|
+
* Safety: this NEVER deletes the consolidated files (graph.json, index.json,
|
|
1192
|
+
* file_map.json, connections.jsonl, reverse-deps.json, NAVSUMMARY*.md,
|
|
1193
|
+
* hashes.json, timeline.json, etc.). It only touches the contents of the
|
|
1194
|
+
* `components/` and `connections/` subdirectories and removes those
|
|
1195
|
+
* directories themselves once empty. If `perEntityFiles` is true, this is
|
|
1196
|
+
* a no-op.
|
|
1197
|
+
*
|
|
1198
|
+
* Returns a count summary for caller logging.
|
|
1199
|
+
*/
|
|
1200
|
+
export async function migratePerEntityFiles(config, projectRoot) {
|
|
1201
|
+
const cfg = config || getConfig();
|
|
1202
|
+
const summary = { componentsRemoved: 0, connectionsRemoved: 0, dirsRemoved: 0 };
|
|
1203
|
+
// Per-entity mode is on — nothing to migrate; let the writer manage these
|
|
1204
|
+
// dirs as before.
|
|
1205
|
+
if (cfg.perEntityFiles)
|
|
1206
|
+
return summary;
|
|
1207
|
+
for (const dir of [getComponentsPath(cfg, projectRoot), getConnectionsPath(cfg, projectRoot)]) {
|
|
1208
|
+
if (!fs.existsSync(dir))
|
|
1209
|
+
continue;
|
|
1210
|
+
try {
|
|
1211
|
+
const entries = await fs.promises.readdir(dir);
|
|
1212
|
+
const jsonFiles = entries.filter((f) => f.endsWith('.json'));
|
|
1213
|
+
const isComponentsDir = dir.endsWith(`${path.sep}components`) || dir.endsWith('/components');
|
|
1214
|
+
// Delete only `.json` files (legacy per-entity payload). Any other
|
|
1215
|
+
// files (e.g. README, future schema markers) are left untouched.
|
|
1216
|
+
await Promise.all(jsonFiles.map((file) => fs.promises.unlink(path.join(dir, file)).catch(() => { })));
|
|
1217
|
+
if (isComponentsDir)
|
|
1218
|
+
summary.componentsRemoved += jsonFiles.length;
|
|
1219
|
+
else
|
|
1220
|
+
summary.connectionsRemoved += jsonFiles.length;
|
|
1221
|
+
// If the dir is now empty, remove it. Best-effort — leave it if other
|
|
1222
|
+
// files exist (the *.json filter above means non-json siblings survive).
|
|
1223
|
+
const remaining = await fs.promises.readdir(dir);
|
|
1224
|
+
if (remaining.length === 0) {
|
|
1225
|
+
await fs.promises.rmdir(dir).catch(() => { });
|
|
1226
|
+
summary.dirsRemoved += 1;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
catch {
|
|
1230
|
+
// Best-effort migration: failures (permissions, races) are swallowed
|
|
1231
|
+
// so they never block a scan. Next scan retries automatically.
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return summary;
|
|
1235
|
+
}
|
|
753
1236
|
/**
|
|
754
1237
|
* Clear all stored data (parallelized for efficiency)
|
|
755
1238
|
*/
|
|
@@ -777,6 +1260,14 @@ export async function clearStorage(config, projectRoot) {
|
|
|
777
1260
|
if (fs.existsSync(graphPath)) {
|
|
778
1261
|
deletePromises.push(fs.promises.unlink(graphPath).catch(() => { }));
|
|
779
1262
|
}
|
|
1263
|
+
// R6: delete consolidated full-shape JSONL files. These are always written
|
|
1264
|
+
// by the scanner (even when per-entity files are off) and are the primary
|
|
1265
|
+
// source for loadAllComponents / loadAllConnections. Leaving them behind
|
|
1266
|
+
// after clearStorage causes loadAll* to return stale data if a subsequent
|
|
1267
|
+
// scan crashes before rewriting them.
|
|
1268
|
+
const storeDir = getStoragePath(cfg, projectRoot);
|
|
1269
|
+
deletePromises.push(fs.promises.unlink(path.join(storeDir, 'components.full.jsonl')).catch(() => { }));
|
|
1270
|
+
deletePromises.push(fs.promises.unlink(path.join(storeDir, 'connections.full.jsonl')).catch(() => { }));
|
|
780
1271
|
await Promise.all(deletePromises);
|
|
781
1272
|
}
|
|
782
1273
|
// =============================================================================
|
|
@@ -882,7 +1373,7 @@ export async function saveHashes(hashes, config, projectRoot) {
|
|
|
882
1373
|
files: hashes,
|
|
883
1374
|
};
|
|
884
1375
|
const hashesPath = getHashesPath(cfg, root);
|
|
885
|
-
await
|
|
1376
|
+
await atomicWriteJSON(hashesPath, navHashes);
|
|
886
1377
|
}
|
|
887
1378
|
/**
|
|
888
1379
|
* Load file hashes from disk
|
|
@@ -948,6 +1439,374 @@ export async function detectFileChanges(currentFiles, projectRoot, config) {
|
|
|
948
1439
|
result.removed = Array.from(previousFiles);
|
|
949
1440
|
return result;
|
|
950
1441
|
}
|
|
1442
|
+
// =============================================================================
|
|
1443
|
+
// ATOMIC WRITES (Run 1 — D1)
|
|
1444
|
+
// =============================================================================
|
|
1445
|
+
/**
|
|
1446
|
+
* Atomically write a string to disk. Writes to `<target>.tmp` first, then
|
|
1447
|
+
* renames over `<target>`. fs.rename is atomic on POSIX within the same
|
|
1448
|
+
* filesystem, so a crashed mid-write leaves the prior file intact.
|
|
1449
|
+
*
|
|
1450
|
+
* Use this for any file that must remain readable during/after a scan
|
|
1451
|
+
* (index.json, graph.json, file_map.json, NAVSUMMARY.md, hashes.json).
|
|
1452
|
+
*/
|
|
1453
|
+
export async function atomicWriteFile(target, content, encoding = 'utf-8') {
|
|
1454
|
+
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
1455
|
+
await fs.promises.mkdir(path.dirname(target), { recursive: true });
|
|
1456
|
+
await fs.promises.writeFile(tmp, content, encoding);
|
|
1457
|
+
await fs.promises.rename(tmp, target);
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Atomically write a JSON-serializable value to disk (pretty-printed).
|
|
1461
|
+
*/
|
|
1462
|
+
export async function atomicWriteJSON(target, value) {
|
|
1463
|
+
await atomicWriteFile(target, JSON.stringify(value, null, 2), 'utf-8');
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Clear only the components and connections whose source files overlap
|
|
1467
|
+
* `changedPaths`. Used by incremental scans so we re-emit just the touched
|
|
1468
|
+
* subset and merge the rest by stable_id.
|
|
1469
|
+
*
|
|
1470
|
+
* - For components: a component is cleared if any of its `source.config_files`
|
|
1471
|
+
* appears in `changedPaths`.
|
|
1472
|
+
* - For connections: a connection is cleared if its `code_reference.file`
|
|
1473
|
+
* appears in `changedPaths`. (We do NOT delete based on the target's
|
|
1474
|
+
* source files — that would over-clear.)
|
|
1475
|
+
*
|
|
1476
|
+
* Survivors stay on disk and get merged with new incoming data via
|
|
1477
|
+
* mergeByStableId.
|
|
1478
|
+
*/
|
|
1479
|
+
export async function clearForFiles(config, projectRoot, changedPaths) {
|
|
1480
|
+
const cfg = config || getConfig();
|
|
1481
|
+
const componentsPath = getComponentsPath(cfg, projectRoot);
|
|
1482
|
+
const connectionsPath = getConnectionsPath(cfg, projectRoot);
|
|
1483
|
+
let componentsCleared = 0;
|
|
1484
|
+
let connectionsCleared = 0;
|
|
1485
|
+
if (changedPaths.size === 0) {
|
|
1486
|
+
return { componentsCleared, connectionsCleared };
|
|
1487
|
+
}
|
|
1488
|
+
// Components: scan and delete those whose source.config_files overlap.
|
|
1489
|
+
if (fs.existsSync(componentsPath)) {
|
|
1490
|
+
const files = await fs.promises.readdir(componentsPath);
|
|
1491
|
+
await Promise.all(files.map(async (file) => {
|
|
1492
|
+
const fp = path.join(componentsPath, file);
|
|
1493
|
+
try {
|
|
1494
|
+
const content = await fs.promises.readFile(fp, 'utf-8');
|
|
1495
|
+
const c = JSON.parse(content);
|
|
1496
|
+
const sourceFiles = c.source?.config_files ?? [];
|
|
1497
|
+
for (const sf of sourceFiles) {
|
|
1498
|
+
if (changedPaths.has(sf)) {
|
|
1499
|
+
await fs.promises.unlink(fp).catch(() => { });
|
|
1500
|
+
componentsCleared++;
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
catch {
|
|
1506
|
+
// Corrupt component file — leave alone; integrity check will catch it.
|
|
1507
|
+
}
|
|
1508
|
+
}));
|
|
1509
|
+
}
|
|
1510
|
+
// Connections: delete those whose origin file is in changedPaths.
|
|
1511
|
+
if (fs.existsSync(connectionsPath)) {
|
|
1512
|
+
const files = await fs.promises.readdir(connectionsPath);
|
|
1513
|
+
await Promise.all(files.map(async (file) => {
|
|
1514
|
+
const fp = path.join(connectionsPath, file);
|
|
1515
|
+
try {
|
|
1516
|
+
const content = await fs.promises.readFile(fp, 'utf-8');
|
|
1517
|
+
const c = JSON.parse(content);
|
|
1518
|
+
const refFile = c.code_reference?.file;
|
|
1519
|
+
if (refFile && changedPaths.has(refFile)) {
|
|
1520
|
+
await fs.promises.unlink(fp).catch(() => { });
|
|
1521
|
+
connectionsCleared++;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
catch {
|
|
1525
|
+
// Corrupt connection — leave alone.
|
|
1526
|
+
}
|
|
1527
|
+
}));
|
|
1528
|
+
}
|
|
1529
|
+
return { componentsCleared, connectionsCleared };
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Merge two arrays by stable_id, keeping the incoming entry on collision
|
|
1533
|
+
* (incoming wins because it's the freshly-scanned version of that entity).
|
|
1534
|
+
*
|
|
1535
|
+
* Generic over T because we use it for both components (keyed by stable_id)
|
|
1536
|
+
* and connections (keyed by composite from|to|type|file:line). Caller
|
|
1537
|
+
* supplies the key picker.
|
|
1538
|
+
*/
|
|
1539
|
+
export function mergeByStableId(existing, incoming, pickKey) {
|
|
1540
|
+
const merged = new Map();
|
|
1541
|
+
for (const e of existing) {
|
|
1542
|
+
const k = pickKey(e);
|
|
1543
|
+
if (k)
|
|
1544
|
+
merged.set(k, e);
|
|
1545
|
+
}
|
|
1546
|
+
for (const i of incoming) {
|
|
1547
|
+
const k = pickKey(i);
|
|
1548
|
+
if (k)
|
|
1549
|
+
merged.set(k, i); // incoming overwrites
|
|
1550
|
+
}
|
|
1551
|
+
return Array.from(merged.values());
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Load only the connections whose target component's source files include
|
|
1555
|
+
* any path in `changedFiles`. Returns the set of FROM-side source files —
|
|
1556
|
+
* i.e. files that import / depend on something in changedFiles.
|
|
1557
|
+
*
|
|
1558
|
+
* This is the reverse-dependency walk used by selectScanMode to widen the
|
|
1559
|
+
* incremental walk-set so changes to a leaf module re-scan the modules
|
|
1560
|
+
* that depend on it.
|
|
1561
|
+
*
|
|
1562
|
+
* Single-level only. Acceptable for Run 1; deeper transitivity is part of
|
|
1563
|
+
* Run 2's SQC audit layer.
|
|
1564
|
+
*
|
|
1565
|
+
* ALIASED IMPORTS (Run 1.6 — item #7 verify): connection target paths are
|
|
1566
|
+
* stored as RESOLVED, project-relative paths. The import scanner
|
|
1567
|
+
* (`src/scanners/connections/import-scanner.ts:resolveImport`) maps tsconfig
|
|
1568
|
+
* `paths` aliases (e.g. `@/utils/foo`) and `~/`-style aliases to actual file
|
|
1569
|
+
* paths (`src/utils/foo.ts`) before constructing the connection. This means
|
|
1570
|
+
* matching `changedFiles` against `code_reference.file` and the target
|
|
1571
|
+
* component's `source.config_files` is correct without alias-aware
|
|
1572
|
+
* normalization here. See `aliased-imports` test fixture for the regression
|
|
1573
|
+
* lock.
|
|
1574
|
+
*
|
|
1575
|
+
* RUN 1.6 — ITEM #8: This function now reads a derived
|
|
1576
|
+
* `.navgator/architecture/reverse-deps.json` index when present (single file
|
|
1577
|
+
* open per scan). Falls back to the per-edge JSON walk if the index file is
|
|
1578
|
+
* missing, corrupt, or schema-mismatched.
|
|
1579
|
+
*/
|
|
1580
|
+
export async function loadReverseDeps(changedFiles, config, projectRoot) {
|
|
1581
|
+
const cfg = config || getConfig();
|
|
1582
|
+
const out = new Set();
|
|
1583
|
+
if (changedFiles.size === 0)
|
|
1584
|
+
return out;
|
|
1585
|
+
// Run 1.6 — item #8 fast path: read the derived reverse-deps.json index if
|
|
1586
|
+
// present. Single file open vs O(connections) opens.
|
|
1587
|
+
const indexPath = path.join(getStoragePath(cfg, projectRoot), 'reverse-deps.json');
|
|
1588
|
+
if (fs.existsSync(indexPath)) {
|
|
1589
|
+
try {
|
|
1590
|
+
const raw = await fs.promises.readFile(indexPath, 'utf-8');
|
|
1591
|
+
const parsed = JSON.parse(raw);
|
|
1592
|
+
if (parsed && parsed.schema_version === '1.0.0' && parsed.edges) {
|
|
1593
|
+
for (const f of changedFiles) {
|
|
1594
|
+
const importers = parsed.edges[f];
|
|
1595
|
+
if (importers) {
|
|
1596
|
+
for (const imp of importers)
|
|
1597
|
+
out.add(imp);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
return out;
|
|
1601
|
+
}
|
|
1602
|
+
// Bad shape → fall through to legacy walk.
|
|
1603
|
+
}
|
|
1604
|
+
catch {
|
|
1605
|
+
// Corrupt or unreadable → fall through to legacy walk.
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
return loadReverseDepsLegacy(changedFiles, config, projectRoot);
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Legacy reverse-deps walk: opens every per-edge connection JSON. Retained
|
|
1612
|
+
* as the fallback when `reverse-deps.json` is missing, corrupt, or
|
|
1613
|
+
* schema-mismatched. Also useful as the regression baseline for the index.
|
|
1614
|
+
*/
|
|
1615
|
+
export async function loadReverseDepsLegacy(changedFiles, config, projectRoot) {
|
|
1616
|
+
const cfg = config || getConfig();
|
|
1617
|
+
const connectionsPath = getConnectionsPath(cfg, projectRoot);
|
|
1618
|
+
const componentsPath = getComponentsPath(cfg, projectRoot);
|
|
1619
|
+
const out = new Set();
|
|
1620
|
+
if (changedFiles.size === 0)
|
|
1621
|
+
return out;
|
|
1622
|
+
if (!fs.existsSync(connectionsPath) || !fs.existsSync(componentsPath)) {
|
|
1623
|
+
return out;
|
|
1624
|
+
}
|
|
1625
|
+
// Build component_id → source files map once (all on-disk components).
|
|
1626
|
+
const compSourceFiles = new Map();
|
|
1627
|
+
const compFiles = await fs.promises.readdir(componentsPath);
|
|
1628
|
+
await Promise.all(compFiles.map(async (file) => {
|
|
1629
|
+
try {
|
|
1630
|
+
const content = await fs.promises.readFile(path.join(componentsPath, file), 'utf-8');
|
|
1631
|
+
const c = JSON.parse(content);
|
|
1632
|
+
compSourceFiles.set(c.component_id, c.source?.config_files ?? []);
|
|
1633
|
+
}
|
|
1634
|
+
catch {
|
|
1635
|
+
// Corrupt component — ignore.
|
|
1636
|
+
}
|
|
1637
|
+
}));
|
|
1638
|
+
// Walk connections; if target's component has a source_file in changedFiles,
|
|
1639
|
+
// the FROM-side code_reference.file goes into the walk-set.
|
|
1640
|
+
const connFiles = await fs.promises.readdir(connectionsPath);
|
|
1641
|
+
await Promise.all(connFiles.map(async (file) => {
|
|
1642
|
+
try {
|
|
1643
|
+
const content = await fs.promises.readFile(path.join(connectionsPath, file), 'utf-8');
|
|
1644
|
+
const c = JSON.parse(content);
|
|
1645
|
+
const targetId = c.to?.component_id;
|
|
1646
|
+
if (!targetId)
|
|
1647
|
+
return;
|
|
1648
|
+
const targetSources = compSourceFiles.get(targetId) ?? [];
|
|
1649
|
+
const targetMatches = targetSources.some((sf) => changedFiles.has(sf));
|
|
1650
|
+
// Also cover the case where to.component_id IS a FILE: ref.
|
|
1651
|
+
const directFile = targetId.startsWith('FILE:') ? targetId.slice(5) : undefined;
|
|
1652
|
+
const directMatch = directFile ? changedFiles.has(directFile) : false;
|
|
1653
|
+
if (targetMatches || directMatch) {
|
|
1654
|
+
const fromFile = c.code_reference?.file;
|
|
1655
|
+
if (fromFile)
|
|
1656
|
+
out.add(fromFile);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
catch {
|
|
1660
|
+
// Corrupt connection — ignore.
|
|
1661
|
+
}
|
|
1662
|
+
}));
|
|
1663
|
+
return out;
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Build and atomically write `.navgator/architecture/reverse-deps.json` from
|
|
1667
|
+
* the in-memory connection set + components. This avoids re-walking per-edge
|
|
1668
|
+
* JSON files on the next incremental scan.
|
|
1669
|
+
*
|
|
1670
|
+
* Run 1.6 — item #8: HEADLINE PERF WIN. On atomize-ai's 4,570 connections,
|
|
1671
|
+
* this drops `loadReverseDeps` from ~4,570 file opens to 1.
|
|
1672
|
+
*/
|
|
1673
|
+
export async function buildReverseDepsIndex(components, connections, config, projectRoot) {
|
|
1674
|
+
const cfg = config || getConfig();
|
|
1675
|
+
const indexPath = path.join(getStoragePath(cfg, projectRoot), 'reverse-deps.json');
|
|
1676
|
+
// component_id → source files map (in-memory, no I/O).
|
|
1677
|
+
const compSourceFiles = new Map();
|
|
1678
|
+
for (const c of components) {
|
|
1679
|
+
compSourceFiles.set(c.component_id, c.source?.config_files ?? []);
|
|
1680
|
+
}
|
|
1681
|
+
// Build edges: target_file → list of source files that import/reference it.
|
|
1682
|
+
const edges = {};
|
|
1683
|
+
let edgeCount = 0;
|
|
1684
|
+
for (const c of connections) {
|
|
1685
|
+
const fromFile = c.code_reference?.file;
|
|
1686
|
+
if (!fromFile)
|
|
1687
|
+
continue;
|
|
1688
|
+
const targetId = c.to?.component_id;
|
|
1689
|
+
if (!targetId)
|
|
1690
|
+
continue;
|
|
1691
|
+
// Resolve target file(s) — same logic as loadReverseDepsLegacy.
|
|
1692
|
+
const targetFiles = new Set();
|
|
1693
|
+
const targetSources = compSourceFiles.get(targetId) ?? [];
|
|
1694
|
+
for (const sf of targetSources)
|
|
1695
|
+
targetFiles.add(sf);
|
|
1696
|
+
if (targetId.startsWith('FILE:'))
|
|
1697
|
+
targetFiles.add(targetId.slice(5));
|
|
1698
|
+
// Also use to.location.file when present (e.g. import-scanner stores resolved path here).
|
|
1699
|
+
const locFile = c.to?.location?.file;
|
|
1700
|
+
if (locFile)
|
|
1701
|
+
targetFiles.add(locFile);
|
|
1702
|
+
for (const tf of targetFiles) {
|
|
1703
|
+
if (tf === fromFile)
|
|
1704
|
+
continue; // Skip self-edges
|
|
1705
|
+
if (!edges[tf])
|
|
1706
|
+
edges[tf] = new Set();
|
|
1707
|
+
if (!edges[tf].has(fromFile)) {
|
|
1708
|
+
edges[tf].add(fromFile);
|
|
1709
|
+
edgeCount += 1;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
// Sets → arrays for JSON.
|
|
1714
|
+
const edgesOut = {};
|
|
1715
|
+
for (const [target, importers] of Object.entries(edges)) {
|
|
1716
|
+
edgesOut[target] = Array.from(importers).sort();
|
|
1717
|
+
}
|
|
1718
|
+
const payload = {
|
|
1719
|
+
schema_version: '1.0.0',
|
|
1720
|
+
generated_at: Date.now(),
|
|
1721
|
+
edges: edgesOut,
|
|
1722
|
+
};
|
|
1723
|
+
await atomicWriteJSON(indexPath, payload);
|
|
1724
|
+
return { path: indexPath, edge_count: edgeCount };
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Atomically write `.navgator/architecture/manifest.json` describing the
|
|
1728
|
+
* derived artifacts NavGator just emitted. Best-effort — the scan succeeds
|
|
1729
|
+
* even if this fails.
|
|
1730
|
+
*/
|
|
1731
|
+
export async function buildDerivedManifest(config, projectRoot, details) {
|
|
1732
|
+
const cfg = config || getConfig();
|
|
1733
|
+
const storeDir = getStoragePath(cfg, projectRoot);
|
|
1734
|
+
const manifestPath = path.join(storeDir, 'manifest.json');
|
|
1735
|
+
const now = Date.now();
|
|
1736
|
+
const files = {};
|
|
1737
|
+
const candidates = [
|
|
1738
|
+
{ name: 'index.json' },
|
|
1739
|
+
{ name: 'graph.json' },
|
|
1740
|
+
{ name: 'file_map.json' },
|
|
1741
|
+
{ name: 'reverse-deps.json', source_count: details.reverseDepsEdgeCount },
|
|
1742
|
+
];
|
|
1743
|
+
for (const cand of candidates) {
|
|
1744
|
+
const full = path.join(storeDir, cand.name);
|
|
1745
|
+
try {
|
|
1746
|
+
const stat = await fs.promises.stat(full);
|
|
1747
|
+
files[cand.name] = {
|
|
1748
|
+
generated_at: stat.mtimeMs,
|
|
1749
|
+
...(cand.source_count !== undefined ? { source_count: cand.source_count } : {}),
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
catch {
|
|
1753
|
+
// File doesn't exist — skip silently.
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
const payload = {
|
|
1757
|
+
schema_version: '1.0.0',
|
|
1758
|
+
generated_at: now,
|
|
1759
|
+
files,
|
|
1760
|
+
};
|
|
1761
|
+
await atomicWriteJSON(manifestPath, payload);
|
|
1762
|
+
return { path: manifestPath };
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Run an integrity check on the post-merge state.
|
|
1766
|
+
* - Every connection endpoint (from + to component_id) must exist in
|
|
1767
|
+
* the components set, OR be a FILE:-prefixed unresolved ref.
|
|
1768
|
+
* - Every component's source.config_files must exist on disk
|
|
1769
|
+
* (relative to projectRoot). Missing files → orphan component.
|
|
1770
|
+
* - Optional walkSet narrows the source-file existence check to
|
|
1771
|
+
* files in the walk-set; full-scan callers pass an empty set
|
|
1772
|
+
* (means "check all").
|
|
1773
|
+
*
|
|
1774
|
+
* Returns ok=false on any failure with a list of issue strings.
|
|
1775
|
+
* Caller is expected to log scan_type='incremental→full' and
|
|
1776
|
+
* fall through to a full scan on failure.
|
|
1777
|
+
*/
|
|
1778
|
+
export async function runIntegrityCheck(components, connections, projectRoot, walkSet = new Set()) {
|
|
1779
|
+
const issues = [];
|
|
1780
|
+
// 1. Every connection endpoint must exist (or be FILE:).
|
|
1781
|
+
const ids = new Set(components.map((c) => c.component_id));
|
|
1782
|
+
for (const c of connections) {
|
|
1783
|
+
const fromId = c.from?.component_id;
|
|
1784
|
+
const toId = c.to?.component_id;
|
|
1785
|
+
if (fromId && !fromId.startsWith('FILE:') && !ids.has(fromId)) {
|
|
1786
|
+
issues.push(`connection ${c.connection_id}: missing FROM component ${fromId}`);
|
|
1787
|
+
}
|
|
1788
|
+
if (toId && !toId.startsWith('FILE:') && !ids.has(toId)) {
|
|
1789
|
+
issues.push(`connection ${c.connection_id}: missing TO component ${toId}`);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
// 2. Every component's source.config_files must exist on disk.
|
|
1793
|
+
// If walkSet is non-empty, only check files in the walk-set
|
|
1794
|
+
// (we trust the rest from the prior scan).
|
|
1795
|
+
for (const comp of components) {
|
|
1796
|
+
const sourceFiles = comp.source?.config_files ?? [];
|
|
1797
|
+
for (const sf of sourceFiles) {
|
|
1798
|
+
if (walkSet.size > 0 && !walkSet.has(sf))
|
|
1799
|
+
continue;
|
|
1800
|
+
try {
|
|
1801
|
+
await fs.promises.access(path.join(projectRoot, sf));
|
|
1802
|
+
}
|
|
1803
|
+
catch {
|
|
1804
|
+
issues.push(`component ${comp.component_id} (${comp.name}): missing source file ${sf}`);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return { ok: issues.length === 0, issues };
|
|
1809
|
+
}
|
|
951
1810
|
/**
|
|
952
1811
|
* Get a summary of file changes for display
|
|
953
1812
|
*/
|