@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.
Files changed (692) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude-plugin/marketplace.json +21 -7
  3. package/.claude-plugin/plugin.json +16 -11
  4. package/.codex-plugin/plugin.json +31 -0
  5. package/.mcp.json +8 -0
  6. package/CLAUDE.md +197 -23
  7. package/LICENSE +202 -21
  8. package/README.md +220 -33
  9. package/agents/architecture-advisor.md +6 -2
  10. package/agents/architecture-investigator.md +163 -0
  11. package/agents/architecture-planner.md +160 -0
  12. package/agents/external-resolver.md +97 -0
  13. package/dist/__tests__/agent-output.test.d.ts +5 -0
  14. package/dist/__tests__/agent-output.test.d.ts.map +1 -0
  15. package/dist/__tests__/agent-output.test.js +233 -0
  16. package/dist/__tests__/agent-output.test.js.map +1 -0
  17. package/dist/__tests__/architecture-insights-stack.test.d.ts +21 -0
  18. package/dist/__tests__/architecture-insights-stack.test.d.ts.map +1 -0
  19. package/dist/__tests__/architecture-insights-stack.test.js +86 -0
  20. package/dist/__tests__/architecture-insights-stack.test.js.map +1 -0
  21. package/dist/__tests__/architecture-insights.test.d.ts +2 -0
  22. package/dist/__tests__/architecture-insights.test.d.ts.map +1 -0
  23. package/dist/__tests__/architecture-insights.test.js +46 -0
  24. package/dist/__tests__/architecture-insights.test.js.map +1 -0
  25. package/dist/__tests__/audit-sampler.test.d.ts +10 -0
  26. package/dist/__tests__/audit-sampler.test.d.ts.map +1 -0
  27. package/dist/__tests__/audit-sampler.test.js +172 -0
  28. package/dist/__tests__/audit-sampler.test.js.map +1 -0
  29. package/dist/__tests__/audit-spc.test.d.ts +5 -0
  30. package/dist/__tests__/audit-spc.test.d.ts.map +1 -0
  31. package/dist/__tests__/audit-spc.test.js +94 -0
  32. package/dist/__tests__/audit-spc.test.js.map +1 -0
  33. package/dist/__tests__/audit-verifiers.test.d.ts +5 -0
  34. package/dist/__tests__/audit-verifiers.test.d.ts.map +1 -0
  35. package/dist/__tests__/audit-verifiers.test.js +248 -0
  36. package/dist/__tests__/audit-verifiers.test.js.map +1 -0
  37. package/dist/__tests__/auto-refresh.test.d.ts +12 -0
  38. package/dist/__tests__/auto-refresh.test.d.ts.map +1 -0
  39. package/dist/__tests__/auto-refresh.test.js +236 -0
  40. package/dist/__tests__/auto-refresh.test.js.map +1 -0
  41. package/dist/__tests__/bare-imports.test.d.ts +8 -0
  42. package/dist/__tests__/bare-imports.test.d.ts.map +1 -0
  43. package/dist/__tests__/bare-imports.test.js +176 -0
  44. package/dist/__tests__/bare-imports.test.js.map +1 -0
  45. package/dist/__tests__/classify.test.d.ts +5 -0
  46. package/dist/__tests__/classify.test.d.ts.map +1 -0
  47. package/dist/__tests__/classify.test.js +158 -0
  48. package/dist/__tests__/classify.test.js.map +1 -0
  49. package/dist/__tests__/cli-commands.test.d.ts +8 -0
  50. package/dist/__tests__/cli-commands.test.d.ts.map +1 -0
  51. package/dist/__tests__/cli-commands.test.js +207 -0
  52. package/dist/__tests__/cli-commands.test.js.map +1 -0
  53. package/dist/__tests__/consolidated-readers.test.d.ts +23 -0
  54. package/dist/__tests__/consolidated-readers.test.d.ts.map +1 -0
  55. package/dist/__tests__/consolidated-readers.test.js +200 -0
  56. package/dist/__tests__/consolidated-readers.test.js.map +1 -0
  57. package/dist/__tests__/coverage.test.d.ts +5 -0
  58. package/dist/__tests__/coverage.test.d.ts.map +1 -0
  59. package/dist/__tests__/coverage.test.js +120 -0
  60. package/dist/__tests__/coverage.test.js.map +1 -0
  61. package/dist/__tests__/deploy-scanner-runtime.test.d.ts +6 -0
  62. package/dist/__tests__/deploy-scanner-runtime.test.d.ts.map +1 -0
  63. package/dist/__tests__/deploy-scanner-runtime.test.js +168 -0
  64. package/dist/__tests__/deploy-scanner-runtime.test.js.map +1 -0
  65. package/dist/__tests__/env-scanner.test.d.ts +2 -0
  66. package/dist/__tests__/env-scanner.test.d.ts.map +1 -0
  67. package/dist/__tests__/env-scanner.test.js +191 -0
  68. package/dist/__tests__/env-scanner.test.js.map +1 -0
  69. package/dist/__tests__/freshness/cli-freshness.test.d.ts +2 -0
  70. package/dist/__tests__/freshness/cli-freshness.test.d.ts.map +1 -0
  71. package/dist/__tests__/freshness/cli-freshness.test.js +26 -0
  72. package/dist/__tests__/freshness/cli-freshness.test.js.map +1 -0
  73. package/dist/__tests__/freshness/dirty-ledger.test.d.ts +2 -0
  74. package/dist/__tests__/freshness/dirty-ledger.test.d.ts.map +1 -0
  75. package/dist/__tests__/freshness/dirty-ledger.test.js +39 -0
  76. package/dist/__tests__/freshness/dirty-ledger.test.js.map +1 -0
  77. package/dist/__tests__/freshness/drainer.test.d.ts +2 -0
  78. package/dist/__tests__/freshness/drainer.test.d.ts.map +1 -0
  79. package/dist/__tests__/freshness/drainer.test.js +103 -0
  80. package/dist/__tests__/freshness/drainer.test.js.map +1 -0
  81. package/dist/__tests__/freshness/paths.test.d.ts +2 -0
  82. package/dist/__tests__/freshness/paths.test.d.ts.map +1 -0
  83. package/dist/__tests__/freshness/paths.test.js +19 -0
  84. package/dist/__tests__/freshness/paths.test.js.map +1 -0
  85. package/dist/__tests__/freshness/scan-lock.test.d.ts +2 -0
  86. package/dist/__tests__/freshness/scan-lock.test.d.ts.map +1 -0
  87. package/dist/__tests__/freshness/scan-lock.test.js +40 -0
  88. package/dist/__tests__/freshness/scan-lock.test.js.map +1 -0
  89. package/dist/__tests__/freshness/stamp.test.d.ts +2 -0
  90. package/dist/__tests__/freshness/stamp.test.d.ts.map +1 -0
  91. package/dist/__tests__/freshness/stamp.test.js +36 -0
  92. package/dist/__tests__/freshness/stamp.test.js.map +1 -0
  93. package/dist/__tests__/gitignore-safety.test.d.ts +2 -0
  94. package/dist/__tests__/gitignore-safety.test.d.ts.map +1 -0
  95. package/dist/__tests__/gitignore-safety.test.js +110 -0
  96. package/dist/__tests__/gitignore-safety.test.js.map +1 -0
  97. package/dist/__tests__/helpers.d.ts +37 -0
  98. package/dist/__tests__/helpers.d.ts.map +1 -0
  99. package/dist/__tests__/helpers.js +134 -0
  100. package/dist/__tests__/helpers.js.map +1 -0
  101. package/dist/__tests__/impact.test.d.ts +5 -0
  102. package/dist/__tests__/impact.test.d.ts.map +1 -0
  103. package/dist/__tests__/impact.test.js +221 -0
  104. package/dist/__tests__/impact.test.js.map +1 -0
  105. package/dist/__tests__/lessons-store.test.d.ts +8 -0
  106. package/dist/__tests__/lessons-store.test.d.ts.map +1 -0
  107. package/dist/__tests__/lessons-store.test.js +232 -0
  108. package/dist/__tests__/lessons-store.test.js.map +1 -0
  109. package/dist/__tests__/llm-dedup.test.d.ts +2 -0
  110. package/dist/__tests__/llm-dedup.test.d.ts.map +1 -0
  111. package/dist/__tests__/llm-dedup.test.js +155 -0
  112. package/dist/__tests__/llm-dedup.test.js.map +1 -0
  113. package/dist/__tests__/mjs-frontend-fetch.test.d.ts +19 -0
  114. package/dist/__tests__/mjs-frontend-fetch.test.d.ts.map +1 -0
  115. package/dist/__tests__/mjs-frontend-fetch.test.js +179 -0
  116. package/dist/__tests__/mjs-frontend-fetch.test.js.map +1 -0
  117. package/dist/__tests__/multi-stack-discovery.test.d.ts +11 -0
  118. package/dist/__tests__/multi-stack-discovery.test.d.ts.map +1 -0
  119. package/dist/__tests__/multi-stack-discovery.test.js +75 -0
  120. package/dist/__tests__/multi-stack-discovery.test.js.map +1 -0
  121. package/dist/__tests__/per-entity-files-gate.test.d.ts +22 -0
  122. package/dist/__tests__/per-entity-files-gate.test.d.ts.map +1 -0
  123. package/dist/__tests__/per-entity-files-gate.test.js +160 -0
  124. package/dist/__tests__/per-entity-files-gate.test.js.map +1 -0
  125. package/dist/__tests__/prisma-calls.test.d.ts +2 -0
  126. package/dist/__tests__/prisma-calls.test.d.ts.map +1 -0
  127. package/dist/__tests__/prisma-calls.test.js +125 -0
  128. package/dist/__tests__/prisma-calls.test.js.map +1 -0
  129. package/dist/__tests__/prisma-parser.test.d.ts +2 -0
  130. package/dist/__tests__/prisma-parser.test.d.ts.map +1 -0
  131. package/dist/__tests__/prisma-parser.test.js +252 -0
  132. package/dist/__tests__/prisma-parser.test.js.map +1 -0
  133. package/dist/__tests__/prompt-detector.test.d.ts +5 -0
  134. package/dist/__tests__/prompt-detector.test.d.ts.map +1 -0
  135. package/dist/__tests__/prompt-detector.test.js +75 -0
  136. package/dist/__tests__/prompt-detector.test.js.map +1 -0
  137. package/dist/__tests__/queue-scanner.test.d.ts +5 -0
  138. package/dist/__tests__/queue-scanner.test.d.ts.map +1 -0
  139. package/dist/__tests__/queue-scanner.test.js +85 -0
  140. package/dist/__tests__/queue-scanner.test.js.map +1 -0
  141. package/dist/__tests__/resolve.test.d.ts +5 -0
  142. package/dist/__tests__/resolve.test.d.ts.map +1 -0
  143. package/dist/__tests__/resolve.test.js +196 -0
  144. package/dist/__tests__/resolve.test.js.map +1 -0
  145. package/dist/__tests__/rules.test.d.ts +2 -0
  146. package/dist/__tests__/rules.test.d.ts.map +1 -0
  147. package/dist/__tests__/rules.test.js +343 -0
  148. package/dist/__tests__/rules.test.js.map +1 -0
  149. package/dist/__tests__/sandbox.test.d.ts +5 -0
  150. package/dist/__tests__/sandbox.test.d.ts.map +1 -0
  151. package/dist/__tests__/sandbox.test.js +189 -0
  152. package/dist/__tests__/sandbox.test.js.map +1 -0
  153. package/dist/__tests__/scanner-audit.test.d.ts +9 -0
  154. package/dist/__tests__/scanner-audit.test.d.ts.map +1 -0
  155. package/dist/__tests__/scanner-audit.test.js +64 -0
  156. package/dist/__tests__/scanner-audit.test.js.map +1 -0
  157. package/dist/__tests__/scanner-characterization.test.d.ts +16 -0
  158. package/dist/__tests__/scanner-characterization.test.d.ts.map +1 -0
  159. package/dist/__tests__/scanner-characterization.test.js +167 -0
  160. package/dist/__tests__/scanner-characterization.test.js.map +1 -0
  161. package/dist/__tests__/scanner-incremental.test.d.ts +13 -0
  162. package/dist/__tests__/scanner-incremental.test.d.ts.map +1 -0
  163. package/dist/__tests__/scanner-incremental.test.js +725 -0
  164. package/dist/__tests__/scanner-incremental.test.js.map +1 -0
  165. package/dist/__tests__/scanner-integration.test.d.ts +7 -0
  166. package/dist/__tests__/scanner-integration.test.d.ts.map +1 -0
  167. package/dist/__tests__/scanner-integration.test.js +211 -0
  168. package/dist/__tests__/scanner-integration.test.js.map +1 -0
  169. package/dist/__tests__/scip-new-catches.test.d.ts +19 -0
  170. package/dist/__tests__/scip-new-catches.test.d.ts.map +1 -0
  171. package/dist/__tests__/scip-new-catches.test.js +90 -0
  172. package/dist/__tests__/scip-new-catches.test.js.map +1 -0
  173. package/dist/__tests__/subgraph.test.d.ts +5 -0
  174. package/dist/__tests__/subgraph.test.d.ts.map +1 -0
  175. package/dist/__tests__/subgraph.test.js +145 -0
  176. package/dist/__tests__/subgraph.test.js.map +1 -0
  177. package/dist/__tests__/trace.test.d.ts +5 -0
  178. package/dist/__tests__/trace.test.d.ts.map +1 -0
  179. package/dist/__tests__/trace.test.js +221 -0
  180. package/dist/__tests__/trace.test.js.map +1 -0
  181. package/dist/agent-output.d.ts +16 -0
  182. package/dist/agent-output.d.ts.map +1 -0
  183. package/dist/agent-output.js +142 -0
  184. package/dist/agent-output.js.map +1 -0
  185. package/dist/architecture-insights.d.ts +17 -0
  186. package/dist/architecture-insights.d.ts.map +1 -0
  187. package/dist/architecture-insights.js +178 -0
  188. package/dist/architecture-insights.js.map +1 -0
  189. package/dist/audit/index.d.ts +69 -0
  190. package/dist/audit/index.d.ts.map +1 -0
  191. package/dist/audit/index.js +255 -0
  192. package/dist/audit/index.js.map +1 -0
  193. package/dist/audit/sampler.d.ts +98 -0
  194. package/dist/audit/sampler.d.ts.map +1 -0
  195. package/dist/audit/sampler.js +298 -0
  196. package/dist/audit/sampler.js.map +1 -0
  197. package/dist/audit/spc.d.ts +62 -0
  198. package/dist/audit/spc.d.ts.map +1 -0
  199. package/dist/audit/spc.js +81 -0
  200. package/dist/audit/spc.js.map +1 -0
  201. package/dist/audit/verifiers.d.ts +81 -0
  202. package/dist/audit/verifiers.d.ts.map +1 -0
  203. package/dist/audit/verifiers.js +366 -0
  204. package/dist/audit/verifiers.js.map +1 -0
  205. package/dist/classify.d.ts +19 -0
  206. package/dist/classify.d.ts.map +1 -0
  207. package/dist/classify.js +124 -0
  208. package/dist/classify.js.map +1 -0
  209. package/dist/cli/commands/connections.d.ts +3 -0
  210. package/dist/cli/commands/connections.d.ts.map +1 -0
  211. package/dist/cli/commands/connections.js +125 -0
  212. package/dist/cli/commands/connections.js.map +1 -0
  213. package/dist/cli/commands/coverage.d.ts +3 -0
  214. package/dist/cli/commands/coverage.d.ts.map +1 -0
  215. package/dist/cli/commands/coverage.js +94 -0
  216. package/dist/cli/commands/coverage.js.map +1 -0
  217. package/dist/cli/commands/dead.d.ts +3 -0
  218. package/dist/cli/commands/dead.d.ts.map +1 -0
  219. package/dist/cli/commands/dead.js +80 -0
  220. package/dist/cli/commands/dead.js.map +1 -0
  221. package/dist/cli/commands/diagram.d.ts +3 -0
  222. package/dist/cli/commands/diagram.d.ts.map +1 -0
  223. package/dist/cli/commands/diagram.js +102 -0
  224. package/dist/cli/commands/diagram.js.map +1 -0
  225. package/dist/cli/commands/find.d.ts +3 -0
  226. package/dist/cli/commands/find.d.ts.map +1 -0
  227. package/dist/cli/commands/find.js +128 -0
  228. package/dist/cli/commands/find.js.map +1 -0
  229. package/dist/cli/commands/freshness.d.ts +20 -0
  230. package/dist/cli/commands/freshness.d.ts.map +1 -0
  231. package/dist/cli/commands/freshness.js +90 -0
  232. package/dist/cli/commands/freshness.js.map +1 -0
  233. package/dist/cli/commands/helpers.d.ts +7 -0
  234. package/dist/cli/commands/helpers.d.ts.map +1 -0
  235. package/dist/cli/commands/helpers.js +30 -0
  236. package/dist/cli/commands/helpers.js.map +1 -0
  237. package/dist/cli/commands/impact.d.ts +3 -0
  238. package/dist/cli/commands/impact.d.ts.map +1 -0
  239. package/dist/cli/commands/impact.js +172 -0
  240. package/dist/cli/commands/impact.js.map +1 -0
  241. package/dist/cli/commands/lessons.d.ts +6 -0
  242. package/dist/cli/commands/lessons.d.ts.map +1 -0
  243. package/dist/cli/commands/lessons.js +279 -0
  244. package/dist/cli/commands/lessons.js.map +1 -0
  245. package/dist/cli/commands/list.d.ts +3 -0
  246. package/dist/cli/commands/list.d.ts.map +1 -0
  247. package/dist/cli/commands/list.js +91 -0
  248. package/dist/cli/commands/list.js.map +1 -0
  249. package/dist/cli/commands/llm-map.d.ts +3 -0
  250. package/dist/cli/commands/llm-map.d.ts.map +1 -0
  251. package/dist/cli/commands/llm-map.js +121 -0
  252. package/dist/cli/commands/llm-map.js.map +1 -0
  253. package/dist/cli/commands/misc.d.ts +17 -0
  254. package/dist/cli/commands/misc.d.ts.map +1 -0
  255. package/dist/cli/commands/misc.js +495 -0
  256. package/dist/cli/commands/misc.js.map +1 -0
  257. package/dist/cli/commands/prompts.d.ts +3 -0
  258. package/dist/cli/commands/prompts.d.ts.map +1 -0
  259. package/dist/cli/commands/prompts.js +74 -0
  260. package/dist/cli/commands/prompts.js.map +1 -0
  261. package/dist/cli/commands/rules.d.ts +3 -0
  262. package/dist/cli/commands/rules.d.ts.map +1 -0
  263. package/dist/cli/commands/rules.js +61 -0
  264. package/dist/cli/commands/rules.js.map +1 -0
  265. package/dist/cli/commands/scan.d.ts +3 -0
  266. package/dist/cli/commands/scan.d.ts.map +1 -0
  267. package/dist/cli/commands/scan.js +177 -0
  268. package/dist/cli/commands/scan.js.map +1 -0
  269. package/dist/cli/commands/schema.d.ts +3 -0
  270. package/dist/cli/commands/schema.d.ts.map +1 -0
  271. package/dist/cli/commands/schema.js +126 -0
  272. package/dist/cli/commands/schema.js.map +1 -0
  273. package/dist/cli/commands/status.d.ts +3 -0
  274. package/dist/cli/commands/status.d.ts.map +1 -0
  275. package/dist/cli/commands/status.js +340 -0
  276. package/dist/cli/commands/status.js.map +1 -0
  277. package/dist/cli/commands/subgraph.d.ts +3 -0
  278. package/dist/cli/commands/subgraph.d.ts.map +1 -0
  279. package/dist/cli/commands/subgraph.js +55 -0
  280. package/dist/cli/commands/subgraph.js.map +1 -0
  281. package/dist/cli/commands/temporal.d.ts +3 -0
  282. package/dist/cli/commands/temporal.d.ts.map +1 -0
  283. package/dist/cli/commands/temporal.js +112 -0
  284. package/dist/cli/commands/temporal.js.map +1 -0
  285. package/dist/cli/commands/trace.d.ts +3 -0
  286. package/dist/cli/commands/trace.d.ts.map +1 -0
  287. package/dist/cli/commands/trace.js +65 -0
  288. package/dist/cli/commands/trace.js.map +1 -0
  289. package/dist/cli/index.js +88 -825
  290. package/dist/cli/index.js.map +1 -1
  291. package/dist/config.d.ts +13 -2
  292. package/dist/config.d.ts.map +1 -1
  293. package/dist/config.js +106 -12
  294. package/dist/config.js.map +1 -1
  295. package/dist/coverage.d.ts +37 -0
  296. package/dist/coverage.d.ts.map +1 -0
  297. package/dist/coverage.js +177 -0
  298. package/dist/coverage.js.map +1 -0
  299. package/dist/diagram.d.ts.map +1 -1
  300. package/dist/diagram.js +41 -0
  301. package/dist/diagram.js.map +1 -1
  302. package/dist/diff.d.ts +57 -0
  303. package/dist/diff.d.ts.map +1 -0
  304. package/dist/diff.js +527 -0
  305. package/dist/diff.js.map +1 -0
  306. package/dist/enrich/cache.d.ts +41 -0
  307. package/dist/enrich/cache.d.ts.map +1 -0
  308. package/dist/enrich/cache.js +97 -0
  309. package/dist/enrich/cache.js.map +1 -0
  310. package/dist/enrich/external-enrichment.types.d.ts +91 -0
  311. package/dist/enrich/external-enrichment.types.d.ts.map +1 -0
  312. package/dist/enrich/external-enrichment.types.js +38 -0
  313. package/dist/enrich/external-enrichment.types.js.map +1 -0
  314. package/dist/enrich/external-resolver.d.ts +95 -0
  315. package/dist/enrich/external-resolver.d.ts.map +1 -0
  316. package/dist/enrich/external-resolver.js +222 -0
  317. package/dist/enrich/external-resolver.js.map +1 -0
  318. package/dist/enrich/fetchers.d.ts +30 -0
  319. package/dist/enrich/fetchers.d.ts.map +1 -0
  320. package/dist/enrich/fetchers.js +89 -0
  321. package/dist/enrich/fetchers.js.map +1 -0
  322. package/dist/file-resolve.d.ts +35 -0
  323. package/dist/file-resolve.d.ts.map +1 -0
  324. package/dist/file-resolve.js +159 -0
  325. package/dist/file-resolve.js.map +1 -0
  326. package/dist/freshness/dirty-ledger.d.ts +7 -0
  327. package/dist/freshness/dirty-ledger.d.ts.map +1 -0
  328. package/dist/freshness/dirty-ledger.js +56 -0
  329. package/dist/freshness/dirty-ledger.js.map +1 -0
  330. package/dist/freshness/drainer.d.ts +38 -0
  331. package/dist/freshness/drainer.d.ts.map +1 -0
  332. package/dist/freshness/drainer.js +88 -0
  333. package/dist/freshness/drainer.js.map +1 -0
  334. package/dist/freshness/paths.d.ts +9 -0
  335. package/dist/freshness/paths.d.ts.map +1 -0
  336. package/dist/freshness/paths.js +24 -0
  337. package/dist/freshness/paths.js.map +1 -0
  338. package/dist/freshness/scan-lock.d.ts +8 -0
  339. package/dist/freshness/scan-lock.d.ts.map +1 -0
  340. package/dist/freshness/scan-lock.js +68 -0
  341. package/dist/freshness/scan-lock.js.map +1 -0
  342. package/dist/freshness/stamp.d.ts +25 -0
  343. package/dist/freshness/stamp.d.ts.map +1 -0
  344. package/dist/freshness/stamp.js +50 -0
  345. package/dist/freshness/stamp.js.map +1 -0
  346. package/dist/git.d.ts +12 -0
  347. package/dist/git.d.ts.map +1 -0
  348. package/dist/git.js +42 -0
  349. package/dist/git.js.map +1 -0
  350. package/dist/gitignore-safety.d.ts +41 -0
  351. package/dist/gitignore-safety.d.ts.map +1 -0
  352. package/dist/gitignore-safety.js +107 -0
  353. package/dist/gitignore-safety.js.map +1 -0
  354. package/dist/impact.d.ts +20 -0
  355. package/dist/impact.d.ts.map +1 -0
  356. package/dist/impact.js +89 -0
  357. package/dist/impact.js.map +1 -0
  358. package/dist/index.d.ts +24 -2
  359. package/dist/index.d.ts.map +1 -1
  360. package/dist/index.js +31 -1
  361. package/dist/index.js.map +1 -1
  362. package/dist/lessons-store.d.ts +93 -0
  363. package/dist/lessons-store.d.ts.map +1 -0
  364. package/dist/lessons-store.js +265 -0
  365. package/dist/lessons-store.js.map +1 -0
  366. package/dist/llm-dedup.d.ts +40 -0
  367. package/dist/llm-dedup.d.ts.map +1 -0
  368. package/dist/llm-dedup.js +373 -0
  369. package/dist/llm-dedup.js.map +1 -0
  370. package/dist/mcp/server.d.ts +9 -0
  371. package/dist/mcp/server.d.ts.map +1 -0
  372. package/dist/mcp/server.js +87 -0
  373. package/dist/mcp/server.js.map +1 -0
  374. package/dist/mcp/tools.d.ts +198 -0
  375. package/dist/mcp/tools.d.ts.map +1 -0
  376. package/dist/mcp/tools.js +744 -0
  377. package/dist/mcp/tools.js.map +1 -0
  378. package/dist/metrics/pagerank-louvain.d.ts +44 -0
  379. package/dist/metrics/pagerank-louvain.d.ts.map +1 -0
  380. package/dist/metrics/pagerank-louvain.js +128 -0
  381. package/dist/metrics/pagerank-louvain.js.map +1 -0
  382. package/dist/parsers/scip-runner.d.ts +63 -0
  383. package/dist/parsers/scip-runner.d.ts.map +1 -0
  384. package/dist/parsers/scip-runner.js +179 -0
  385. package/dist/parsers/scip-runner.js.map +1 -0
  386. package/dist/projects.d.ts +54 -0
  387. package/dist/projects.d.ts.map +1 -0
  388. package/dist/projects.js +153 -0
  389. package/dist/projects.js.map +1 -0
  390. package/dist/resolve.d.ts +22 -0
  391. package/dist/resolve.d.ts.map +1 -0
  392. package/dist/resolve.js +128 -0
  393. package/dist/resolve.js.map +1 -0
  394. package/dist/rules.d.ts +36 -0
  395. package/dist/rules.d.ts.map +1 -0
  396. package/dist/rules.js +484 -0
  397. package/dist/rules.js.map +1 -0
  398. package/dist/sandbox.d.ts +33 -0
  399. package/dist/sandbox.d.ts.map +1 -0
  400. package/dist/sandbox.js +91 -0
  401. package/dist/sandbox.js.map +1 -0
  402. package/dist/scan-lock.d.ts +37 -0
  403. package/dist/scan-lock.d.ts.map +1 -0
  404. package/dist/scan-lock.js +145 -0
  405. package/dist/scan-lock.js.map +1 -0
  406. package/dist/scanner.d.ts +126 -1
  407. package/dist/scanner.d.ts.map +1 -1
  408. package/dist/scanner.js +1711 -235
  409. package/dist/scanner.js.map +1 -1
  410. package/dist/scanners/connections/ast-scanner.d.ts +9 -2
  411. package/dist/scanners/connections/ast-scanner.d.ts.map +1 -1
  412. package/dist/scanners/connections/ast-scanner.js +19 -4
  413. package/dist/scanners/connections/ast-scanner.js.map +1 -1
  414. package/dist/scanners/connections/import-scanner.d.ts +27 -0
  415. package/dist/scanners/connections/import-scanner.d.ts.map +1 -0
  416. package/dist/scanners/connections/import-scanner.js +537 -0
  417. package/dist/scanners/connections/import-scanner.js.map +1 -0
  418. package/dist/scanners/connections/llm-call-tracer.d.ts +1 -1
  419. package/dist/scanners/connections/llm-call-tracer.d.ts.map +1 -1
  420. package/dist/scanners/connections/llm-call-tracer.js +6 -2
  421. package/dist/scanners/connections/llm-call-tracer.js.map +1 -1
  422. package/dist/scanners/connections/prisma-calls.d.ts +11 -0
  423. package/dist/scanners/connections/prisma-calls.d.ts.map +1 -0
  424. package/dist/scanners/connections/prisma-calls.js +237 -0
  425. package/dist/scanners/connections/prisma-calls.js.map +1 -0
  426. package/dist/scanners/connections/service-calls.d.ts +1 -1
  427. package/dist/scanners/connections/service-calls.d.ts.map +1 -1
  428. package/dist/scanners/connections/service-calls.js +35 -3
  429. package/dist/scanners/connections/service-calls.js.map +1 -1
  430. package/dist/scanners/infrastructure/cron-scanner.d.ts +14 -0
  431. package/dist/scanners/infrastructure/cron-scanner.d.ts.map +1 -0
  432. package/dist/scanners/infrastructure/cron-scanner.js +383 -0
  433. package/dist/scanners/infrastructure/cron-scanner.js.map +1 -0
  434. package/dist/scanners/infrastructure/deploy-scanner.d.ts +11 -0
  435. package/dist/scanners/infrastructure/deploy-scanner.d.ts.map +1 -0
  436. package/dist/scanners/infrastructure/deploy-scanner.js +508 -0
  437. package/dist/scanners/infrastructure/deploy-scanner.js.map +1 -0
  438. package/dist/scanners/infrastructure/env-scanner.d.ts +55 -0
  439. package/dist/scanners/infrastructure/env-scanner.d.ts.map +1 -0
  440. package/dist/scanners/infrastructure/env-scanner.js +431 -0
  441. package/dist/scanners/infrastructure/env-scanner.js.map +1 -0
  442. package/dist/scanners/infrastructure/field-usage-analyzer.d.ts +52 -0
  443. package/dist/scanners/infrastructure/field-usage-analyzer.d.ts.map +1 -0
  444. package/dist/scanners/infrastructure/field-usage-analyzer.js +480 -0
  445. package/dist/scanners/infrastructure/field-usage-analyzer.js.map +1 -0
  446. package/dist/scanners/infrastructure/prisma-parser.d.ts +21 -0
  447. package/dist/scanners/infrastructure/prisma-parser.d.ts.map +1 -0
  448. package/dist/scanners/infrastructure/prisma-parser.js +58 -0
  449. package/dist/scanners/infrastructure/prisma-parser.js.map +1 -0
  450. package/dist/scanners/infrastructure/prisma-scanner.d.ts +30 -0
  451. package/dist/scanners/infrastructure/prisma-scanner.d.ts.map +1 -0
  452. package/dist/scanners/infrastructure/prisma-scanner.js +329 -0
  453. package/dist/scanners/infrastructure/prisma-scanner.js.map +1 -0
  454. package/dist/scanners/infrastructure/queue-scanner.d.ts +14 -0
  455. package/dist/scanners/infrastructure/queue-scanner.d.ts.map +1 -0
  456. package/dist/scanners/infrastructure/queue-scanner.js +455 -0
  457. package/dist/scanners/infrastructure/queue-scanner.js.map +1 -0
  458. package/dist/scanners/infrastructure/typespec-validator.d.ts +50 -0
  459. package/dist/scanners/infrastructure/typespec-validator.d.ts.map +1 -0
  460. package/dist/scanners/infrastructure/typespec-validator.js +407 -0
  461. package/dist/scanners/infrastructure/typespec-validator.js.map +1 -0
  462. package/dist/scanners/packages/swift.d.ts +5 -0
  463. package/dist/scanners/packages/swift.d.ts.map +1 -1
  464. package/dist/scanners/packages/swift.js +23 -0
  465. package/dist/scanners/packages/swift.js.map +1 -1
  466. package/dist/scanners/prompts/detector.d.ts +13 -2
  467. package/dist/scanners/prompts/detector.d.ts.map +1 -1
  468. package/dist/scanners/prompts/detector.js +97 -46
  469. package/dist/scanners/prompts/detector.js.map +1 -1
  470. package/dist/scanners/prompts/index.d.ts +1 -1
  471. package/dist/scanners/prompts/index.d.ts.map +1 -1
  472. package/dist/scanners/prompts/index.js +2 -2
  473. package/dist/scanners/prompts/index.js.map +1 -1
  474. package/dist/scanners/swift/code-scanner.d.ts +1 -1
  475. package/dist/scanners/swift/code-scanner.d.ts.map +1 -1
  476. package/dist/scanners/swift/code-scanner.js +216 -2
  477. package/dist/scanners/swift/code-scanner.js.map +1 -1
  478. package/dist/scanners/swift/swiftui-scanner.d.ts +45 -0
  479. package/dist/scanners/swift/swiftui-scanner.d.ts.map +1 -0
  480. package/dist/scanners/swift/swiftui-scanner.js +606 -0
  481. package/dist/scanners/swift/swiftui-scanner.js.map +1 -0
  482. package/dist/scanners/xcode/pbxproj-parser.d.ts +32 -0
  483. package/dist/scanners/xcode/pbxproj-parser.d.ts.map +1 -0
  484. package/dist/scanners/xcode/pbxproj-parser.js +407 -0
  485. package/dist/scanners/xcode/pbxproj-parser.js.map +1 -0
  486. package/dist/scanners/xcode/storyboard-scanner.d.ts +13 -0
  487. package/dist/scanners/xcode/storyboard-scanner.d.ts.map +1 -0
  488. package/dist/scanners/xcode/storyboard-scanner.js +236 -0
  489. package/dist/scanners/xcode/storyboard-scanner.js.map +1 -0
  490. package/dist/storage/markdown-view.d.ts +49 -0
  491. package/dist/storage/markdown-view.d.ts.map +1 -0
  492. package/dist/storage/markdown-view.js +233 -0
  493. package/dist/storage/markdown-view.js.map +1 -0
  494. package/dist/storage.d.ts +225 -9
  495. package/dist/storage.d.ts.map +1 -1
  496. package/dist/storage.js +945 -86
  497. package/dist/storage.js.map +1 -1
  498. package/dist/subgraph.d.ts +30 -0
  499. package/dist/subgraph.d.ts.map +1 -0
  500. package/dist/subgraph.js +106 -0
  501. package/dist/subgraph.js.map +1 -0
  502. package/dist/temporal/git-store.d.ts +65 -0
  503. package/dist/temporal/git-store.d.ts.map +1 -0
  504. package/dist/temporal/git-store.js +166 -0
  505. package/dist/temporal/git-store.js.map +1 -0
  506. package/dist/trace.d.ts +38 -0
  507. package/dist/trace.d.ts.map +1 -0
  508. package/dist/trace.js +292 -0
  509. package/dist/trace.js.map +1 -0
  510. package/dist/types.d.ts +322 -2
  511. package/dist/types.d.ts.map +1 -1
  512. package/dist/types.js +53 -0
  513. package/dist/types.js.map +1 -1
  514. package/hooks/hooks.json +1 -55
  515. package/hooks/mark-dirty.sh +20 -0
  516. package/hooks/post-bash-suggest.sh +30 -0
  517. package/hooks/post-edit-suggest.sh +29 -0
  518. package/hooks/pre-edit-warn.sh +28 -0
  519. package/hooks/session-start.sh +19 -0
  520. package/hooks/stop-suggest.sh +49 -0
  521. package/package.json +30 -11
  522. package/scripts/install-codex-plugin.sh +119 -0
  523. package/scripts/install-plugin.sh +29 -24
  524. package/skills/architecture-export/SKILL.md +79 -0
  525. package/skills/architecture-scan/SKILL.md +64 -0
  526. package/skills/code-review/SKILL.md +368 -0
  527. package/skills/impact-analysis/SKILL.md +80 -0
  528. package/skills/infrastructure-scanning.md +42 -0
  529. package/skills/navgator-setup/SKILL.md +108 -0
  530. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  531. package/web/.next/standalone/web/.next/app-path-routes-manifest.json +3 -0
  532. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  533. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  534. package/web/.next/standalone/web/.next/required-server-files.json +4 -4
  535. package/web/.next/standalone/web/.next/routes-manifest.json +18 -0
  536. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  537. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  538. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  539. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  540. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  541. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  542. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  543. package/web/.next/standalone/web/.next/server/app/_not-found/page/next-font-manifest.json +2 -2
  544. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  545. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  546. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +3 -3
  547. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  548. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  549. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  550. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  551. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  552. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +3 -3
  553. package/web/.next/standalone/web/.next/server/app/api/prompts/route.js.nft.json +1 -1
  554. package/web/.next/standalone/web/.next/server/app/api/rules/route/app-paths-manifest.json +3 -0
  555. package/web/.next/standalone/web/.next/server/app/api/rules/route/build-manifest.json +11 -0
  556. package/web/.next/standalone/web/.next/server/app/api/rules/route/server-reference-manifest.json +4 -0
  557. package/web/.next/standalone/web/.next/server/app/api/rules/route.js +6 -0
  558. package/web/.next/standalone/web/.next/server/app/api/rules/route.js.map +5 -0
  559. package/web/.next/standalone/web/.next/server/app/api/rules/route.js.nft.json +1 -0
  560. package/web/.next/standalone/web/.next/server/app/api/rules/route_client-reference-manifest.js +2 -0
  561. package/web/.next/standalone/web/.next/server/app/api/subgraph/route/app-paths-manifest.json +3 -0
  562. package/web/.next/standalone/web/.next/server/app/api/subgraph/route/build-manifest.json +11 -0
  563. package/web/.next/standalone/web/.next/server/app/api/subgraph/route/server-reference-manifest.json +4 -0
  564. package/web/.next/standalone/web/.next/server/app/api/subgraph/route.js +6 -0
  565. package/web/.next/standalone/web/.next/server/app/api/subgraph/route.js.map +5 -0
  566. package/web/.next/standalone/web/.next/server/app/api/subgraph/route.js.nft.json +1 -0
  567. package/web/.next/standalone/web/.next/server/app/api/subgraph/route_client-reference-manifest.js +2 -0
  568. package/web/.next/standalone/web/.next/server/app/api/trace/route/app-paths-manifest.json +3 -0
  569. package/web/.next/standalone/web/.next/server/app/api/trace/route/build-manifest.json +11 -0
  570. package/web/.next/standalone/web/.next/server/app/api/trace/route/server-reference-manifest.json +4 -0
  571. package/web/.next/standalone/web/.next/server/app/api/trace/route.js +6 -0
  572. package/web/.next/standalone/web/.next/server/app/api/trace/route.js.map +5 -0
  573. package/web/.next/standalone/web/.next/server/app/api/trace/route.js.nft.json +1 -0
  574. package/web/.next/standalone/web/.next/server/app/api/trace/route_client-reference-manifest.js +2 -0
  575. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  576. package/web/.next/standalone/web/.next/server/app/index.rsc +6 -6
  577. package/web/.next/standalone/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  578. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +6 -6
  579. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  580. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +3 -3
  581. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +5 -5
  582. package/web/.next/standalone/web/.next/server/app/page/next-font-manifest.json +2 -2
  583. package/web/.next/standalone/web/.next/server/app/page_client-reference-manifest.js +1 -1
  584. package/web/.next/standalone/web/.next/server/app-paths-manifest.json +3 -0
  585. package/web/.next/standalone/web/.next/server/chunks/2374f_next_dist_esm_build_templates_app-route_0bb4e66a.js +1 -1
  586. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__006b837d._.js +1 -1
  587. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__0426efe8._.js +3 -0
  588. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__2e09fec9._.js +1 -1
  589. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__38d0390f._.js +1 -1
  590. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__594bcf20._.js +1 -1
  591. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__b888fadf._.js +1 -1
  592. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__cd5f36ce._.js +3 -0
  593. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__ee6fc95f._.js +3 -0
  594. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__fa2ec862._.js +1 -1
  595. package/web/.next/standalone/web/.next/server/chunks/ssr/web_171de0df._.js +9 -4
  596. package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_rules_route_actions_3de01bd5.js +3 -0
  597. package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_subgraph_route_actions_d8b5a63f.js +3 -0
  598. package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_trace_route_actions_b0703ae2.js +3 -0
  599. package/web/.next/standalone/web/.next/server/next-font-manifest.js +1 -1
  600. package/web/.next/standalone/web/.next/server/next-font-manifest.json +4 -4
  601. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  602. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  603. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  604. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  605. package/web/.next/standalone/web/.next/static/chunks/22a09ecf6ba35cfd.js +17 -0
  606. package/web/.next/standalone/web/.next/static/chunks/9857ba86ce4e82d8.css +2 -0
  607. package/web/.next/standalone/web/.next/static/chunks/f899547f99ef4b76.css +1 -0
  608. package/web/.next/standalone/web/.next/static/media/4fa387ec64143e14-s.c36e1862.woff2 +0 -0
  609. package/web/.next/standalone/web/.next/static/media/53b9e256198e5412-s.853d50a3.woff2 +0 -0
  610. package/web/.next/standalone/web/.next/static/media/5ce348bf30bf5439-s.ebceb24d.woff2 +0 -0
  611. package/web/.next/standalone/web/.next/static/media/6306c77e7c8268e4-s.ff4a2084.woff2 +0 -0
  612. package/web/.next/standalone/web/.next/static/media/7178b3e590c64307-s.55554cd0.woff2 +0 -0
  613. package/web/.next/standalone/web/.next/static/media/797e433ab948586e-s.p.479bea2b.woff2 +0 -0
  614. package/web/.next/standalone/web/.next/static/media/7d817b4c03b0c5f1-s.f377b9c4.woff2 +0 -0
  615. package/web/.next/standalone/web/.next/static/media/8a480f0b521d4e75-s.ea323500.woff2 +0 -0
  616. package/web/.next/standalone/web/.next/static/media/bbc41e54d2fcbd21-s.d1207556.woff2 +0 -0
  617. package/web/.next/standalone/web/.next/static/media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2 +0 -0
  618. package/web/.next/standalone/web/.next/static/media/fef07dbb0973bf53-s.518e079e.woff2 +0 -0
  619. package/web/.next/standalone/web/app/api/components/route.ts +1 -1
  620. package/web/.next/standalone/web/app/api/connections/route.ts +3 -1
  621. package/web/.next/standalone/web/app/api/graph/route.ts +3 -3
  622. package/web/.next/standalone/web/app/api/projects/route.ts +1 -1
  623. package/web/.next/standalone/web/app/api/prompts/route.ts +2 -2
  624. package/web/.next/standalone/web/app/api/rules/route.ts +213 -0
  625. package/web/.next/standalone/web/app/api/settings/route.ts +2 -2
  626. package/web/.next/standalone/web/app/api/status/route.ts +1 -1
  627. package/web/.next/standalone/web/app/api/subgraph/route.ts +267 -0
  628. package/web/.next/standalone/web/app/api/trace/route.ts +321 -0
  629. package/web/.next/standalone/web/app/page.tsx +9 -1
  630. package/web/.next/standalone/web/components/connections-panel.tsx +23 -6
  631. package/web/.next/standalone/web/components/coverage-panel.tsx +309 -0
  632. package/web/.next/standalone/web/components/rules-panel.tsx +156 -0
  633. package/web/.next/standalone/web/components/sidebar.tsx +8 -0
  634. package/web/.next/standalone/web/components/status-overview.tsx +24 -1
  635. package/web/.next/standalone/web/components/subgraph-panel.tsx +382 -0
  636. package/web/.next/standalone/web/components/trace-panel.tsx +325 -0
  637. package/web/.next/standalone/web/lib/hooks/index.ts +3 -0
  638. package/web/.next/standalone/web/lib/hooks/use-coverage.ts +68 -0
  639. package/web/.next/standalone/web/lib/hooks/use-subgraph.ts +85 -0
  640. package/web/.next/standalone/web/lib/hooks/use-trace.ts +84 -0
  641. package/web/.next/standalone/web/lib/types.ts +108 -0
  642. package/web/.next/standalone/web/package-lock.json +218 -0
  643. package/web/.next/standalone/web/package.json +4 -2
  644. package/web/.next/standalone/web/server.js +1 -1
  645. package/web/.next/static/chunks/22a09ecf6ba35cfd.js +17 -0
  646. package/web/.next/static/chunks/9857ba86ce4e82d8.css +2 -0
  647. package/web/.next/static/chunks/f899547f99ef4b76.css +1 -0
  648. package/web/.next/static/media/4fa387ec64143e14-s.c36e1862.woff2 +0 -0
  649. package/web/.next/static/media/53b9e256198e5412-s.853d50a3.woff2 +0 -0
  650. package/web/.next/static/media/5ce348bf30bf5439-s.ebceb24d.woff2 +0 -0
  651. package/web/.next/static/media/6306c77e7c8268e4-s.ff4a2084.woff2 +0 -0
  652. package/web/.next/static/media/7178b3e590c64307-s.55554cd0.woff2 +0 -0
  653. package/web/.next/static/media/797e433ab948586e-s.p.479bea2b.woff2 +0 -0
  654. package/web/.next/static/media/7d817b4c03b0c5f1-s.f377b9c4.woff2 +0 -0
  655. package/web/.next/static/media/8a480f0b521d4e75-s.ea323500.woff2 +0 -0
  656. package/web/.next/static/media/bbc41e54d2fcbd21-s.d1207556.woff2 +0 -0
  657. package/web/.next/static/media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2 +0 -0
  658. package/web/.next/static/media/fef07dbb0973bf53-s.518e079e.woff2 +0 -0
  659. package/skills/check/SKILL.md +0 -64
  660. package/skills/connections/SKILL.md +0 -54
  661. package/skills/diagram/SKILL.md +0 -64
  662. package/skills/export/SKILL.md +0 -49
  663. package/skills/impact/SKILL.md +0 -58
  664. package/skills/install/SKILL.md +0 -94
  665. package/skills/scan/SKILL.md +0 -75
  666. package/skills/status/SKILL.md +0 -37
  667. package/skills/ui/SKILL.md +0 -43
  668. package/skills/update/SKILL.md +0 -43
  669. package/web/.next/standalone/web/.next/static/chunks/8a80e7184ad3a13f.css +0 -2
  670. package/web/.next/standalone/web/.next/static/chunks/c056475f5f4424b6.css +0 -1
  671. package/web/.next/standalone/web/.next/static/chunks/cb3513192b63e480.js +0 -12
  672. package/web/.next/standalone/web/.next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
  673. package/web/.next/standalone/web/.next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
  674. package/web/.next/standalone/web/.next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
  675. package/web/.next/standalone/web/.next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
  676. package/web/.next/standalone/web/.next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
  677. package/web/.next/standalone/web/.next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
  678. package/web/.next/static/chunks/8a80e7184ad3a13f.css +0 -2
  679. package/web/.next/static/chunks/c056475f5f4424b6.css +0 -1
  680. package/web/.next/static/chunks/cb3513192b63e480.js +0 -12
  681. package/web/.next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
  682. package/web/.next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
  683. package/web/.next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
  684. package/web/.next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
  685. package/web/.next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
  686. package/web/.next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
  687. /package/web/.next/standalone/web/.next/static/{P-ZMQO7_Wnj487ks3guqa → qZVrJ4kmwXfw4Ikgj1oXR}/_buildManifest.js +0 -0
  688. /package/web/.next/standalone/web/.next/static/{P-ZMQO7_Wnj487ks3guqa → qZVrJ4kmwXfw4Ikgj1oXR}/_clientMiddlewareManifest.json +0 -0
  689. /package/web/.next/standalone/web/.next/static/{P-ZMQO7_Wnj487ks3guqa → qZVrJ4kmwXfw4Ikgj1oXR}/_ssgManifest.js +0 -0
  690. /package/web/.next/static/{P-ZMQO7_Wnj487ks3guqa → qZVrJ4kmwXfw4Ikgj1oXR}/_buildManifest.js +0 -0
  691. /package/web/.next/static/{P-ZMQO7_Wnj487ks3guqa → qZVrJ4kmwXfw4Ikgj1oXR}/_clientMiddlewareManifest.json +0 -0
  692. /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 { getConfig, getComponentsPath, getConnectionsPath, getIndexPath, getGraphPath, getSnapshotsPath, getHashesPath, getSummaryPath, getSummaryFullPath, getFileMapPath, getPromptsPath, ensureStorageDirectories, isValidComponentId, isValidConnectionId, } from './config.js';
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 fs.promises.writeFile(filePath, JSON.stringify(component, null, 2), 'utf-8');
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 (parallelized for efficiency)
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
- if (!fs.existsSync(componentsPath)) {
54
- return [];
55
- }
56
- const files = await fs.promises.readdir(componentsPath);
57
- const jsonFiles = files.filter((f) => f.endsWith('.json'));
58
- // Parallelize reads
59
- const results = await Promise.all(jsonFiles.map(async (file) => {
60
- try {
61
- const filePath = path.join(componentsPath, file);
62
- const content = await fs.promises.readFile(filePath, 'utf-8');
63
- return JSON.parse(content);
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
- catch {
66
- return null;
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
- return results.filter((c) => c !== null);
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 fs.promises.writeFile(filePath, JSON.stringify(connection, null, 2), 'utf-8');
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 (parallelized for efficiency)
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
- if (!fs.existsSync(connectionsPath)) {
132
- return [];
133
- }
134
- const files = await fs.promises.readdir(connectionsPath);
135
- const jsonFiles = files.filter((f) => f.endsWith('.json'));
136
- // Parallelize reads
137
- const results = await Promise.all(jsonFiles.map(async (file) => {
138
- try {
139
- const filePath = path.join(connectionsPath, file);
140
- const content = await fs.promises.readFile(filePath, 'utf-8');
141
- return JSON.parse(content);
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
- catch {
144
- return null;
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
- return results.filter((c) => c !== null);
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
- const components = await loadAllComponents(cfg, projectRoot);
174
- const connections = await loadAllConnections(cfg, projectRoot);
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 fs.promises.writeFile(indexPath, JSON.stringify(index, null, 2), 'utf-8');
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
- return JSON.parse(content);
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
- const components = await loadAllComponents(cfg, projectRoot);
282
- const connections = await loadAllConnections(cfg, projectRoot);
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 fs.promises.writeFile(graphPath, JSON.stringify(graph, null, 2), 'utf-8');
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
- const components = await loadAllComponents(cfg, root);
321
- const connections = await loadAllConnections(cfg, root);
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 fs.promises.writeFile(fileMapPath, JSON.stringify(fileMap, null, 2), 'utf-8');
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
- await fs.promises.writeFile(promptsPath, JSON.stringify(promptData, null, 2), 'utf-8');
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
- const components = await loadAllComponents(cfg, root);
376
- const connections = await loadAllConnections(cfg, root);
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
- // Delta compare with previous summary
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 (fs.existsSync(summaryPath)) {
486
- try {
487
- const prev = await fs.promises.readFile(summaryPath, 'utf-8');
488
- const prevNames = new Set();
489
- for (const match of prev.matchAll(/^- \*\*(.+?)\*\*/gm)) {
490
- prevNames.add(match[1]);
491
- }
492
- const currentNames = new Set(components.map((c) => c.name));
493
- const added = components.filter((c) => !prevNames.has(c.name));
494
- const removed = [...prevNames].filter((n) => !currentNames.has(n));
495
- if (added.length > 0 || removed.length > 0) {
496
- lines.push('## Changes Since Last Scan');
497
- for (const c of added) {
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
- for (const name of removed) {
501
- lines.push(`- Removed: \`${name}\``);
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
- catch {
507
- // First scan or parse error — skip delta
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 SUMMARY_FULL.md
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: `SUMMARY_FULL.md`');
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 SUMMARY_FULL.md)`);
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 SUMMARY_FULL.md) | |`);
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**: `SUMMARY_FULL.md`');
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 fs.promises.writeFile(filePath, JSON.stringify(component, null, 2), 'utf-8');
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 fs.promises.writeFile(filePath, JSON.stringify(connection, null, 2), 'utf-8');
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 fs.promises.writeFile(hashesPath, JSON.stringify(navHashes, null, 2), 'utf-8');
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
  */