openlore 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (634) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +268 -0
  3. package/dist/api/analyze.d.ts +17 -0
  4. package/dist/api/analyze.d.ts.map +1 -0
  5. package/dist/api/analyze.js +143 -0
  6. package/dist/api/analyze.js.map +1 -0
  7. package/dist/api/audit.d.ts +10 -0
  8. package/dist/api/audit.d.ts.map +1 -0
  9. package/dist/api/audit.js +117 -0
  10. package/dist/api/audit.js.map +1 -0
  11. package/dist/api/decisions.d.ts +55 -0
  12. package/dist/api/decisions.d.ts.map +1 -0
  13. package/dist/api/decisions.js +157 -0
  14. package/dist/api/decisions.js.map +1 -0
  15. package/dist/api/drift.d.ts +21 -0
  16. package/dist/api/drift.d.ts.map +1 -0
  17. package/dist/api/drift.js +152 -0
  18. package/dist/api/drift.js.map +1 -0
  19. package/dist/api/generate.d.ts +18 -0
  20. package/dist/api/generate.d.ts.map +1 -0
  21. package/dist/api/generate.js +259 -0
  22. package/dist/api/generate.js.map +1 -0
  23. package/dist/api/index.d.ts +41 -0
  24. package/dist/api/index.d.ts.map +1 -0
  25. package/dist/api/index.js +34 -0
  26. package/dist/api/index.js.map +1 -0
  27. package/dist/api/init.d.ts +18 -0
  28. package/dist/api/init.d.ts.map +1 -0
  29. package/dist/api/init.js +83 -0
  30. package/dist/api/init.js.map +1 -0
  31. package/dist/api/run.d.ts +19 -0
  32. package/dist/api/run.d.ts.map +1 -0
  33. package/dist/api/run.js +312 -0
  34. package/dist/api/run.js.map +1 -0
  35. package/dist/api/specs.d.ts +49 -0
  36. package/dist/api/specs.d.ts.map +1 -0
  37. package/dist/api/specs.js +137 -0
  38. package/dist/api/specs.js.map +1 -0
  39. package/dist/api/types.d.ts +201 -0
  40. package/dist/api/types.d.ts.map +1 -0
  41. package/dist/api/types.js +9 -0
  42. package/dist/api/types.js.map +1 -0
  43. package/dist/api/verify.d.ts +20 -0
  44. package/dist/api/verify.d.ts.map +1 -0
  45. package/dist/api/verify.js +117 -0
  46. package/dist/api/verify.js.map +1 -0
  47. package/dist/cli/commands/analyze.d.ts +30 -0
  48. package/dist/cli/commands/analyze.d.ts.map +1 -0
  49. package/dist/cli/commands/analyze.js +683 -0
  50. package/dist/cli/commands/analyze.js.map +1 -0
  51. package/dist/cli/commands/audit.d.ts +9 -0
  52. package/dist/cli/commands/audit.d.ts.map +1 -0
  53. package/dist/cli/commands/audit.js +98 -0
  54. package/dist/cli/commands/audit.js.map +1 -0
  55. package/dist/cli/commands/decisions.d.ts +16 -0
  56. package/dist/cli/commands/decisions.d.ts.map +1 -0
  57. package/dist/cli/commands/decisions.js +864 -0
  58. package/dist/cli/commands/decisions.js.map +1 -0
  59. package/dist/cli/commands/digest.d.ts +9 -0
  60. package/dist/cli/commands/digest.d.ts.map +1 -0
  61. package/dist/cli/commands/digest.js +61 -0
  62. package/dist/cli/commands/digest.js.map +1 -0
  63. package/dist/cli/commands/doctor.d.ts +9 -0
  64. package/dist/cli/commands/doctor.d.ts.map +1 -0
  65. package/dist/cli/commands/doctor.js +398 -0
  66. package/dist/cli/commands/doctor.js.map +1 -0
  67. package/dist/cli/commands/drift.d.ts +9 -0
  68. package/dist/cli/commands/drift.d.ts.map +1 -0
  69. package/dist/cli/commands/drift.js +550 -0
  70. package/dist/cli/commands/drift.js.map +1 -0
  71. package/dist/cli/commands/generate.d.ts +9 -0
  72. package/dist/cli/commands/generate.d.ts.map +1 -0
  73. package/dist/cli/commands/generate.js +565 -0
  74. package/dist/cli/commands/generate.js.map +1 -0
  75. package/dist/cli/commands/init.d.ts +9 -0
  76. package/dist/cli/commands/init.d.ts.map +1 -0
  77. package/dist/cli/commands/init.js +173 -0
  78. package/dist/cli/commands/init.js.map +1 -0
  79. package/dist/cli/commands/mcp.d.ts +2235 -0
  80. package/dist/cli/commands/mcp.d.ts.map +1 -0
  81. package/dist/cli/commands/mcp.js +1384 -0
  82. package/dist/cli/commands/mcp.js.map +1 -0
  83. package/dist/cli/commands/refresh-stories.d.ts +10 -0
  84. package/dist/cli/commands/refresh-stories.d.ts.map +1 -0
  85. package/dist/cli/commands/refresh-stories.js +314 -0
  86. package/dist/cli/commands/refresh-stories.js.map +1 -0
  87. package/dist/cli/commands/run.d.ts +9 -0
  88. package/dist/cli/commands/run.d.ts.map +1 -0
  89. package/dist/cli/commands/run.js +459 -0
  90. package/dist/cli/commands/run.js.map +1 -0
  91. package/dist/cli/commands/setup.d.ts +19 -0
  92. package/dist/cli/commands/setup.d.ts.map +1 -0
  93. package/dist/cli/commands/setup.js +355 -0
  94. package/dist/cli/commands/setup.js.map +1 -0
  95. package/dist/cli/commands/test.d.ts +22 -0
  96. package/dist/cli/commands/test.d.ts.map +1 -0
  97. package/dist/cli/commands/test.js +180 -0
  98. package/dist/cli/commands/test.js.map +1 -0
  99. package/dist/cli/commands/verify.d.ts +9 -0
  100. package/dist/cli/commands/verify.d.ts.map +1 -0
  101. package/dist/cli/commands/verify.js +383 -0
  102. package/dist/cli/commands/verify.js.map +1 -0
  103. package/dist/cli/commands/view.d.ts +13 -0
  104. package/dist/cli/commands/view.d.ts.map +1 -0
  105. package/dist/cli/commands/view.js +547 -0
  106. package/dist/cli/commands/view.js.map +1 -0
  107. package/dist/cli/index.d.ts +9 -0
  108. package/dist/cli/index.d.ts.map +1 -0
  109. package/dist/cli/index.js +118 -0
  110. package/dist/cli/index.js.map +1 -0
  111. package/dist/cli/tui-approval.d.ts +11 -0
  112. package/dist/cli/tui-approval.d.ts.map +1 -0
  113. package/dist/cli/tui-approval.js +129 -0
  114. package/dist/cli/tui-approval.js.map +1 -0
  115. package/dist/constants.d.ts +314 -0
  116. package/dist/constants.d.ts.map +1 -0
  117. package/dist/constants.js +382 -0
  118. package/dist/constants.js.map +1 -0
  119. package/dist/core/analyzer/ai-config-generator.d.ts +54 -0
  120. package/dist/core/analyzer/ai-config-generator.d.ts.map +1 -0
  121. package/dist/core/analyzer/ai-config-generator.js +98 -0
  122. package/dist/core/analyzer/ai-config-generator.js.map +1 -0
  123. package/dist/core/analyzer/architecture-writer.d.ts +67 -0
  124. package/dist/core/analyzer/architecture-writer.d.ts.map +1 -0
  125. package/dist/core/analyzer/architecture-writer.js +209 -0
  126. package/dist/core/analyzer/architecture-writer.js.map +1 -0
  127. package/dist/core/analyzer/artifact-generator.d.ts +261 -0
  128. package/dist/core/analyzer/artifact-generator.d.ts.map +1 -0
  129. package/dist/core/analyzer/artifact-generator.js +909 -0
  130. package/dist/core/analyzer/artifact-generator.js.map +1 -0
  131. package/dist/core/analyzer/ast-chunker.d.ts +24 -0
  132. package/dist/core/analyzer/ast-chunker.d.ts.map +1 -0
  133. package/dist/core/analyzer/ast-chunker.js +198 -0
  134. package/dist/core/analyzer/ast-chunker.js.map +1 -0
  135. package/dist/core/analyzer/call-graph.d.ts +162 -0
  136. package/dist/core/analyzer/call-graph.d.ts.map +1 -0
  137. package/dist/core/analyzer/call-graph.js +2040 -0
  138. package/dist/core/analyzer/call-graph.js.map +1 -0
  139. package/dist/core/analyzer/code-shaper.d.ts +33 -0
  140. package/dist/core/analyzer/code-shaper.d.ts.map +1 -0
  141. package/dist/core/analyzer/code-shaper.js +154 -0
  142. package/dist/core/analyzer/code-shaper.js.map +1 -0
  143. package/dist/core/analyzer/codebase-digest.d.ts +40 -0
  144. package/dist/core/analyzer/codebase-digest.d.ts.map +1 -0
  145. package/dist/core/analyzer/codebase-digest.js +195 -0
  146. package/dist/core/analyzer/codebase-digest.js.map +1 -0
  147. package/dist/core/analyzer/cpp-header-resolver.d.ts +30 -0
  148. package/dist/core/analyzer/cpp-header-resolver.d.ts.map +1 -0
  149. package/dist/core/analyzer/cpp-header-resolver.js +71 -0
  150. package/dist/core/analyzer/cpp-header-resolver.js.map +1 -0
  151. package/dist/core/analyzer/dependency-graph.d.ts +230 -0
  152. package/dist/core/analyzer/dependency-graph.d.ts.map +1 -0
  153. package/dist/core/analyzer/dependency-graph.js +752 -0
  154. package/dist/core/analyzer/dependency-graph.js.map +1 -0
  155. package/dist/core/analyzer/duplicate-detector.d.ts +52 -0
  156. package/dist/core/analyzer/duplicate-detector.d.ts.map +1 -0
  157. package/dist/core/analyzer/duplicate-detector.js +289 -0
  158. package/dist/core/analyzer/duplicate-detector.js.map +1 -0
  159. package/dist/core/analyzer/embedding-service.d.ts +56 -0
  160. package/dist/core/analyzer/embedding-service.d.ts.map +1 -0
  161. package/dist/core/analyzer/embedding-service.js +118 -0
  162. package/dist/core/analyzer/embedding-service.js.map +1 -0
  163. package/dist/core/analyzer/env-extractor.d.ts +33 -0
  164. package/dist/core/analyzer/env-extractor.d.ts.map +1 -0
  165. package/dist/core/analyzer/env-extractor.js +196 -0
  166. package/dist/core/analyzer/env-extractor.js.map +1 -0
  167. package/dist/core/analyzer/external-packages.d.ts +20 -0
  168. package/dist/core/analyzer/external-packages.d.ts.map +1 -0
  169. package/dist/core/analyzer/external-packages.js +175 -0
  170. package/dist/core/analyzer/external-packages.js.map +1 -0
  171. package/dist/core/analyzer/file-walker.d.ts +78 -0
  172. package/dist/core/analyzer/file-walker.d.ts.map +1 -0
  173. package/dist/core/analyzer/file-walker.js +532 -0
  174. package/dist/core/analyzer/file-walker.js.map +1 -0
  175. package/dist/core/analyzer/function-registry-trie.d.ts +21 -0
  176. package/dist/core/analyzer/function-registry-trie.d.ts.map +1 -0
  177. package/dist/core/analyzer/function-registry-trie.js +39 -0
  178. package/dist/core/analyzer/function-registry-trie.js.map +1 -0
  179. package/dist/core/analyzer/http-route-parser.d.ts +152 -0
  180. package/dist/core/analyzer/http-route-parser.d.ts.map +1 -0
  181. package/dist/core/analyzer/http-route-parser.js +971 -0
  182. package/dist/core/analyzer/http-route-parser.js.map +1 -0
  183. package/dist/core/analyzer/import-parser.d.ts +100 -0
  184. package/dist/core/analyzer/import-parser.d.ts.map +1 -0
  185. package/dist/core/analyzer/import-parser.js +952 -0
  186. package/dist/core/analyzer/import-parser.js.map +1 -0
  187. package/dist/core/analyzer/import-resolver-bridge.d.ts +25 -0
  188. package/dist/core/analyzer/import-resolver-bridge.d.ts.map +1 -0
  189. package/dist/core/analyzer/import-resolver-bridge.js +99 -0
  190. package/dist/core/analyzer/import-resolver-bridge.js.map +1 -0
  191. package/dist/core/analyzer/index.d.ts +10 -0
  192. package/dist/core/analyzer/index.d.ts.map +1 -0
  193. package/dist/core/analyzer/index.js +10 -0
  194. package/dist/core/analyzer/index.js.map +1 -0
  195. package/dist/core/analyzer/middleware-extractor.d.ts +29 -0
  196. package/dist/core/analyzer/middleware-extractor.d.ts.map +1 -0
  197. package/dist/core/analyzer/middleware-extractor.js +195 -0
  198. package/dist/core/analyzer/middleware-extractor.js.map +1 -0
  199. package/dist/core/analyzer/refactor-analyzer.d.ts +83 -0
  200. package/dist/core/analyzer/refactor-analyzer.d.ts.map +1 -0
  201. package/dist/core/analyzer/refactor-analyzer.js +351 -0
  202. package/dist/core/analyzer/refactor-analyzer.js.map +1 -0
  203. package/dist/core/analyzer/repository-mapper.d.ts +150 -0
  204. package/dist/core/analyzer/repository-mapper.d.ts.map +1 -0
  205. package/dist/core/analyzer/repository-mapper.js +740 -0
  206. package/dist/core/analyzer/repository-mapper.js.map +1 -0
  207. package/dist/core/analyzer/schema-extractor.d.ts +41 -0
  208. package/dist/core/analyzer/schema-extractor.d.ts.map +1 -0
  209. package/dist/core/analyzer/schema-extractor.js +229 -0
  210. package/dist/core/analyzer/schema-extractor.js.map +1 -0
  211. package/dist/core/analyzer/signature-extractor.d.ts +31 -0
  212. package/dist/core/analyzer/signature-extractor.d.ts.map +1 -0
  213. package/dist/core/analyzer/signature-extractor.js +675 -0
  214. package/dist/core/analyzer/signature-extractor.js.map +1 -0
  215. package/dist/core/analyzer/significance-scorer.d.ts +79 -0
  216. package/dist/core/analyzer/significance-scorer.d.ts.map +1 -0
  217. package/dist/core/analyzer/significance-scorer.js +407 -0
  218. package/dist/core/analyzer/significance-scorer.js.map +1 -0
  219. package/dist/core/analyzer/spec-snapshot-generator.d.ts +17 -0
  220. package/dist/core/analyzer/spec-snapshot-generator.d.ts.map +1 -0
  221. package/dist/core/analyzer/spec-snapshot-generator.js +201 -0
  222. package/dist/core/analyzer/spec-snapshot-generator.js.map +1 -0
  223. package/dist/core/analyzer/spec-vector-index.d.ts +68 -0
  224. package/dist/core/analyzer/spec-vector-index.d.ts.map +1 -0
  225. package/dist/core/analyzer/spec-vector-index.js +340 -0
  226. package/dist/core/analyzer/spec-vector-index.js.map +1 -0
  227. package/dist/core/analyzer/subgraph-extractor.d.ts +51 -0
  228. package/dist/core/analyzer/subgraph-extractor.d.ts.map +1 -0
  229. package/dist/core/analyzer/subgraph-extractor.js +147 -0
  230. package/dist/core/analyzer/subgraph-extractor.js.map +1 -0
  231. package/dist/core/analyzer/type-inference-engine.d.ts +23 -0
  232. package/dist/core/analyzer/type-inference-engine.d.ts.map +1 -0
  233. package/dist/core/analyzer/type-inference-engine.js +130 -0
  234. package/dist/core/analyzer/type-inference-engine.js.map +1 -0
  235. package/dist/core/analyzer/ui-component-extractor.d.ts +43 -0
  236. package/dist/core/analyzer/ui-component-extractor.d.ts.map +1 -0
  237. package/dist/core/analyzer/ui-component-extractor.js +245 -0
  238. package/dist/core/analyzer/ui-component-extractor.js.map +1 -0
  239. package/dist/core/analyzer/unified-search.d.ts +116 -0
  240. package/dist/core/analyzer/unified-search.d.ts.map +1 -0
  241. package/dist/core/analyzer/unified-search.js +231 -0
  242. package/dist/core/analyzer/unified-search.js.map +1 -0
  243. package/dist/core/analyzer/vector-index.d.ts +92 -0
  244. package/dist/core/analyzer/vector-index.d.ts.map +1 -0
  245. package/dist/core/analyzer/vector-index.js +451 -0
  246. package/dist/core/analyzer/vector-index.js.map +1 -0
  247. package/dist/core/decisions/consolidator.d.ts +14 -0
  248. package/dist/core/decisions/consolidator.d.ts.map +1 -0
  249. package/dist/core/decisions/consolidator.js +169 -0
  250. package/dist/core/decisions/consolidator.js.map +1 -0
  251. package/dist/core/decisions/extractor.d.ts +26 -0
  252. package/dist/core/decisions/extractor.d.ts.map +1 -0
  253. package/dist/core/decisions/extractor.js +156 -0
  254. package/dist/core/decisions/extractor.js.map +1 -0
  255. package/dist/core/decisions/index.d.ts +19 -0
  256. package/dist/core/decisions/index.d.ts.map +1 -0
  257. package/dist/core/decisions/index.js +16 -0
  258. package/dist/core/decisions/index.js.map +1 -0
  259. package/dist/core/decisions/store.d.ts +36 -0
  260. package/dist/core/decisions/store.d.ts.map +1 -0
  261. package/dist/core/decisions/store.js +109 -0
  262. package/dist/core/decisions/store.js.map +1 -0
  263. package/dist/core/decisions/syncer.d.ts +27 -0
  264. package/dist/core/decisions/syncer.d.ts.map +1 -0
  265. package/dist/core/decisions/syncer.js +214 -0
  266. package/dist/core/decisions/syncer.js.map +1 -0
  267. package/dist/core/decisions/verifier.d.ts +20 -0
  268. package/dist/core/decisions/verifier.d.ts.map +1 -0
  269. package/dist/core/decisions/verifier.js +115 -0
  270. package/dist/core/decisions/verifier.js.map +1 -0
  271. package/dist/core/digest/digest-generator.d.ts +29 -0
  272. package/dist/core/digest/digest-generator.d.ts.map +1 -0
  273. package/dist/core/digest/digest-generator.js +181 -0
  274. package/dist/core/digest/digest-generator.js.map +1 -0
  275. package/dist/core/drift/drift-detector.d.ts +102 -0
  276. package/dist/core/drift/drift-detector.d.ts.map +1 -0
  277. package/dist/core/drift/drift-detector.js +598 -0
  278. package/dist/core/drift/drift-detector.js.map +1 -0
  279. package/dist/core/drift/git-diff.d.ts +60 -0
  280. package/dist/core/drift/git-diff.d.ts.map +1 -0
  281. package/dist/core/drift/git-diff.js +383 -0
  282. package/dist/core/drift/git-diff.js.map +1 -0
  283. package/dist/core/drift/index.d.ts +12 -0
  284. package/dist/core/drift/index.d.ts.map +1 -0
  285. package/dist/core/drift/index.js +9 -0
  286. package/dist/core/drift/index.js.map +1 -0
  287. package/dist/core/drift/spec-mapper.d.ts +73 -0
  288. package/dist/core/drift/spec-mapper.d.ts.map +1 -0
  289. package/dist/core/drift/spec-mapper.js +353 -0
  290. package/dist/core/drift/spec-mapper.js.map +1 -0
  291. package/dist/core/drift/test-suggester.d.ts +18 -0
  292. package/dist/core/drift/test-suggester.d.ts.map +1 -0
  293. package/dist/core/drift/test-suggester.js +107 -0
  294. package/dist/core/drift/test-suggester.js.map +1 -0
  295. package/dist/core/generator/adr-generator.d.ts +32 -0
  296. package/dist/core/generator/adr-generator.d.ts.map +1 -0
  297. package/dist/core/generator/adr-generator.js +192 -0
  298. package/dist/core/generator/adr-generator.js.map +1 -0
  299. package/dist/core/generator/index.d.ts +9 -0
  300. package/dist/core/generator/index.d.ts.map +1 -0
  301. package/dist/core/generator/index.js +12 -0
  302. package/dist/core/generator/index.js.map +1 -0
  303. package/dist/core/generator/mapping-generator.d.ts +54 -0
  304. package/dist/core/generator/mapping-generator.d.ts.map +1 -0
  305. package/dist/core/generator/mapping-generator.js +240 -0
  306. package/dist/core/generator/mapping-generator.js.map +1 -0
  307. package/dist/core/generator/openspec-compat.d.ts +160 -0
  308. package/dist/core/generator/openspec-compat.d.ts.map +1 -0
  309. package/dist/core/generator/openspec-compat.js +524 -0
  310. package/dist/core/generator/openspec-compat.js.map +1 -0
  311. package/dist/core/generator/openspec-format-generator.d.ts +131 -0
  312. package/dist/core/generator/openspec-format-generator.d.ts.map +1 -0
  313. package/dist/core/generator/openspec-format-generator.js +963 -0
  314. package/dist/core/generator/openspec-format-generator.js.map +1 -0
  315. package/dist/core/generator/openspec-writer.d.ts +130 -0
  316. package/dist/core/generator/openspec-writer.d.ts.map +1 -0
  317. package/dist/core/generator/openspec-writer.js +404 -0
  318. package/dist/core/generator/openspec-writer.js.map +1 -0
  319. package/dist/core/generator/prompts.d.ts +35 -0
  320. package/dist/core/generator/prompts.d.ts.map +1 -0
  321. package/dist/core/generator/prompts.js +212 -0
  322. package/dist/core/generator/prompts.js.map +1 -0
  323. package/dist/core/generator/rag-manifest-generator.d.ts +37 -0
  324. package/dist/core/generator/rag-manifest-generator.d.ts.map +1 -0
  325. package/dist/core/generator/rag-manifest-generator.js +134 -0
  326. package/dist/core/generator/rag-manifest-generator.js.map +1 -0
  327. package/dist/core/generator/schemas.d.ts +365 -0
  328. package/dist/core/generator/schemas.d.ts.map +1 -0
  329. package/dist/core/generator/schemas.js +190 -0
  330. package/dist/core/generator/schemas.js.map +1 -0
  331. package/dist/core/generator/spec-pipeline.d.ts +123 -0
  332. package/dist/core/generator/spec-pipeline.d.ts.map +1 -0
  333. package/dist/core/generator/spec-pipeline.js +699 -0
  334. package/dist/core/generator/spec-pipeline.js.map +1 -0
  335. package/dist/core/generator/stages/stage1-survey.d.ts +19 -0
  336. package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -0
  337. package/dist/core/generator/stages/stage1-survey.js +171 -0
  338. package/dist/core/generator/stages/stage1-survey.js.map +1 -0
  339. package/dist/core/generator/stages/stage2-entities.d.ts +11 -0
  340. package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -0
  341. package/dist/core/generator/stages/stage2-entities.js +74 -0
  342. package/dist/core/generator/stages/stage2-entities.js.map +1 -0
  343. package/dist/core/generator/stages/stage3-services.d.ts +11 -0
  344. package/dist/core/generator/stages/stage3-services.d.ts.map +1 -0
  345. package/dist/core/generator/stages/stage3-services.js +85 -0
  346. package/dist/core/generator/stages/stage3-services.js.map +1 -0
  347. package/dist/core/generator/stages/stage4-api.d.ts +11 -0
  348. package/dist/core/generator/stages/stage4-api.d.ts.map +1 -0
  349. package/dist/core/generator/stages/stage4-api.js +72 -0
  350. package/dist/core/generator/stages/stage4-api.js.map +1 -0
  351. package/dist/core/generator/stages/stage5-architecture.d.ts +11 -0
  352. package/dist/core/generator/stages/stage5-architecture.d.ts.map +1 -0
  353. package/dist/core/generator/stages/stage5-architecture.js +75 -0
  354. package/dist/core/generator/stages/stage5-architecture.js.map +1 -0
  355. package/dist/core/generator/stages/stage6-adr.d.ts +8 -0
  356. package/dist/core/generator/stages/stage6-adr.d.ts.map +1 -0
  357. package/dist/core/generator/stages/stage6-adr.js +47 -0
  358. package/dist/core/generator/stages/stage6-adr.js.map +1 -0
  359. package/dist/core/services/chat-agent.d.ts +50 -0
  360. package/dist/core/services/chat-agent.d.ts.map +1 -0
  361. package/dist/core/services/chat-agent.js +369 -0
  362. package/dist/core/services/chat-agent.js.map +1 -0
  363. package/dist/core/services/chat-tools.d.ts +32 -0
  364. package/dist/core/services/chat-tools.d.ts.map +1 -0
  365. package/dist/core/services/chat-tools.js +494 -0
  366. package/dist/core/services/chat-tools.js.map +1 -0
  367. package/dist/core/services/config-manager.d.ts +61 -0
  368. package/dist/core/services/config-manager.d.ts.map +1 -0
  369. package/dist/core/services/config-manager.js +149 -0
  370. package/dist/core/services/config-manager.js.map +1 -0
  371. package/dist/core/services/edge-store.d.ts +57 -0
  372. package/dist/core/services/edge-store.d.ts.map +1 -0
  373. package/dist/core/services/edge-store.js +419 -0
  374. package/dist/core/services/edge-store.js.map +1 -0
  375. package/dist/core/services/gitignore-manager.d.ts +29 -0
  376. package/dist/core/services/gitignore-manager.d.ts.map +1 -0
  377. package/dist/core/services/gitignore-manager.js +95 -0
  378. package/dist/core/services/gitignore-manager.js.map +1 -0
  379. package/dist/core/services/index.d.ts +8 -0
  380. package/dist/core/services/index.d.ts.map +1 -0
  381. package/dist/core/services/index.js +8 -0
  382. package/dist/core/services/index.js.map +1 -0
  383. package/dist/core/services/llm-service.d.ts +379 -0
  384. package/dist/core/services/llm-service.d.ts.map +1 -0
  385. package/dist/core/services/llm-service.js +1553 -0
  386. package/dist/core/services/llm-service.js.map +1 -0
  387. package/dist/core/services/mcp-handlers/analysis.d.ts +127 -0
  388. package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -0
  389. package/dist/core/services/mcp-handlers/analysis.js +1185 -0
  390. package/dist/core/services/mcp-handlers/analysis.js.map +1 -0
  391. package/dist/core/services/mcp-handlers/change.d.ts +14 -0
  392. package/dist/core/services/mcp-handlers/change.d.ts.map +1 -0
  393. package/dist/core/services/mcp-handlers/change.js +416 -0
  394. package/dist/core/services/mcp-handlers/change.js.map +1 -0
  395. package/dist/core/services/mcp-handlers/decisions.d.ts +16 -0
  396. package/dist/core/services/mcp-handlers/decisions.d.ts.map +1 -0
  397. package/dist/core/services/mcp-handlers/decisions.js +239 -0
  398. package/dist/core/services/mcp-handlers/decisions.js.map +1 -0
  399. package/dist/core/services/mcp-handlers/graph.d.ts +94 -0
  400. package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -0
  401. package/dist/core/services/mcp-handlers/graph.js +693 -0
  402. package/dist/core/services/mcp-handlers/graph.js.map +1 -0
  403. package/dist/core/services/mcp-handlers/orient.d.ts +17 -0
  404. package/dist/core/services/mcp-handlers/orient.d.ts.map +1 -0
  405. package/dist/core/services/mcp-handlers/orient.js +357 -0
  406. package/dist/core/services/mcp-handlers/orient.js.map +1 -0
  407. package/dist/core/services/mcp-handlers/semantic.d.ts +66 -0
  408. package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -0
  409. package/dist/core/services/mcp-handlers/semantic.js +432 -0
  410. package/dist/core/services/mcp-handlers/semantic.js.map +1 -0
  411. package/dist/core/services/mcp-handlers/utils.d.ts +85 -0
  412. package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -0
  413. package/dist/core/services/mcp-handlers/utils.js +262 -0
  414. package/dist/core/services/mcp-handlers/utils.js.map +1 -0
  415. package/dist/core/services/mcp-watcher.d.ts +41 -0
  416. package/dist/core/services/mcp-watcher.d.ts.map +1 -0
  417. package/dist/core/services/mcp-watcher.js +254 -0
  418. package/dist/core/services/mcp-watcher.js.map +1 -0
  419. package/dist/core/services/project-detector.d.ts +32 -0
  420. package/dist/core/services/project-detector.d.ts.map +1 -0
  421. package/dist/core/services/project-detector.js +100 -0
  422. package/dist/core/services/project-detector.js.map +1 -0
  423. package/dist/core/test-generator/coverage-analyzer.d.ts +27 -0
  424. package/dist/core/test-generator/coverage-analyzer.d.ts.map +1 -0
  425. package/dist/core/test-generator/coverage-analyzer.js +285 -0
  426. package/dist/core/test-generator/coverage-analyzer.js.map +1 -0
  427. package/dist/core/test-generator/framework-detector.d.ts +17 -0
  428. package/dist/core/test-generator/framework-detector.d.ts.map +1 -0
  429. package/dist/core/test-generator/framework-detector.js +65 -0
  430. package/dist/core/test-generator/framework-detector.js.map +1 -0
  431. package/dist/core/test-generator/index.d.ts +14 -0
  432. package/dist/core/test-generator/index.d.ts.map +1 -0
  433. package/dist/core/test-generator/index.js +11 -0
  434. package/dist/core/test-generator/index.js.map +1 -0
  435. package/dist/core/test-generator/renderers/catch2.d.ts +8 -0
  436. package/dist/core/test-generator/renderers/catch2.d.ts.map +1 -0
  437. package/dist/core/test-generator/renderers/catch2.js +47 -0
  438. package/dist/core/test-generator/renderers/catch2.js.map +1 -0
  439. package/dist/core/test-generator/renderers/gtest.d.ts +8 -0
  440. package/dist/core/test-generator/renderers/gtest.d.ts.map +1 -0
  441. package/dist/core/test-generator/renderers/gtest.js +45 -0
  442. package/dist/core/test-generator/renderers/gtest.js.map +1 -0
  443. package/dist/core/test-generator/renderers/index.d.ts +20 -0
  444. package/dist/core/test-generator/renderers/index.d.ts.map +1 -0
  445. package/dist/core/test-generator/renderers/index.js +35 -0
  446. package/dist/core/test-generator/renderers/index.js.map +1 -0
  447. package/dist/core/test-generator/renderers/playwright.d.ts +8 -0
  448. package/dist/core/test-generator/renderers/playwright.d.ts.map +1 -0
  449. package/dist/core/test-generator/renderers/playwright.js +44 -0
  450. package/dist/core/test-generator/renderers/playwright.js.map +1 -0
  451. package/dist/core/test-generator/renderers/pytest.d.ts +8 -0
  452. package/dist/core/test-generator/renderers/pytest.d.ts.map +1 -0
  453. package/dist/core/test-generator/renderers/pytest.js +44 -0
  454. package/dist/core/test-generator/renderers/pytest.js.map +1 -0
  455. package/dist/core/test-generator/renderers/shared.d.ts +21 -0
  456. package/dist/core/test-generator/renderers/shared.d.ts.map +1 -0
  457. package/dist/core/test-generator/renderers/shared.js +56 -0
  458. package/dist/core/test-generator/renderers/shared.js.map +1 -0
  459. package/dist/core/test-generator/renderers/vitest.d.ts +8 -0
  460. package/dist/core/test-generator/renderers/vitest.d.ts.map +1 -0
  461. package/dist/core/test-generator/renderers/vitest.js +52 -0
  462. package/dist/core/test-generator/renderers/vitest.js.map +1 -0
  463. package/dist/core/test-generator/scenario-parser.d.ts +33 -0
  464. package/dist/core/test-generator/scenario-parser.d.ts.map +1 -0
  465. package/dist/core/test-generator/scenario-parser.js +244 -0
  466. package/dist/core/test-generator/scenario-parser.js.map +1 -0
  467. package/dist/core/test-generator/test-generator.d.ts +30 -0
  468. package/dist/core/test-generator/test-generator.d.ts.map +1 -0
  469. package/dist/core/test-generator/test-generator.js +174 -0
  470. package/dist/core/test-generator/test-generator.js.map +1 -0
  471. package/dist/core/test-generator/test-writer.d.ts +25 -0
  472. package/dist/core/test-generator/test-writer.d.ts.map +1 -0
  473. package/dist/core/test-generator/test-writer.js +128 -0
  474. package/dist/core/test-generator/test-writer.js.map +1 -0
  475. package/dist/core/test-generator/then-matchers.d.ts +35 -0
  476. package/dist/core/test-generator/then-matchers.d.ts.map +1 -0
  477. package/dist/core/test-generator/then-matchers.js +211 -0
  478. package/dist/core/test-generator/then-matchers.js.map +1 -0
  479. package/dist/core/verifier/index.d.ts +5 -0
  480. package/dist/core/verifier/index.d.ts.map +1 -0
  481. package/dist/core/verifier/index.js +5 -0
  482. package/dist/core/verifier/index.js.map +1 -0
  483. package/dist/core/verifier/verification-engine.d.ts +293 -0
  484. package/dist/core/verifier/verification-engine.d.ts.map +1 -0
  485. package/dist/core/verifier/verification-engine.js +919 -0
  486. package/dist/core/verifier/verification-engine.js.map +1 -0
  487. package/dist/types/index.d.ts +368 -0
  488. package/dist/types/index.d.ts.map +1 -0
  489. package/dist/types/index.js +5 -0
  490. package/dist/types/index.js.map +1 -0
  491. package/dist/types/pipeline.d.ts +167 -0
  492. package/dist/types/pipeline.d.ts.map +1 -0
  493. package/dist/types/pipeline.js +5 -0
  494. package/dist/types/pipeline.js.map +1 -0
  495. package/dist/types/test-generator.d.ts +103 -0
  496. package/dist/types/test-generator.d.ts.map +1 -0
  497. package/dist/types/test-generator.js +17 -0
  498. package/dist/types/test-generator.js.map +1 -0
  499. package/dist/utils/command-helpers.d.ts +68 -0
  500. package/dist/utils/command-helpers.d.ts.map +1 -0
  501. package/dist/utils/command-helpers.js +150 -0
  502. package/dist/utils/command-helpers.js.map +1 -0
  503. package/dist/utils/errors.d.ts +51 -0
  504. package/dist/utils/errors.d.ts.map +1 -0
  505. package/dist/utils/errors.js +129 -0
  506. package/dist/utils/errors.js.map +1 -0
  507. package/dist/utils/logger.d.ts +149 -0
  508. package/dist/utils/logger.d.ts.map +1 -0
  509. package/dist/utils/logger.js +342 -0
  510. package/dist/utils/logger.js.map +1 -0
  511. package/dist/utils/misc.d.ts +10 -0
  512. package/dist/utils/misc.d.ts.map +1 -0
  513. package/dist/utils/misc.js +21 -0
  514. package/dist/utils/misc.js.map +1 -0
  515. package/dist/utils/progress.d.ts +142 -0
  516. package/dist/utils/progress.d.ts.map +1 -0
  517. package/dist/utils/progress.js +283 -0
  518. package/dist/utils/progress.js.map +1 -0
  519. package/dist/utils/prompts.d.ts +53 -0
  520. package/dist/utils/prompts.d.ts.map +1 -0
  521. package/dist/utils/prompts.js +199 -0
  522. package/dist/utils/prompts.js.map +1 -0
  523. package/dist/utils/shutdown.d.ts +89 -0
  524. package/dist/utils/shutdown.d.ts.map +1 -0
  525. package/dist/utils/shutdown.js +238 -0
  526. package/dist/utils/shutdown.js.map +1 -0
  527. package/examples/bmad/README.md +113 -0
  528. package/examples/bmad/agents/architect.md +226 -0
  529. package/examples/bmad/agents/dev-brownfield.md +69 -0
  530. package/examples/bmad/setup/architect.customize.yaml +14 -0
  531. package/examples/bmad/tasks/implement-story.md +254 -0
  532. package/examples/bmad/tasks/onboarding.md +169 -0
  533. package/examples/bmad/tasks/refactor.md +178 -0
  534. package/examples/bmad/tasks/sprint-planning.md +168 -0
  535. package/examples/bmad/templates/story.md +108 -0
  536. package/examples/cline-workflows/openlore-analyze-codebase.md +101 -0
  537. package/examples/cline-workflows/openlore-check-spec-drift.md +102 -0
  538. package/examples/cline-workflows/openlore-execute-refactor.md +212 -0
  539. package/examples/cline-workflows/openlore-implement-feature.md +266 -0
  540. package/examples/cline-workflows/openlore-plan-refactor.md +279 -0
  541. package/examples/cline-workflows/openlore-refactor-codebase.md +16 -0
  542. package/examples/cline-workflows/openlore-write-tests.md +177 -0
  543. package/examples/drift-demo/openspec/config.yaml +14 -0
  544. package/examples/drift-demo/openspec/specs/architecture/spec.md +30 -0
  545. package/examples/drift-demo/openspec/specs/auth/spec.md +71 -0
  546. package/examples/drift-demo/openspec/specs/database/spec.md +33 -0
  547. package/examples/drift-demo/openspec/specs/overview/spec.md +20 -0
  548. package/examples/drift-demo/openspec/specs/projects/spec.md +55 -0
  549. package/examples/drift-demo/openspec/specs/tasks/spec.md +78 -0
  550. package/examples/drift-demo/package.json +21 -0
  551. package/examples/drift-demo/src/auth/auth-middleware.ts +30 -0
  552. package/examples/drift-demo/src/auth/auth-routes.ts +29 -0
  553. package/examples/drift-demo/src/auth/auth-service.ts +45 -0
  554. package/examples/drift-demo/src/database/connection.ts +27 -0
  555. package/examples/drift-demo/src/index.ts +16 -0
  556. package/examples/drift-demo/src/projects/project-model.ts +15 -0
  557. package/examples/drift-demo/src/projects/project-service.ts +34 -0
  558. package/examples/drift-demo/src/tasks/task-model.ts +37 -0
  559. package/examples/drift-demo/src/tasks/task-routes.ts +53 -0
  560. package/examples/drift-demo/src/tasks/task-service.ts +60 -0
  561. package/examples/drift-demo/src/utils/validation.ts +11 -0
  562. package/examples/drift-demo/tests/auth.test.ts +4 -0
  563. package/examples/drift-demo/tests/tasks.test.ts +4 -0
  564. package/examples/drift-demo/tsconfig.json +10 -0
  565. package/examples/drift-test/run-drift-test.sh +1087 -0
  566. package/examples/gsd/README.md +119 -0
  567. package/examples/gsd/commands/gsd/openlore-drift.md +111 -0
  568. package/examples/gsd/commands/gsd/openlore-orient.md +191 -0
  569. package/examples/mistral-vibe/README.md +101 -0
  570. package/examples/mistral-vibe/antipatterns-template.md +18 -0
  571. package/examples/mistral-vibe/skills/openlore-analyze-codebase/SKILL.md +124 -0
  572. package/examples/mistral-vibe/skills/openlore-brainstorm/SKILL.md +379 -0
  573. package/examples/mistral-vibe/skills/openlore-debug/SKILL.md +330 -0
  574. package/examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md +291 -0
  575. package/examples/mistral-vibe/skills/openlore-generate/SKILL.md +245 -0
  576. package/examples/mistral-vibe/skills/openlore-implement-story/SKILL.md +326 -0
  577. package/examples/mistral-vibe/skills/openlore-plan-refactor/SKILL.md +365 -0
  578. package/examples/mistral-vibe/skills/openlore-review-changes/SKILL.md +128 -0
  579. package/examples/mistral-vibe/skills/openlore-write-tests/SKILL.md +261 -0
  580. package/examples/opencode/agent-guard.ts +170 -0
  581. package/examples/opencode/plugins/anti-laziness.ts +202 -0
  582. package/examples/opencode/plugins/lib/openlore-context-injector-helpers.ts +116 -0
  583. package/examples/opencode/plugins/lib/openlore-decision-extractor-helpers.ts +65 -0
  584. package/examples/opencode/plugins/openlore-context-injector.test.ts +211 -0
  585. package/examples/opencode/plugins/openlore-context-injector.ts +165 -0
  586. package/examples/opencode/plugins/openlore-decision-extractor.test.ts +131 -0
  587. package/examples/opencode/plugins/openlore-decision-extractor.ts +322 -0
  588. package/examples/opencode/plugins/openlore-enforcer.ts +227 -0
  589. package/examples/opencode/prompts/sisyphus-sdd.md +150 -0
  590. package/examples/opencode-skills/openlore-analyze-codebase/SKILL.md +101 -0
  591. package/examples/opencode-skills/openlore-brainstorm/SKILL.md +354 -0
  592. package/examples/opencode-skills/openlore-debug/SKILL.md +291 -0
  593. package/examples/opencode-skills/openlore-execute-refactor/SKILL.md +241 -0
  594. package/examples/opencode-skills/openlore-generate/SKILL.md +236 -0
  595. package/examples/opencode-skills/openlore-implement-story/SKILL.md +251 -0
  596. package/examples/opencode-skills/openlore-plan-refactor/SKILL.md +298 -0
  597. package/examples/opencode-skills/openlore-review-changes/SKILL.md +134 -0
  598. package/examples/opencode-skills/openlore-write-tests/SKILL.md +230 -0
  599. package/examples/openspec-analysis/README.md +59 -0
  600. package/examples/openspec-analysis/SUMMARY.md +72 -0
  601. package/examples/openspec-analysis/config.json +16 -0
  602. package/examples/openspec-analysis/dependencies.mermaid +35 -0
  603. package/examples/openspec-analysis/dependency-graph.json +12116 -0
  604. package/examples/openspec-analysis/llm-context.json +119 -0
  605. package/examples/openspec-analysis/repo-structure.json +871 -0
  606. package/examples/openspec-cli/README.md +67 -0
  607. package/examples/openspec-cli/openspec/config.yaml +26 -0
  608. package/examples/openspec-cli/openspec/specs/architecture/spec.md +178 -0
  609. package/examples/openspec-cli/openspec/specs/artifact-graph/spec.md +143 -0
  610. package/examples/openspec-cli/openspec/specs/cli/spec.md +138 -0
  611. package/examples/openspec-cli/openspec/specs/overview/spec.md +60 -0
  612. package/examples/openspec-cli/openspec/specs/parsing/spec.md +123 -0
  613. package/examples/openspec-cli/openspec/specs/validation/spec.md +108 -0
  614. package/examples/spec-kit/README.md +104 -0
  615. package/examples/spec-kit/commands/drift.md +87 -0
  616. package/examples/spec-kit/commands/orient.md +138 -0
  617. package/examples/spec-kit/extension.yml +54 -0
  618. package/package.json +125 -0
  619. package/src/viewer/InteractiveGraphViewer.jsx +1600 -0
  620. package/src/viewer/app/index.html +17 -0
  621. package/src/viewer/app/main.jsx +13 -0
  622. package/src/viewer/components/ArchitectureView.jsx +177 -0
  623. package/src/viewer/components/ChatPanel.jsx +450 -0
  624. package/src/viewer/components/ClassGraph.jsx +782 -0
  625. package/src/viewer/components/ClusterGraph.jsx +469 -0
  626. package/src/viewer/components/FilterBar.jsx +179 -0
  627. package/src/viewer/components/FlatGraph.jsx +282 -0
  628. package/src/viewer/components/MicroComponents.jsx +85 -0
  629. package/src/viewer/hooks/usePanZoom.js +79 -0
  630. package/src/viewer/utils/constants.js +64 -0
  631. package/src/viewer/utils/graph-helpers.js +303 -0
  632. package/src/viewer/utils/graph-helpers.test.ts +39 -0
  633. package/src/viewer/utils/themes.js +206 -0
  634. package/stubs/tree-sitter-cli-stub/package.json +6 -0
@@ -0,0 +1,1600 @@
1
+ import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
2
+ import { extColor, CLUSTER_PALETTE, CLUSTER_PALETTE_LIGHT } from './utils/constants.js';
3
+ import {
4
+ parseSpecRequirements,
5
+ buildMappingIndex,
6
+ normalizePath,
7
+ parseGraph,
8
+ enrichGraphWithRefactors,
9
+ computeBlast,
10
+ } from './utils/graph-helpers.js';
11
+ import { FlatGraph } from './components/FlatGraph.jsx';
12
+ import { ClusterGraph } from './components/ClusterGraph.jsx';
13
+ import { ClassGraph } from './components/ClassGraph.jsx';
14
+ import { FilterBar } from './components/FilterBar.jsx';
15
+ import { ArchitectureView } from './components/ArchitectureView.jsx';
16
+ import { Hint, SL, Row, Chip, KindBadge } from './components/MicroComponents.jsx';
17
+ import { ChatPanel } from './components/ChatPanel.jsx';
18
+ import { THEMES, THEME_KEYS, DEFAULT_THEME } from './utils/themes.js';
19
+
20
+ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '/api/spec' }) {
21
+ const [rawGraph, setRawGraph] = useState(null);
22
+ const [llmCtx, setLlmCtx] = useState(null);
23
+ const [refReport, setRefReport] = useState(null);
24
+ const [classData, setClassData] = useState(null);
25
+ const [selectedClass, setSelectedClass] = useState(null); // full class object
26
+ const selectedClassId = selectedClass?.id ?? null;
27
+ const [focusedPaths, setFocusedPaths] = useState([]);
28
+ const [mapping, setMapping] = useState(null);
29
+ const [specReqs, setSpecReqs] = useState({});
30
+ const [selectedId, setSelectedId] = useState(null);
31
+ const [affectedIds, setAffectedIds] = useState([]);
32
+ const [focusedIds, setFocusedIds] = useState([]);
33
+ const [search, setSearch] = useState('');
34
+ const [semanticResults, setSemanticResults] = useState([]);
35
+ const [semanticAvailable, setSemanticAvailable] = useState(true);
36
+ const semanticTimer = useRef(null);
37
+ const [tab, setTab] = useState('node');
38
+ const [skeletonData, setSkeletonData] = useState(null);
39
+ const [skeletonLoading, setSkeletonLoading] = useState(false);
40
+ const [viewMode, setViewMode] = useState('clusters');
41
+ const [expandedClusters, setExpandedClusters] = useState(new Set());
42
+ const [filters, setFilters] = useState({
43
+ hideOrphans: false,
44
+ minScore: 0,
45
+ topN: 999,
46
+ cluster: '',
47
+ refactorOnly: false,
48
+ });
49
+ const [loaded, setLoaded] = useState(false);
50
+ const [chatOpen, setChatOpen] = useState(false);
51
+ const [themeName, setThemeName] = useState(
52
+ () => localStorage.getItem('openlore-theme') || DEFAULT_THEME
53
+ );
54
+ const theme = THEMES[themeName] ?? THEMES[DEFAULT_THEME];
55
+ const clusterPalette = themeName === 'light' ? CLUSTER_PALETTE_LIGHT : CLUSTER_PALETTE;
56
+
57
+ // Derive graph from raw data — recomputes automatically when theme, refReport or raw data changes.
58
+ const graph = useMemo(() => {
59
+ if (!rawGraph) return null;
60
+ const g = parseGraph(rawGraph, clusterPalette);
61
+ return refReport ? enrichGraphWithRefactors(g, refReport) : g;
62
+ }, [rawGraph, clusterPalette, refReport]);
63
+
64
+ const cycleTheme = () => setThemeName((prev) => {
65
+ const idx = THEME_KEYS.indexOf(prev);
66
+ const next = THEME_KEYS[(idx + 1) % THEME_KEYS.length];
67
+ localStorage.setItem('openlore-theme', next);
68
+ return next;
69
+ });
70
+ const fileRef = useRef();
71
+ const hasAutoLoadedRef = useRef(false);
72
+
73
+ useEffect(() => {
74
+ setTimeout(() => setLoaded(true), 80);
75
+ }, []);
76
+
77
+ const loadGraph = useCallback(
78
+ (jsonStr) => {
79
+ try {
80
+ setRawGraph(JSON.parse(jsonStr));
81
+ setSelectedId(null);
82
+ setAffectedIds([]);
83
+ setFocusedIds([]);
84
+ setSearch('');
85
+ setFilters({
86
+ hideOrphans: false,
87
+ minScore: 0,
88
+ topN: 999,
89
+ cluster: '',
90
+ refactorOnly: false,
91
+ });
92
+ setExpandedClusters(new Set());
93
+ } catch (e) {
94
+ alert('Invalid JSON: ' + e.message);
95
+ }
96
+ },
97
+ []
98
+ );
99
+
100
+ const loadMapping = useCallback((jsonStr) => {
101
+ try {
102
+ const m = JSON.parse(jsonStr);
103
+ setMapping(buildMappingIndex(m));
104
+ } catch (e) {
105
+ console.error('Invalid mapping JSON', e);
106
+ }
107
+ }, []);
108
+
109
+ const loadSpec = useCallback((mdStr) => {
110
+ setSpecReqs(parseSpecRequirements(mdStr));
111
+ }, []);
112
+
113
+ const mappingRef = useRef();
114
+ const specRef = useRef();
115
+
116
+ useEffect(() => {
117
+ if (!graphUrl || hasAutoLoadedRef.current) return;
118
+ hasAutoLoadedRef.current = true;
119
+ (async () => {
120
+ try {
121
+ const res = await fetch(graphUrl);
122
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
123
+ const text = await res.text();
124
+ loadGraph(text);
125
+
126
+ try {
127
+ const ctxRes = await fetch('/api/llm-context');
128
+ if (ctxRes.ok) setLlmCtx(await ctxRes.json());
129
+ } catch { /* ignore */ }
130
+
131
+ try {
132
+ const cgRes = await fetch('/api/class-graph');
133
+ if (cgRes.ok) setClassData(await cgRes.json());
134
+ } catch { /* ignore */ }
135
+
136
+ try {
137
+ const refRes = await fetch('/api/refactor-priorities');
138
+ if (refRes.ok) {
139
+ const report = await refRes.json();
140
+ setRefReport(report);
141
+ }
142
+ } catch { /* ignore */ }
143
+
144
+ try {
145
+ const mRes = await fetch('/api/mapping');
146
+ if (mRes.ok) loadMapping(await mRes.text());
147
+ } catch { /* ignore */ }
148
+ try {
149
+ const srRes = await fetch('/api/spec-requirements');
150
+ if (srRes.ok) {
151
+ const reqsJson = await srRes.json();
152
+ setSpecReqs(reqsJson);
153
+ } else {
154
+ try {
155
+ const sRes = await fetch('/api/spec');
156
+ if (sRes.ok) loadSpec(await sRes.text());
157
+ } catch { /* ignore */ }
158
+ }
159
+ } catch { /* ignore */ }
160
+ } catch (e) {
161
+ console.error('Failed to load graph from', graphUrl, e);
162
+ }
163
+ })();
164
+ }, [graphUrl, mappingUrl, specUrl, loadGraph]);
165
+
166
+ const handleFile = (e) => {
167
+ const f = e.target.files[0];
168
+ if (!f) return;
169
+ const r = new FileReader();
170
+ r.onload = (ev) => loadGraph(ev.target.result);
171
+ r.readAsText(f);
172
+ };
173
+
174
+ // ── Filtered nodes/edges ──────────────────────────────────────────────────
175
+ const { visibleNodes, visibleEdges, filterStats } = useMemo(() => {
176
+ if (!graph) return { visibleNodes: [], visibleEdges: [], filterStats: {} };
177
+
178
+ const connectedIds = new Set();
179
+ graph.edges.forEach((e) => {
180
+ connectedIds.add(e.source);
181
+ connectedIds.add(e.target);
182
+ });
183
+ const orphanCount = graph.nodes.filter((n) => !connectedIds.has(n.id)).length;
184
+
185
+ let nodes = filters.cluster
186
+ ? graph.nodes.filter((n) => n.cluster.name === filters.cluster)
187
+ : graph.nodes;
188
+
189
+ if (filters.refactorOnly) {
190
+ nodes = nodes.filter((n) => n.refactor);
191
+ }
192
+
193
+ if (filters.hideOrphans) nodes = nodes.filter((n) => connectedIds.has(n.id));
194
+ if (filters.minScore > 0) nodes = nodes.filter((n) => n.score >= filters.minScore);
195
+
196
+ if (filters.topN < 999) {
197
+ const ranked = graph.rankings.byImportance || graph.nodes.map((n) => n.id);
198
+ const topSet = new Set(ranked.slice(0, filters.topN));
199
+ nodes = nodes.filter((n) => topSet.has(n.id));
200
+ }
201
+
202
+ const vset = new Set(nodes.map((n) => n.id));
203
+ const edges = graph.edges.filter((e) => vset.has(e.source) && vset.has(e.target));
204
+
205
+ const refactorTotal =
206
+ graph.refactorStats?.withIssues ?? graph.nodes.filter((n) => n.refactor).length;
207
+ const refactorVisible = nodes.filter((n) => n.refactor).length;
208
+
209
+ return {
210
+ visibleNodes: nodes,
211
+ visibleEdges: edges,
212
+ filterStats: {
213
+ total: graph.nodes.length,
214
+ visible: nodes.length,
215
+ visibleEdges: edges.length,
216
+ orphanCount,
217
+ refactorTotal,
218
+ refactorVisible,
219
+ },
220
+ };
221
+ }, [graph, filters]);
222
+
223
+ const handleSearch = (q) => {
224
+ setSearch(q);
225
+ if (!q.trim()) {
226
+ setFocusedIds([]);
227
+ setSemanticResults([]);
228
+ clearTimeout(semanticTimer.current);
229
+ return;
230
+ }
231
+ const lo = q.toLowerCase();
232
+ setFocusedIds(
233
+ visibleNodes
234
+ .filter(
235
+ (n) =>
236
+ n.label.toLowerCase().includes(lo) ||
237
+ n.path.toLowerCase().includes(lo) ||
238
+ n.ext.includes(lo) ||
239
+ n.tags.some((t) => t.toLowerCase().includes(lo)) ||
240
+ n.exports.some((ex) => ex.name.toLowerCase().includes(lo))
241
+ )
242
+ .map((n) => n.id)
243
+ );
244
+ if (!semanticAvailable || q.trim().length < 3) return;
245
+ clearTimeout(semanticTimer.current);
246
+ semanticTimer.current = setTimeout(async () => {
247
+ try {
248
+ const res = await fetch(`/api/search?q=${encodeURIComponent(q.trim())}`);
249
+ if (res.status === 404) { setSemanticAvailable(false); return; }
250
+ if (!res.ok) return;
251
+ setSemanticResults(await res.json());
252
+ } catch { /* ignore */ }
253
+ }, 400);
254
+ };
255
+
256
+ const handleSelect = useCallback(
257
+ (id) => {
258
+ if (selectedId === id) {
259
+ setSelectedId(null);
260
+ setAffectedIds([]);
261
+ return;
262
+ }
263
+ setSelectedId(id);
264
+ setAffectedIds(computeBlast(visibleEdges, id));
265
+ setTab(mapping ? 'spec' : 'node');
266
+ },
267
+ [selectedId, visibleEdges, mapping]
268
+ );
269
+
270
+ const toggleCluster = useCallback((cid) => {
271
+ setExpandedClusters((prev) => {
272
+ const next = new Set(prev);
273
+ next.has(cid) ? next.delete(cid) : next.add(cid);
274
+ return next;
275
+ });
276
+ }, []);
277
+
278
+ const clearSelection = useCallback(() => {
279
+ setSelectedId(null);
280
+ setAffectedIds([]);
281
+ setExpandedClusters(new Set());
282
+ setSemanticResults([]);
283
+ setSkeletonData(null);
284
+ }, []);
285
+
286
+ useEffect(() => {
287
+ const onKey = (e) => { if (e.key === 'Escape') clearSelection(); };
288
+ window.addEventListener('keydown', onKey);
289
+ return () => window.removeEventListener('keydown', onKey);
290
+ }, [clearSelection]);
291
+
292
+ // Track which clusters were auto-expanded by the chatbot (so we can collapse them on clear)
293
+ const chatExpandedClusters = useRef(new Set());
294
+ // Track whether selectedId was set by the chatbot (so we can clear it on clear)
295
+ const chatSelectedId = useRef(null);
296
+
297
+ // Auto-expand clusters when their nodes are highlighted by the chatbot
298
+ useEffect(() => {
299
+ if (!graph) return;
300
+
301
+ if (focusedIds.length === 0) {
302
+ // focusedIds cleared — collapse clusters that were auto-expanded by chat
303
+ if (chatExpandedClusters.current.size > 0) {
304
+ const toCollapse = new Set(chatExpandedClusters.current);
305
+ chatExpandedClusters.current = new Set();
306
+ setExpandedClusters((prev) => {
307
+ const next = new Set(prev);
308
+ toCollapse.forEach((cid) => next.delete(cid));
309
+ return next;
310
+ });
311
+ // If the selected node is inside a collapsing cluster, clear selection
312
+ // to avoid ghost edges rendering from cluster centers
313
+ if (selectedId) {
314
+ const selNode = graph.nodes.find((n) => n.id === selectedId);
315
+ if (selNode && toCollapse.has(selNode.cluster?.id)) {
316
+ setSelectedId(null);
317
+ setAffectedIds([]);
318
+ }
319
+ }
320
+ }
321
+ chatSelectedId.current = null;
322
+ return;
323
+ }
324
+
325
+ const clusterIdsToExpand = new Set();
326
+ const validNodeIds = [];
327
+ focusedIds.forEach((fid) => {
328
+ const node = graph.nodes.find((n) => n.id === fid);
329
+ if (node) {
330
+ if (node.cluster?.id) clusterIdsToExpand.add(node.cluster.id);
331
+ validNodeIds.push(fid);
332
+ }
333
+ });
334
+
335
+ if (clusterIdsToExpand.size > 0) {
336
+ setExpandedClusters((prev) => {
337
+ const next = new Set(prev);
338
+ clusterIdsToExpand.forEach((cid) => {
339
+ if (!prev.has(cid)) {
340
+ next.add(cid);
341
+ chatExpandedClusters.current.add(cid);
342
+ }
343
+ });
344
+ return next;
345
+ });
346
+ }
347
+
348
+ // If exactly one node matched, auto-select it for details — but skip blast radius
349
+ // to avoid lighting up unrelated edges through the full reachability set.
350
+ if (validNodeIds.length === 1) {
351
+ setSelectedId(validNodeIds[0]);
352
+ setAffectedIds([]);
353
+ setTab(mapping ? 'spec' : 'node');
354
+ chatSelectedId.current = validNodeIds[0];
355
+ }
356
+ }, [focusedIds, graph, mapping]);
357
+
358
+ const selectedNode = graph?.nodes.find((n) => n.id === selectedId);
359
+
360
+ const selectedPath = selectedNode?.path ?? null;
361
+ useEffect(() => {
362
+ if (tab !== 'skeleton' || !selectedPath) { setSkeletonData(null); return; }
363
+ setSkeletonLoading(true);
364
+ fetch(`/api/skeleton?file=${encodeURIComponent(selectedPath)}`)
365
+ .then(r => r.ok ? r.json() : null)
366
+ .then(d => { setSkeletonData(d); setSkeletonLoading(false); })
367
+ .catch(() => setSkeletonLoading(false));
368
+ }, [tab, selectedPath]);
369
+
370
+ const selectedEdges = useMemo(() => {
371
+ if (!selectedId) return [];
372
+ return visibleEdges.filter((e) => e.source === selectedId || e.target === selectedId);
373
+ }, [selectedId, visibleEdges]);
374
+
375
+ const linkedIds = useMemo(() => {
376
+ if (!selectedId) return new Set();
377
+ const set = new Set([selectedId, ...affectedIds]);
378
+ visibleEdges.forEach((e) => {
379
+ if (e.source === selectedId) set.add(e.target);
380
+ if (e.target === selectedId) set.add(e.source);
381
+ });
382
+ return set;
383
+ }, [selectedId, affectedIds, visibleEdges]);
384
+
385
+ const stats = graph?.statistics || {};
386
+ // Use structuralClusters (clusters with real internal edges) for all UI.
387
+ // Compute from clusters if not present in the JSON (for backward compatibility).
388
+ const structuralClusters = graph?.structuralClusters ??
389
+ (graph?.clusters?.filter(c => c.internalEdges > 0) ?? []);
390
+ // Fall back to all directory clusters when no structural ones exist (e.g. Swift/C++ projects
391
+ // where dep edges come from the call graph and may not yet be available).
392
+ const displayClusters = structuralClusters.length > 0
393
+ ? structuralClusters
394
+ : (graph?.clusters ?? []);
395
+ const clusterNames = displayClusters.map((c) => c.name);
396
+
397
+ // ── Upload screen ─────────────────────────────────────────────────────────
398
+ if (!graph)
399
+ return (
400
+ <div
401
+ style={{
402
+ ...theme.vars,
403
+ width: '100%',
404
+ height: '100vh',
405
+ background: 'var(--bg-base)',
406
+ display: 'flex',
407
+ flexDirection: 'column',
408
+ alignItems: 'center',
409
+ justifyContent: 'center',
410
+ fontFamily: "'JetBrains Mono',monospace",
411
+ color: 'var(--tx-primary)',
412
+ opacity: loaded ? 1 : 0,
413
+ transition: 'opacity 0.3s',
414
+ }}
415
+ >
416
+ <div style={{ fontSize: 10, letterSpacing: '0.18em', color: 'var(--tx-faint)', marginBottom: 28 }}>
417
+ INTERACTIVE GRAPH VIEWER
418
+ </div>
419
+ <div
420
+ style={{
421
+ border: '1px dashed var(--ac-edge-type)',
422
+ borderRadius: 12,
423
+ padding: '44px 64px',
424
+ textAlign: 'center',
425
+ cursor: 'pointer',
426
+ }}
427
+ onClick={() => fileRef.current.click()}
428
+ onDragOver={(e) => e.preventDefault()}
429
+ onDrop={(e) => {
430
+ e.preventDefault();
431
+ const f = e.dataTransfer.files[0];
432
+ if (f) {
433
+ const r = new FileReader();
434
+ r.onload = (ev) => loadGraph(ev.target.result);
435
+ r.readAsText(f);
436
+ }
437
+ }}
438
+ >
439
+ <div style={{ fontSize: 32, marginBottom: 14, color: 'var(--ac-primary)' }}>⬡</div>
440
+ <div style={{ fontSize: 12, color: 'var(--tx-secondary)', marginBottom: 6 }}>
441
+ Drop a <code style={{ color: 'var(--ac-primary)' }}>dependency-graph.json</code>
442
+ </div>
443
+ <div style={{ fontSize: 10, color: 'var(--tx-ghost)' }}>or click to browse</div>
444
+ </div>
445
+ <input
446
+ ref={fileRef}
447
+ type="file"
448
+ accept=".json"
449
+ style={{ display: 'none' }}
450
+ onChange={handleFile}
451
+ />
452
+ <input
453
+ ref={mappingRef}
454
+ type="file"
455
+ accept=".json"
456
+ style={{ display: 'none' }}
457
+ onChange={(e) => {
458
+ const f = e.target.files[0];
459
+ if (f) {
460
+ const r = new FileReader();
461
+ r.onload = (ev) => loadMapping(ev.target.result);
462
+ r.readAsText(f);
463
+ }
464
+ }}
465
+ />
466
+ <input
467
+ ref={specRef}
468
+ type="file"
469
+ accept=".md"
470
+ style={{ display: 'none' }}
471
+ onChange={(e) => {
472
+ const f = e.target.files[0];
473
+ if (f) {
474
+ const r = new FileReader();
475
+ r.onload = (ev) => loadSpec(ev.target.result);
476
+ r.readAsText(f);
477
+ }
478
+ }}
479
+ />
480
+ </div>
481
+ );
482
+
483
+ // ── Main UI ───────────────────────────────────────────────────────────────
484
+ return (
485
+ <div
486
+ style={{
487
+ ...theme.vars,
488
+ width: '100%',
489
+ height: '100vh',
490
+ background: 'var(--bg-base)',
491
+ fontFamily: "'JetBrains Mono',monospace",
492
+ color: 'var(--tx-primary)',
493
+ display: 'flex',
494
+ flexDirection: 'column',
495
+ opacity: loaded ? 1 : 0,
496
+ transition: 'opacity 0.3s',
497
+ }}
498
+ >
499
+ {/* Top bar */}
500
+ <div
501
+ style={{
502
+ display: 'flex',
503
+ alignItems: 'center',
504
+ gap: 10,
505
+ padding: '8px 18px',
506
+ borderBottom: '1px solid var(--bd-faint)',
507
+ background: 'var(--bg-panel)',
508
+ flexShrink: 0,
509
+ }}
510
+ >
511
+ <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
512
+ <div
513
+ style={{
514
+ width: 6,
515
+ height: 6,
516
+ borderRadius: '50%',
517
+ background: 'var(--ac-primary)',
518
+ boxShadow: '0 0 8px var(--ac-primary)',
519
+ }}
520
+ />
521
+ <span
522
+ style={{ fontSize: 10, fontWeight: 700, color: 'var(--tx-bright)', letterSpacing: '0.09em' }}
523
+ >
524
+ GRAPH VIEWER
525
+ </span>
526
+ </div>
527
+ {[
528
+ ['nodes', stats.nodeCount],
529
+ ['edges', stats.edgeCount],
530
+ ['clusters', displayClusters.length],
531
+ ].map(([l, v]) => (
532
+ <div
533
+ key={l}
534
+ style={{
535
+ fontSize: 9,
536
+ color: 'var(--tx-dim)',
537
+ background: 'var(--bg-raised)',
538
+ borderRadius: 4,
539
+ padding: '2px 7px',
540
+ border: '1px solid var(--bd-muted)',
541
+ }}
542
+ >
543
+ <span style={{ color: 'var(--tx-muted)' }}>{v}</span> {l}
544
+ </div>
545
+ ))}
546
+ <div style={{ display: 'flex', gap: 2, marginLeft: 8 }}>
547
+ {[
548
+ ['clusters', '⬡ clusters'],
549
+ ['flat', '⊙ flat'],
550
+ ['architecture', '⬛ architecture'],
551
+ ['classes', '◈ classes'],
552
+ ].map(([v, lbl]) => (
553
+ <button
554
+ key={v}
555
+ onClick={() => {
556
+ setViewMode(v);
557
+ setSelectedId(null);
558
+ setAffectedIds([]);
559
+ }}
560
+ style={{
561
+ padding: '3px 10px',
562
+ fontSize: 9,
563
+ background: viewMode === v ? 'var(--bg-select)' : 'transparent',
564
+ border: `1px solid ${viewMode === v ? 'var(--ac-primary)' : 'var(--bd-muted)'}`,
565
+ borderRadius: 4,
566
+ color: viewMode === v ? 'var(--tx-primary)' : 'var(--tx-ghost)',
567
+ cursor: 'pointer',
568
+ fontFamily: 'inherit',
569
+ }}
570
+ >
571
+ {lbl}
572
+ </button>
573
+ ))}
574
+ </div>
575
+ <div style={{ marginLeft: 'auto', position: 'relative' }}>
576
+ <input
577
+ value={search}
578
+ onChange={(e) => handleSearch(e.target.value)}
579
+ placeholder="search name, path, export, tag..."
580
+ style={{
581
+ background: 'var(--bg-input)',
582
+ border: '1px solid var(--bd-muted)',
583
+ color: 'var(--tx-primary)',
584
+ padding: '5px 12px 5px 26px',
585
+ borderRadius: 5,
586
+ fontSize: 9,
587
+ width: 230,
588
+ outline: 'none',
589
+ fontFamily: 'inherit',
590
+ }}
591
+ />
592
+ <span
593
+ style={{
594
+ position: 'absolute',
595
+ left: 8,
596
+ top: '50%',
597
+ transform: 'translateY(-50%)',
598
+ fontSize: 11,
599
+ color: 'var(--tx-ghost)',
600
+ }}
601
+ >
602
+
603
+ </span>
604
+ {search && (
605
+ <span
606
+ onClick={() => handleSearch('')}
607
+ style={{
608
+ position: 'absolute',
609
+ right: focusedIds.length > 0 ? 22 : 8,
610
+ top: '50%',
611
+ transform: 'translateY(-50%)',
612
+ fontSize: 10,
613
+ color: 'var(--tx-ghost)',
614
+ cursor: 'pointer',
615
+ lineHeight: 1,
616
+ }}
617
+ >
618
+ x
619
+ </span>
620
+ )}
621
+ {focusedIds.length > 0 && (
622
+ <span
623
+ style={{
624
+ position: 'absolute',
625
+ right: 8,
626
+ top: '50%',
627
+ transform: 'translateY(-50%)',
628
+ fontSize: 9,
629
+ color: 'var(--ac-primary)',
630
+ }}
631
+ >
632
+ {focusedIds.length}
633
+ </span>
634
+ )}
635
+ {semanticResults.length > 0 && (
636
+ <div
637
+ style={{
638
+ position: 'absolute',
639
+ top: '100%',
640
+ right: 0,
641
+ marginTop: 4,
642
+ width: 280,
643
+ background: 'var(--bg-input)',
644
+ border: '1px solid var(--bd-muted)',
645
+ borderRadius: 5,
646
+ zIndex: 100,
647
+ boxShadow: '0 4px 16px rgba(0,0,0,0.5)',
648
+ overflow: 'hidden',
649
+ }}
650
+ >
651
+ <div style={{ padding: '4px 8px', borderBottom: '1px solid var(--bd-muted)', fontSize: 8, color: 'var(--tx-ghost)', fontFamily: 'inherit' }}>
652
+ ✦ semantic matches
653
+ </div>
654
+ {semanticResults.map((r) => {
655
+ const node = graph?.nodes.find((n) => n.path === r.filePath || n.path.endsWith(r.filePath) || r.filePath.endsWith(n.path));
656
+ return (
657
+ <div
658
+ key={r.id}
659
+ onClick={() => { if (node) { handleSelect(node.id); setSemanticResults([]); setSearch(''); } }}
660
+ style={{
661
+ padding: '5px 8px',
662
+ cursor: node ? 'pointer' : 'default',
663
+ borderBottom: '1px solid var(--bd-faint)',
664
+ display: 'flex',
665
+ flexDirection: 'column',
666
+ gap: 2,
667
+ opacity: node ? 1 : 0.4,
668
+ }}
669
+ onMouseEnter={(e) => { if (node) e.currentTarget.style.background = 'var(--bg-hover)'; }}
670
+ onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}
671
+ >
672
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
673
+ <span style={{ fontSize: 9, color: 'var(--tx-primary)', fontFamily: "'JetBrains Mono',monospace" }}>{r.name}</span>
674
+ <span style={{ fontSize: 8, color: 'var(--tx-dim)', fontFamily: 'inherit' }}>{(1 - r.score).toFixed(2)}</span>
675
+ </div>
676
+ <span style={{ fontSize: 8, color: 'var(--tx-ghost)', fontFamily: 'inherit', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
677
+ {r.filePath.split('/').slice(-2).join('/')}
678
+ </span>
679
+ </div>
680
+ );
681
+ })}
682
+ </div>
683
+ )}
684
+ </div>
685
+ <button
686
+ onClick={() => {
687
+ setRawGraph(null);
688
+ setSelectedId(null);
689
+ }}
690
+ style={{
691
+ background: 'none',
692
+ border: '1px solid var(--bd-muted)',
693
+ borderRadius: 4,
694
+ color: 'var(--tx-ghost)',
695
+ fontSize: 8,
696
+ padding: '3px 8px',
697
+ cursor: 'pointer',
698
+ fontFamily: 'inherit',
699
+ letterSpacing: '0.06em',
700
+ }}
701
+ >
702
+ LOAD
703
+ </button>
704
+ <button
705
+ onClick={() => mappingRef.current.click()}
706
+ style={{
707
+ background: mapping ? 'var(--bg-select)' : 'none',
708
+ border: `1px solid ${mapping ? 'var(--ac-teal)' : 'var(--bd-muted)'}`,
709
+ borderRadius: 4,
710
+ color: mapping ? 'var(--ac-teal)' : 'var(--tx-ghost)',
711
+ fontSize: 8,
712
+ padding: '3px 8px',
713
+ cursor: 'pointer',
714
+ fontFamily: 'inherit',
715
+ letterSpacing: '0.06em',
716
+ }}
717
+ title="Load mapping.json"
718
+ >
719
+ {mapping ? '[x] MAP' : 'MAP'}
720
+ </button>
721
+ <button
722
+ onClick={() => specRef.current.click()}
723
+ style={{
724
+ background: Object.keys(specReqs).length ? 'var(--bg-select)' : 'none',
725
+ border: `1px solid ${Object.keys(specReqs).length ? 'var(--ac-primary)' : 'var(--bd-muted)'}`,
726
+ borderRadius: 4,
727
+ color: Object.keys(specReqs).length ? 'var(--ac-primary)' : 'var(--tx-ghost)',
728
+ fontSize: 8,
729
+ padding: '3px 8px',
730
+ cursor: 'pointer',
731
+ fontFamily: 'inherit',
732
+ letterSpacing: '0.06em',
733
+ }}
734
+ title="Load spec.md"
735
+ >
736
+ {Object.keys(specReqs).length ? '[x] SPEC' : 'SPEC'}
737
+ </button>
738
+ <button
739
+ onClick={() => setChatOpen((v) => !v)}
740
+ style={{
741
+ background: chatOpen ? 'var(--bg-select)' : 'none',
742
+ border: `1px solid ${chatOpen ? 'var(--ac-primary)' : 'var(--bd-muted)'}`,
743
+ borderRadius: 4,
744
+ color: chatOpen ? 'var(--ac-primary)' : 'var(--tx-ghost)',
745
+ fontSize: 8,
746
+ padding: '3px 8px',
747
+ cursor: 'pointer',
748
+ fontFamily: 'inherit',
749
+ letterSpacing: '0.06em',
750
+ }}
751
+ title="Toggle AI chat"
752
+ >
753
+ CHAT
754
+ </button>
755
+ <button
756
+ onClick={cycleTheme}
757
+ title="Cycle theme"
758
+ style={{
759
+ background: 'none',
760
+ border: '1px solid var(--bd-muted)',
761
+ borderRadius: 4,
762
+ color: 'var(--ac-primary)',
763
+ fontSize: 8,
764
+ padding: '3px 8px',
765
+ cursor: 'pointer',
766
+ fontFamily: 'inherit',
767
+ letterSpacing: '0.06em',
768
+ }}
769
+ >
770
+ {theme.label}
771
+ </button>
772
+ <input
773
+ ref={mappingRef}
774
+ type="file"
775
+ accept=".json"
776
+ style={{ display: 'none' }}
777
+ onChange={(e) => {
778
+ const f = e.target.files[0];
779
+ if (f) {
780
+ const r = new FileReader();
781
+ r.onload = (ev) => loadMapping(ev.target.result);
782
+ r.readAsText(f);
783
+ }
784
+ }}
785
+ />
786
+ <input
787
+ ref={specRef}
788
+ type="file"
789
+ accept=".md"
790
+ style={{ display: 'none' }}
791
+ onChange={(e) => {
792
+ const f = e.target.files[0];
793
+ if (f) {
794
+ const r = new FileReader();
795
+ r.onload = (ev) => loadSpec(ev.target.result);
796
+ r.readAsText(f);
797
+ }
798
+ }}
799
+ />
800
+ </div>
801
+
802
+ {/* Filter bar */}
803
+ {viewMode !== 'architecture' && viewMode !== 'classes' && (
804
+ <FilterBar
805
+ filters={filters}
806
+ setFilters={setFilters}
807
+ stats={filterStats}
808
+ clusterNames={clusterNames}
809
+ />
810
+ )}
811
+
812
+ {/* Body */}
813
+ <div style={{ flex: 1, display: 'flex', overflow: 'hidden' }}>
814
+ {/* Canvas */}
815
+ <div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
816
+ {viewMode === 'architecture' ? (
817
+ <ArchitectureView graph={graph} llmCtx={llmCtx} focusedIds={focusedIds} />
818
+ ) : viewMode === 'classes' ? (
819
+ <ClassGraph
820
+ classData={classData}
821
+ selectedClassId={selectedClassId}
822
+ onSelectClass={setSelectedClass}
823
+ focusedPaths={focusedPaths}
824
+ onClear={() => setFocusedPaths([])}
825
+ />
826
+ ) : viewMode === 'clusters' ? (
827
+ <ClusterGraph
828
+ clusters={displayClusters.filter(
829
+ (cl) => !filters.cluster || cl.name === filters.cluster
830
+ )}
831
+ edges={visibleEdges}
832
+ nodes={visibleNodes}
833
+ allNodes={graph.nodes.filter(
834
+ (n) => !filters.cluster || n.cluster.name === filters.cluster
835
+ )}
836
+ expandedClusters={expandedClusters}
837
+ onToggle={toggleCluster}
838
+ onSelectNode={handleSelect}
839
+ onClear={clearSelection}
840
+ hasSelection={selectedId !== null || expandedClusters.size > 0}
841
+ selectedId={selectedId}
842
+ affectedIds={affectedIds}
843
+ linkedIds={linkedIds}
844
+ focusedIds={focusedIds}
845
+ noGlow={themeName === 'light' || themeName === 'warm'}
846
+ />
847
+ ) : (
848
+ <FlatGraph
849
+ nodes={visibleNodes}
850
+ edges={visibleEdges}
851
+ selectedId={selectedId}
852
+ affectedIds={affectedIds}
853
+ focusedIds={focusedIds}
854
+ onSelect={handleSelect}
855
+ refactorOnly={filters.refactorOnly}
856
+ linkedIds={linkedIds}
857
+ noGlow={themeName === 'light' || themeName === 'warm'}
858
+ />
859
+ )}
860
+ {!selectedId && (
861
+ <div
862
+ style={{
863
+ position: 'absolute',
864
+ bottom: 12,
865
+ left: '50%',
866
+ transform: 'translateX(-50%)',
867
+ fontSize: 9,
868
+ color: 'var(--bd-edge)',
869
+ letterSpacing: '0.1em',
870
+ pointerEvents: 'none',
871
+ whiteSpace: 'nowrap',
872
+ }}
873
+ >
874
+ {viewMode === 'clusters'
875
+ ? 'CLICK CLUSTER -> EXPAND · CLICK NODE -> INSPECT'
876
+ : viewMode === 'classes'
877
+ ? 'CLICK CLASS -> EXPAND METHODS · DBL-CLICK -> RESET VIEW'
878
+ : 'CLICK NODE -> INSPECT'}
879
+ </div>
880
+ )}
881
+ </div>
882
+
883
+ {/* Chat panel */}
884
+ {chatOpen && (
885
+ <ChatPanel
886
+ onHighlight={(ids) => setFocusedIds(ids)}
887
+ onHighlightPaths={(paths) => setFocusedPaths(paths)}
888
+ onClose={() => { setChatOpen(false); setFocusedIds([]); setFocusedPaths([]); }}
889
+ onClearGraph={() => {
890
+ setFocusedIds([]);
891
+ setFocusedPaths([]);
892
+ setExpandedClusters(new Set());
893
+ setSelectedId(null);
894
+ setAffectedIds([]);
895
+ }}
896
+ />
897
+ )}
898
+
899
+ {/* Side panel */}
900
+ <div
901
+ style={{
902
+ width: 282,
903
+ borderLeft: '1px solid var(--bd-faint)',
904
+ background: 'var(--bg-deep)',
905
+ display: viewMode === 'architecture' || viewMode === 'classes' ? 'none' : 'flex',
906
+ flexDirection: 'column',
907
+ overflow: 'hidden',
908
+ flexShrink: 0,
909
+ }}
910
+ >
911
+ <div style={{ display: 'flex', borderBottom: '1px solid var(--bd-faint)', flexShrink: 0 }}>
912
+ {['node', 'links', 'blast', 'spec', 'skeleton', 'info'].map((t) => (
913
+ <button
914
+ key={t}
915
+ onClick={() => setTab(t)}
916
+ style={{
917
+ flex: 1,
918
+ padding: '7px 0',
919
+ background: 'none',
920
+ border: 'none',
921
+ borderBottom: tab === t ? '2px solid var(--ac-primary)' : '2px solid transparent',
922
+ color: tab === t ? 'var(--tx-primary)' : 'var(--tx-ghost)',
923
+ fontSize: 8,
924
+ letterSpacing: '0.06em',
925
+ fontWeight: 700,
926
+ cursor: 'pointer',
927
+ fontFamily: 'inherit',
928
+ textTransform: 'uppercase',
929
+ }}
930
+ >
931
+ {t}
932
+ </button>
933
+ ))}
934
+ </div>
935
+
936
+ <div style={{ flex: 1, overflow: 'auto', padding: 13 }}>
937
+ {/* NODE */}
938
+ {tab === 'node' && !selectedNode && <Hint>Select a node to inspect it.</Hint>}
939
+ {tab === 'node' && selectedNode && (
940
+ <div>
941
+ <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--tx-bright)', marginBottom: 2 }}>
942
+ {selectedNode.label}
943
+ </div>
944
+ <div
945
+ style={{
946
+ fontSize: 8,
947
+ color: 'var(--tx-ghost)',
948
+ marginBottom: 9,
949
+ wordBreak: 'break-all',
950
+ lineHeight: 1.7,
951
+ }}
952
+ >
953
+ {selectedNode.path}
954
+ </div>
955
+ <Row
956
+ label="ext"
957
+ value={<Chip color={extColor(selectedNode.ext)}>{selectedNode.ext || '--'}</Chip>}
958
+ />
959
+ <Row label="lines" value={selectedNode.lines} />
960
+ <Row label="size" value={`${(selectedNode.size / 1024).toFixed(1)} KB`} />
961
+ <Row
962
+ label="score"
963
+ value={
964
+ <span style={{ color: 'var(--ac-primary)', fontWeight: 700 }}>{selectedNode.score}</span>
965
+ }
966
+ />
967
+ <Row
968
+ label="cluster"
969
+ value={
970
+ <Chip color={selectedNode.cluster.color}>{selectedNode.cluster.name}</Chip>
971
+ }
972
+ />
973
+ <div style={{ display: 'flex', gap: 4, marginTop: 8, flexWrap: 'wrap' }}>
974
+ {selectedNode.isEntry && <Chip color="#f77c6a">entry-point</Chip>}
975
+ {selectedNode.isConfig && <Chip color="#f5c518">config</Chip>}
976
+ {selectedNode.isTest && <Chip color="#3ecfcf">test</Chip>}
977
+ {selectedNode.tags.map((t) => (
978
+ <Chip key={t} color="#4a5070">
979
+ {t}
980
+ </Chip>
981
+ ))}
982
+ </div>
983
+ {selectedNode.exports.length > 0 && (
984
+ <>
985
+ <SL>Exports ({selectedNode.exports.length})</SL>
986
+ {selectedNode.exports.map((ex, i) => (
987
+ <div
988
+ key={i}
989
+ style={{
990
+ display: 'flex',
991
+ gap: 5,
992
+ alignItems: 'center',
993
+ padding: '3px 0',
994
+ borderBottom: '1px solid var(--bd-faint)',
995
+ }}
996
+ >
997
+ <KindBadge kind={ex.kind} />
998
+ <span style={{ fontSize: 9, color: 'var(--tx-secondary)' }}>{ex.name}</span>
999
+ <span style={{ marginLeft: 'auto', fontSize: 8, color: 'var(--tx-faint)' }}>
1000
+ L{ex.line}
1001
+ </span>
1002
+ </div>
1003
+ ))}
1004
+ </>
1005
+ )}
1006
+ <SL>Metrics</SL>
1007
+ {[
1008
+ ['inDegree', '↙'],
1009
+ ['outDegree', '↗'],
1010
+ ['pageRank', 'PR'],
1011
+ ['betweenness', '⋈'],
1012
+ ].map(([k, s]) => (
1013
+ <Row
1014
+ key={k}
1015
+ label={`${s} ${k}`}
1016
+ value={
1017
+ typeof selectedNode.metrics[k] === 'number'
1018
+ ? selectedNode.metrics[k].toFixed(3)
1019
+ : '-'
1020
+ }
1021
+ />
1022
+ ))}
1023
+ {selectedNode.refactor && (
1024
+ <>
1025
+ <SL>Refactor</SL>
1026
+ <Row label="Functions affected" value={selectedNode.refactor.functions} />
1027
+ <Row
1028
+ label="Max priority"
1029
+ value={
1030
+ <span
1031
+ style={{
1032
+ color: selectedNode.refactor.maxPriority >= 5 ? '#f97373' : '#fbbf24',
1033
+ fontWeight: 700,
1034
+ }}
1035
+ >
1036
+ {selectedNode.refactor.maxPriority.toFixed(1)}
1037
+ </span>
1038
+ }
1039
+ />
1040
+ <div style={{ marginTop: 6, display: 'flex', flexWrap: 'wrap', gap: 4 }}>
1041
+ {selectedNode.refactor.issues.map((iss) => (
1042
+ <Chip key={iss} color="#f97373">
1043
+ {iss.replace(/_/g, ' ')}
1044
+ </Chip>
1045
+ ))}
1046
+ </div>
1047
+ </>
1048
+ )}
1049
+ </div>
1050
+ )}
1051
+
1052
+ {/* LINKS */}
1053
+ {tab === 'links' && !selectedId && (
1054
+ <Hint>Select a node to see its direct imports/exports.</Hint>
1055
+ )}
1056
+ {tab === 'links' && selectedId && (
1057
+ <div>
1058
+ {(() => {
1059
+ const outEdges = selectedEdges.filter((e) => e.source === selectedId);
1060
+ const inEdges = selectedEdges.filter((e) => e.target === selectedId);
1061
+ return (
1062
+ <>
1063
+ <SL>Imports ({outEdges.length})</SL>
1064
+ {outEdges.length === 0 && (
1065
+ <div style={{ color: 'var(--tx-faint)', fontSize: 9 }}>No imports.</div>
1066
+ )}
1067
+ {outEdges.map((e, i) => {
1068
+ const tn = graph.nodes.find((n) => n.id === e.target);
1069
+ return (
1070
+ <div
1071
+ key={i}
1072
+ onClick={() => handleSelect(e.target)}
1073
+ style={{
1074
+ padding: '5px 7px',
1075
+ marginBottom: 3,
1076
+ background: 'var(--bg-input)',
1077
+ borderRadius: 4,
1078
+ border: `1px solid ${tn?.cluster.color || 'var(--bd-muted)'}22`,
1079
+ cursor: 'pointer',
1080
+ }}
1081
+ >
1082
+ <div
1083
+ style={{
1084
+ display: 'flex',
1085
+ alignItems: 'center',
1086
+ gap: 5,
1087
+ marginBottom: e.importedNames.length ? 3 : 0,
1088
+ }}
1089
+ >
1090
+ <span style={{ fontSize: 8, color: extColor(tn?.ext || '') }}>↗</span>
1091
+ <span style={{ fontSize: 9, color: 'var(--tx-primary)' }}>
1092
+ {tn?.label || e.target}
1093
+ </span>
1094
+ {e.isType && (
1095
+ <span style={{ fontSize: 7, color: 'var(--tx-ghost)', marginLeft: 'auto' }}>
1096
+ type
1097
+ </span>
1098
+ )}
1099
+ </div>
1100
+ {e.importedNames.length > 0 && (
1101
+ <div style={{ fontSize: 7.5, color: 'var(--tx-dim)', paddingLeft: 12 }}>
1102
+ {e.importedNames.join(', ')}
1103
+ </div>
1104
+ )}
1105
+ </div>
1106
+ );
1107
+ })}
1108
+ <SL>Imported by ({inEdges.length})</SL>
1109
+ {inEdges.length === 0 && (
1110
+ <div style={{ color: 'var(--tx-faint)', fontSize: 9 }}>
1111
+ Not imported by any visible files.
1112
+ </div>
1113
+ )}
1114
+ {inEdges.map((e, i) => {
1115
+ const sn = graph.nodes.find((n) => n.id === e.source);
1116
+ return (
1117
+ <div
1118
+ key={i}
1119
+ onClick={() => handleSelect(e.source)}
1120
+ style={{
1121
+ padding: '5px 7px',
1122
+ marginBottom: 3,
1123
+ background: 'var(--bg-input)',
1124
+ borderRadius: 4,
1125
+ border: `1px solid ${sn?.cluster.color || 'var(--bd-muted)'}22`,
1126
+ cursor: 'pointer',
1127
+ }}
1128
+ >
1129
+ <div
1130
+ style={{
1131
+ display: 'flex',
1132
+ alignItems: 'center',
1133
+ gap: 5,
1134
+ marginBottom: e.importedNames.length ? 3 : 0,
1135
+ }}
1136
+ >
1137
+ <span style={{ fontSize: 8, color: 'var(--ac-primary)' }}>↙</span>
1138
+ <span style={{ fontSize: 9, color: 'var(--tx-primary)' }}>
1139
+ {sn?.label || e.source}
1140
+ </span>
1141
+ {e.isType && (
1142
+ <span style={{ fontSize: 7, color: 'var(--tx-ghost)', marginLeft: 'auto' }}>
1143
+ type
1144
+ </span>
1145
+ )}
1146
+ </div>
1147
+ {e.importedNames.length > 0 && (
1148
+ <div style={{ fontSize: 7.5, color: 'var(--tx-dim)', paddingLeft: 12 }}>
1149
+ {e.importedNames.join(', ')}
1150
+ </div>
1151
+ )}
1152
+ </div>
1153
+ );
1154
+ })}
1155
+ </>
1156
+ );
1157
+ })()}
1158
+ </div>
1159
+ )}
1160
+
1161
+ {/* BLAST */}
1162
+ {tab === 'blast' && !selectedId && (
1163
+ <Hint>Select a node to compute downstream impact.</Hint>
1164
+ )}
1165
+ {tab === 'blast' && selectedId && (
1166
+ <div>
1167
+ <div style={{ fontSize: 9, color: 'var(--tx-secondary)', marginBottom: 10 }}>
1168
+ Modifying <span style={{ color: 'var(--ac-primary)' }}>{selectedNode?.label}</span> impacts:
1169
+ </div>
1170
+ {affectedIds.length === 0 ? (
1171
+ <div style={{ color: 'var(--tx-faint)', fontSize: 9 }}>No visible downstream nodes.</div>
1172
+ ) : (
1173
+ affectedIds.map((id) => {
1174
+ const n = graph.nodes.find((x) => x.id === id);
1175
+ return (
1176
+ <div
1177
+ key={id}
1178
+ onClick={() => handleSelect(id)}
1179
+ style={{
1180
+ display: 'flex',
1181
+ alignItems: 'center',
1182
+ gap: 6,
1183
+ padding: '4px 7px',
1184
+ marginBottom: 3,
1185
+ background: 'var(--bg-input)',
1186
+ borderRadius: 4,
1187
+ border: '1px solid var(--bd-muted)',
1188
+ cursor: 'pointer',
1189
+ }}
1190
+ >
1191
+ <span style={{ fontSize: 8, color: extColor(n?.ext || '') }}>
1192
+ {n?.ext || '?'}
1193
+ </span>
1194
+ <span
1195
+ style={{
1196
+ fontSize: 9,
1197
+ color: 'var(--tx-primary)',
1198
+ flex: 1,
1199
+ overflow: 'hidden',
1200
+ textOverflow: 'ellipsis',
1201
+ whiteSpace: 'nowrap',
1202
+ }}
1203
+ >
1204
+ {n?.label || id}
1205
+ </span>
1206
+ <span style={{ fontSize: 7, color: `${n?.cluster.color || '#3a3f5c'}80` }}>
1207
+ {n?.cluster.name.split('/').pop()}
1208
+ </span>
1209
+ </div>
1210
+ );
1211
+ })
1212
+ )}
1213
+ <div
1214
+ style={{
1215
+ marginTop: 10,
1216
+ padding: '8px 10px',
1217
+ background: 'var(--bg-input)',
1218
+ borderRadius: 5,
1219
+ border: '1px solid var(--bd-muted)',
1220
+ }}
1221
+ >
1222
+ <div style={{ fontSize: 8, color: 'var(--tx-ghost)', marginBottom: 2 }}>BLAST RADIUS</div>
1223
+ <div
1224
+ style={{
1225
+ fontSize: 22,
1226
+ fontWeight: 700,
1227
+ color:
1228
+ affectedIds.length > 8
1229
+ ? '#f77c6a'
1230
+ : affectedIds.length > 3
1231
+ ? '#f7c76a'
1232
+ : 'var(--ac-primary)',
1233
+ }}
1234
+ >
1235
+ {affectedIds.length}{' '}
1236
+ <span style={{ fontSize: 10, fontWeight: 400, color: 'var(--tx-ghost)' }}>nodes</span>
1237
+ </div>
1238
+ </div>
1239
+ </div>
1240
+ )}
1241
+
1242
+ {/* SPEC */}
1243
+ {tab === 'spec' && !mapping && (
1244
+ <Hint>
1245
+ Load a <code style={{ color: 'var(--ac-primary)' }}>mapping.json</code> and{' '}
1246
+ <code style={{ color: 'var(--ac-primary)' }}>spec.md</code> using the MAP / SPEC buttons in
1247
+ the top bar.
1248
+ </Hint>
1249
+ )}
1250
+ {tab === 'spec' && mapping && !selectedId && (
1251
+ <Hint>Select a node to see its linked spec requirements.</Hint>
1252
+ )}
1253
+ {tab === 'spec' &&
1254
+ mapping &&
1255
+ selectedId &&
1256
+ (() => {
1257
+ const nodePath = normalizePath(selectedNode?.path || selectedId);
1258
+ const entries = [];
1259
+ for (const [k, list] of Object.entries(mapping)) {
1260
+ if (nodePath.endsWith(k) || k.endsWith(nodePath) || nodePath === k) {
1261
+ entries.push(...list);
1262
+ }
1263
+ }
1264
+ const seen = new Set();
1265
+ const unique = entries.filter((e) => {
1266
+ const key = e.requirement;
1267
+ if (seen.has(key)) return false;
1268
+ seen.add(key);
1269
+ return true;
1270
+ });
1271
+
1272
+ if (unique.length === 0)
1273
+ return <Hint>No spec requirements mapped to this file.</Hint>;
1274
+
1275
+ const confidenceColor = (c) => (c === 'llm' ? '#4ade80' : 'var(--tx-ghost)');
1276
+
1277
+ return (
1278
+ <div>
1279
+ <div style={{ fontSize: 8, color: 'var(--tx-ghost)', marginBottom: 8 }}>
1280
+ {unique.length} requirement{unique.length > 1 ? 's' : ''} linked
1281
+ </div>
1282
+ {unique.map((entry, i) => {
1283
+ const req = specReqs ? specReqs[entry.requirement] : null;
1284
+ const domainColor =
1285
+ {
1286
+ llm: '#3ecfcf',
1287
+ task: '#f7c76a',
1288
+ project: '#6af7a0',
1289
+ openspec: '#7c6af7',
1290
+ }[entry.domain] || '#64748b';
1291
+ return (
1292
+ <div
1293
+ key={i}
1294
+ style={{
1295
+ marginBottom: 10,
1296
+ background: 'var(--bg-node)',
1297
+ borderRadius: 5,
1298
+ border: '1px solid var(--bd-muted)',
1299
+ overflow: 'hidden',
1300
+ }}
1301
+ >
1302
+ <div
1303
+ style={{
1304
+ padding: '6px 9px',
1305
+ borderBottom: '1px solid var(--bd-faint)',
1306
+ display: 'flex',
1307
+ alignItems: 'center',
1308
+ gap: 5,
1309
+ flexWrap: 'wrap',
1310
+ }}
1311
+ >
1312
+ <span
1313
+ style={{ fontSize: 9, fontWeight: 700, color: 'var(--tx-primary)', flex: 1 }}
1314
+ >
1315
+ {entry.requirement}
1316
+ </span>
1317
+ <span
1318
+ style={{
1319
+ fontSize: 7,
1320
+ padding: '1px 5px',
1321
+ borderRadius: 3,
1322
+ background: `${domainColor}18`,
1323
+ color: domainColor,
1324
+ border: `1px solid ${domainColor}30`,
1325
+ }}
1326
+ >
1327
+ {entry.domain}
1328
+ </span>
1329
+ <span
1330
+ style={{ fontSize: 7, color: confidenceColor(entry.confidence) }}
1331
+ title={`confidence: ${entry.confidence}`}
1332
+ >
1333
+ {entry.confidence === 'llm' ? '● llm' : '◌ heuristic'}
1334
+ </span>
1335
+ </div>
1336
+ {req?.body ? (
1337
+ <div
1338
+ style={{
1339
+ padding: '7px 9px',
1340
+ fontSize: 8.5,
1341
+ color: 'var(--tx-secondary)',
1342
+ lineHeight: 1.7,
1343
+ maxHeight: 200,
1344
+ overflow: 'auto',
1345
+ }}
1346
+ >
1347
+ {req.body.split('\n').map((line, li) => {
1348
+ if (line.startsWith('####'))
1349
+ return (
1350
+ <div
1351
+ key={li}
1352
+ style={{
1353
+ color: 'var(--tx-node)',
1354
+ fontWeight: 700,
1355
+ marginTop: 6,
1356
+ fontSize: 8,
1357
+ }}
1358
+ >
1359
+ {line.replace(/^#+\s*/, '')}
1360
+ </div>
1361
+ );
1362
+ if (line.startsWith('- **'))
1363
+ return (
1364
+ <div key={li} style={{ paddingLeft: 6, color: 'var(--tx-secondary)' }}>
1365
+ {line.replace(/\*\*/g, '')}
1366
+ </div>
1367
+ );
1368
+ if (line.trim() === '')
1369
+ return <div key={li} style={{ height: 4 }} />;
1370
+ return <div key={li}>{line}</div>;
1371
+ })}
1372
+ </div>
1373
+ ) : (
1374
+ <div style={{ padding: '7px 9px', fontSize: 8, color: 'var(--tx-faint)' }}>
1375
+ {req
1376
+ ? 'Requirement title mismatch — spec section not found in the spec file.'
1377
+ : <>Spec not loaded — run <code style={{ color: 'var(--ac-primary)' }}>openlore view</code> or load <code style={{ color: 'var(--ac-primary)' }}>spec.md</code> manually.</>}
1378
+ </div>
1379
+ )}
1380
+ <div
1381
+ style={{
1382
+ padding: '4px 9px',
1383
+ borderTop: '1px solid var(--bd-faint)',
1384
+ fontSize: 7.5,
1385
+ color: 'var(--ac-cluster-arr)',
1386
+ }}
1387
+ >
1388
+ service: <span style={{ color: 'var(--tx-dim)' }}>{entry.service}</span>
1389
+ </div>
1390
+ </div>
1391
+ );
1392
+ })}
1393
+ </div>
1394
+ );
1395
+ })()}
1396
+
1397
+ {/* SKELETON */}
1398
+ {tab === 'skeleton' && !selectedNode && (
1399
+ <Hint>Select a node to view its code skeleton.</Hint>
1400
+ )}
1401
+ {tab === 'skeleton' && selectedNode && (
1402
+ <div>
1403
+ {skeletonLoading && <Hint>Loading...</Hint>}
1404
+ {!skeletonLoading && !skeletonData && <Hint>Skeleton unavailable for this file.</Hint>}
1405
+ {!skeletonLoading && skeletonData && (
1406
+ <div>
1407
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
1408
+ <span style={{ fontSize: 9, color: 'var(--tx-muted)', fontFamily: 'inherit' }}>
1409
+ {skeletonData.language} · {skeletonData.skeletonLines}/{skeletonData.originalLines} lines
1410
+ </span>
1411
+ <span style={{ fontSize: 9, color: skeletonData.reductionPct >= 20 ? 'var(--ac-primary)' : 'var(--tx-ghost)', fontFamily: 'inherit' }}>
1412
+ -{skeletonData.reductionPct}%
1413
+ </span>
1414
+ </div>
1415
+ <pre style={{
1416
+ margin: 0,
1417
+ fontSize: 8,
1418
+ lineHeight: 1.6,
1419
+ color: 'var(--tx-secondary)',
1420
+ fontFamily: "'JetBrains Mono', monospace",
1421
+ whiteSpace: 'pre-wrap',
1422
+ wordBreak: 'break-word',
1423
+ background: 'var(--bg-deep)',
1424
+ border: '1px solid var(--bd-faint)',
1425
+ borderRadius: 4,
1426
+ padding: '8px 10px',
1427
+ }}>
1428
+ {skeletonData.skeleton}
1429
+ </pre>
1430
+ </div>
1431
+ )}
1432
+ </div>
1433
+ )}
1434
+
1435
+ {/* INFO */}
1436
+ {tab === 'info' && (
1437
+ <div>
1438
+ <SL>Statistics</SL>
1439
+ {[
1440
+ ['Nodes', stats.nodeCount],
1441
+ ['Edges', stats.edgeCount],
1442
+ ['Clusters', stats.structuralClusterCount ?? displayClusters.length],
1443
+ ['Cycles', stats.cycleCount],
1444
+ ['Avg degree', stats.avgDegree?.toFixed(2)],
1445
+ ['Density', stats.density?.toFixed(4)],
1446
+ ].map(([l, v]) => (
1447
+ <Row key={l} label={l} value={v ?? '-'} />
1448
+ ))}
1449
+ <SL>Active filters</SL>
1450
+ <Row
1451
+ label="Visible nodes"
1452
+ value={<span style={{ color: 'var(--ac-primary)' }}>{filterStats.visible}</span>}
1453
+ />
1454
+ <Row
1455
+ label="Visible edges"
1456
+ value={<span style={{ color: 'var(--ac-teal)' }}>{filterStats.visibleEdges}</span>}
1457
+ />
1458
+ <Row label="Orphans" value={filterStats.orphanCount} />
1459
+ <SL>Top 10 by score</SL>
1460
+ {(graph.rankings.byImportance || []).slice(0, 10).map((fid, i) => {
1461
+ const n = graph.nodes.find((x) => x.id === fid);
1462
+ if (!n) return null;
1463
+ return (
1464
+ <div
1465
+ key={fid}
1466
+ onClick={() => handleSelect(fid)}
1467
+ style={{
1468
+ display: 'flex',
1469
+ gap: 5,
1470
+ alignItems: 'center',
1471
+ padding: '3px 0',
1472
+ cursor: 'pointer',
1473
+ }}
1474
+ >
1475
+ <span style={{ fontSize: 8, color: 'var(--tx-faint)', minWidth: 12 }}>{i + 1}</span>
1476
+ <span style={{ fontSize: 8, color: extColor(n.ext) }}>{n.ext || '—'}</span>
1477
+ <span
1478
+ style={{
1479
+ fontSize: 9,
1480
+ color: 'var(--tx-secondary)',
1481
+ flex: 1,
1482
+ overflow: 'hidden',
1483
+ textOverflow: 'ellipsis',
1484
+ whiteSpace: 'nowrap',
1485
+ }}
1486
+ >
1487
+ {n.label}
1488
+ </span>
1489
+ <span style={{ fontSize: 9, color: 'var(--ac-primary)' }}>{n.score}</span>
1490
+ </div>
1491
+ );
1492
+ })}
1493
+ </div>
1494
+ )}
1495
+ </div>
1496
+
1497
+ {/* Cluster legend */}
1498
+ <div style={{ padding: '9px 13px', borderTop: '1px solid var(--bd-faint)', flexShrink: 0 }}>
1499
+ <div style={{ display: 'flex', gap: 12, marginBottom: 8 }}>
1500
+ <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
1501
+ <svg width="24" height="8" style={{ overflow: 'visible' }}>
1502
+ <line
1503
+ x1="0"
1504
+ y1="4"
1505
+ x2="18"
1506
+ y2="4"
1507
+ stroke="var(--tx-node)"
1508
+ strokeWidth="1.5"
1509
+ markerEnd="url(#arr-legend)"
1510
+ />
1511
+ <defs>
1512
+ <marker
1513
+ id="arr-legend"
1514
+ markerWidth="5"
1515
+ markerHeight="5"
1516
+ refX="4"
1517
+ refY="2.5"
1518
+ orient="auto"
1519
+ >
1520
+ <path d="M0,0 L0,5 L5,2.5z" style={{ fill: 'var(--tx-node)' }} />
1521
+ </marker>
1522
+ </defs>
1523
+ </svg>
1524
+ <span style={{ fontSize: 7.5, color: 'var(--tx-ghost)' }}>runtime import</span>
1525
+ </div>
1526
+ <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
1527
+ <svg width="24" height="8" style={{ overflow: 'visible' }}>
1528
+ <line
1529
+ x1="0"
1530
+ y1="4"
1531
+ x2="18"
1532
+ y2="4"
1533
+ stroke="var(--tx-ghost)"
1534
+ strokeWidth="1.2"
1535
+ strokeDasharray="3 2"
1536
+ markerEnd="url(#arr-legend-type)"
1537
+ />
1538
+ <defs>
1539
+ <marker
1540
+ id="arr-legend-type"
1541
+ markerWidth="5"
1542
+ markerHeight="5"
1543
+ refX="4"
1544
+ refY="2.5"
1545
+ orient="auto"
1546
+ >
1547
+ <path d="M0,0 L0,5 L5,2.5z" style={{ fill: 'var(--tx-ghost)' }} />
1548
+ </marker>
1549
+ </defs>
1550
+ </svg>
1551
+ <span style={{ fontSize: 7.5, color: 'var(--tx-ghost)' }}>type-only</span>
1552
+ </div>
1553
+ </div>
1554
+ <div
1555
+ style={{ fontSize: 8, color: 'var(--ac-arrow)', letterSpacing: '0.08em', marginBottom: 5 }}
1556
+ >
1557
+ CLUSTERS · click to filter
1558
+ </div>
1559
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
1560
+ {displayClusters.map((cl) => (
1561
+ <div
1562
+ key={cl.id}
1563
+ onClick={() =>
1564
+ setFilters((f) => ({ ...f, cluster: f.cluster === cl.name ? '' : cl.name }))
1565
+ }
1566
+ style={{
1567
+ display: 'flex',
1568
+ alignItems: 'center',
1569
+ gap: 3,
1570
+ cursor: 'pointer',
1571
+ opacity: filters.cluster && filters.cluster !== cl.name ? 0.25 : 1,
1572
+ transition: 'opacity 0.15s',
1573
+ }}
1574
+ >
1575
+ <div
1576
+ style={{
1577
+ width: 5,
1578
+ height: 5,
1579
+ borderRadius: '50%',
1580
+ background: cl.color,
1581
+ boxShadow: filters.cluster === cl.name ? `0 0 5px ${cl.color}` : 'none',
1582
+ }}
1583
+ />
1584
+ <span
1585
+ style={{
1586
+ fontSize: 7.5,
1587
+ color: filters.cluster === cl.name ? cl.color : 'var(--tx-ghost)',
1588
+ }}
1589
+ >
1590
+ {cl.name}
1591
+ </span>
1592
+ </div>
1593
+ ))}
1594
+ </div>
1595
+ </div>
1596
+ </div>
1597
+ </div>
1598
+ </div>
1599
+ );
1600
+ }