toolnet-memory 0.2.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 (329) hide show
  1. package/.env.example +28 -0
  2. package/README.md +147 -0
  3. package/bin/toolnet-memory +415 -0
  4. package/dist/packages/cli/commands.js +1 -0
  5. package/dist/packages/cli/index.js +1 -0
  6. package/dist/packages/sdk/client.js +78 -0
  7. package/dist/packages/sdk/index.js +1 -0
  8. package/dist/packages/sdk/types.js +1 -0
  9. package/dist/src/api/index.js +1 -0
  10. package/dist/src/api/routes/health.js +1 -0
  11. package/dist/src/api/routes/index.js +1 -0
  12. package/dist/src/api/routes/memory.js +1 -0
  13. package/dist/src/api/routes/projects.js +1 -0
  14. package/dist/src/api/routes/search.js +1 -0
  15. package/dist/src/api/server.js +1 -0
  16. package/dist/src/capture/activity-capture.js +21 -0
  17. package/dist/src/capture/command-capture.js +12 -0
  18. package/dist/src/capture/error-capture.js +14 -0
  19. package/dist/src/capture/event-deduplicator.js +28 -0
  20. package/dist/src/capture/event-queue.js +20 -0
  21. package/dist/src/capture/file-capture.js +23 -0
  22. package/dist/src/capture/index.js +8 -0
  23. package/dist/src/capture/session-capture.js +18 -0
  24. package/dist/src/capture/tool-capture.js +13 -0
  25. package/dist/src/code-intelligence/analysis/analysis-engine.js +26 -0
  26. package/dist/src/code-intelligence/analysis/dead-code-analyzer.js +127 -0
  27. package/dist/src/code-intelligence/analysis/dependency-analyzer.js +66 -0
  28. package/dist/src/code-intelligence/analysis/index.js +4 -0
  29. package/dist/src/code-intelligence/analysis/types.js +1 -0
  30. package/dist/src/code-intelligence/architecture/architecture-engine.js +48 -0
  31. package/dist/src/code-intelligence/architecture/clusters.js +322 -0
  32. package/dist/src/code-intelligence/architecture/entry-points.js +150 -0
  33. package/dist/src/code-intelligence/architecture/hotspots.js +136 -0
  34. package/dist/src/code-intelligence/architecture/index.js +6 -0
  35. package/dist/src/code-intelligence/architecture/layers.js +197 -0
  36. package/dist/src/code-intelligence/architecture/types.js +1 -0
  37. package/dist/src/code-intelligence/chunks/index.js +2 -0
  38. package/dist/src/code-intelligence/chunks/smart-chunker.js +125 -0
  39. package/dist/src/code-intelligence/chunks/types.js +1 -0
  40. package/dist/src/code-intelligence/git/git-diff.js +109 -0
  41. package/dist/src/code-intelligence/git/index.js +1 -0
  42. package/dist/src/code-intelligence/graph/architecture.js +26 -0
  43. package/dist/src/code-intelligence/graph/graph-builder.js +213 -0
  44. package/dist/src/code-intelligence/graph/graph-store.js +57 -0
  45. package/dist/src/code-intelligence/graph/index.js +1 -0
  46. package/dist/src/code-intelligence/graph/relationships.js +1 -0
  47. package/dist/src/code-intelligence/graph/trace.js +65 -0
  48. package/dist/src/code-intelligence/impact/blast-radius.js +160 -0
  49. package/dist/src/code-intelligence/impact/change-mapper.js +69 -0
  50. package/dist/src/code-intelligence/impact/edit-tool-detector.js +92 -0
  51. package/dist/src/code-intelligence/impact/impact-analyzer.js +60 -0
  52. package/dist/src/code-intelligence/impact/impact-guard.js +33 -0
  53. package/dist/src/code-intelligence/impact/index.js +5 -0
  54. package/dist/src/code-intelligence/incremental/file-hash.js +8 -0
  55. package/dist/src/code-intelligence/incremental/incremental-indexer.js +106 -0
  56. package/dist/src/code-intelligence/incremental/manifest-builder.js +19 -0
  57. package/dist/src/code-intelligence/incremental/manifest.js +31 -0
  58. package/dist/src/code-intelligence/index.js +23 -0
  59. package/dist/src/code-intelligence/indexer/file-watcher.js +1 -0
  60. package/dist/src/code-intelligence/indexer/incremental-indexer.js +1 -0
  61. package/dist/src/code-intelligence/indexer/index.js +1 -0
  62. package/dist/src/code-intelligence/indexer/repository-indexer.js +31 -0
  63. package/dist/src/code-intelligence/indexer/repository-scanner.js +50 -0
  64. package/dist/src/code-intelligence/parsers/index.js +1 -0
  65. package/dist/src/code-intelligence/parsers/tree-sitter/index.js +1 -0
  66. package/dist/src/code-intelligence/parsers/tree-sitter/languages.js +1 -0
  67. package/dist/src/code-intelligence/parsers/tree-sitter/parser.js +1 -0
  68. package/dist/src/code-intelligence/parsers/typescript-parser.js +190 -0
  69. package/dist/src/code-intelligence/query/graph-query-engine.js +275 -0
  70. package/dist/src/code-intelligence/query/index.js +2 -0
  71. package/dist/src/code-intelligence/query/types.js +1 -0
  72. package/dist/src/code-intelligence/resolution/index.js +2 -0
  73. package/dist/src/code-intelligence/resolution/types.js +1 -0
  74. package/dist/src/code-intelligence/resolution/typescript-resolver.js +316 -0
  75. package/dist/src/code-intelligence/rich/index.js +1 -0
  76. package/dist/src/code-intelligence/rich/rich-graph-enricher.js +463 -0
  77. package/dist/src/code-intelligence/semantic/index.js +2 -0
  78. package/dist/src/code-intelligence/semantic/semantic-code-engine.js +213 -0
  79. package/dist/src/code-intelligence/semantic/source-priority.js +22 -0
  80. package/dist/src/code-intelligence/symbols/index.js +1 -0
  81. package/dist/src/code-intelligence/symbols/reference-resolver.js +21 -0
  82. package/dist/src/code-intelligence/symbols/symbol-extractor.js +1 -0
  83. package/dist/src/code-intelligence/test-analysis.js +72 -0
  84. package/dist/src/code-intelligence/test-architecture.js +67 -0
  85. package/dist/src/code-intelligence/test-impact.js +47 -0
  86. package/dist/src/code-intelligence/test-incremental.js +24 -0
  87. package/dist/src/code-intelligence/test-index.js +39 -0
  88. package/dist/src/code-intelligence/test-resolution.js +54 -0
  89. package/dist/src/code-intelligence/test-rich-graph.js +48 -0
  90. package/dist/src/code-intelligence/test-semantic.js +57 -0
  91. package/dist/src/code-intelligence/test-visualization.js +36 -0
  92. package/dist/src/code-intelligence/types.js +1 -0
  93. package/dist/src/code-intelligence/visualization/index.js +2 -0
  94. package/dist/src/code-intelligence/visualization/types.js +1 -0
  95. package/dist/src/code-intelligence/visualization/visualization-builder.js +104 -0
  96. package/dist/src/core/config.js +66 -0
  97. package/dist/src/core/global-env.js +70 -0
  98. package/dist/src/core/importance.js +30 -0
  99. package/dist/src/core/index.js +5 -0
  100. package/dist/src/core/memory-engine.js +183 -0
  101. package/dist/src/core/project-manager.js +243 -0
  102. package/dist/src/core/types.js +1 -0
  103. package/dist/src/embeddings/batch-embedder.js +1 -0
  104. package/dist/src/embeddings/cache.js +1 -0
  105. package/dist/src/embeddings/embedder.js +51 -0
  106. package/dist/src/embeddings/index.js +4 -0
  107. package/dist/src/embeddings/local.js +37 -0
  108. package/dist/src/embeddings/provider.js +1 -0
  109. package/dist/src/embeddings/remote.js +76 -0
  110. package/dist/src/embeddings/test-connection.js +16 -0
  111. package/dist/src/hooks/after-edit.js +3 -0
  112. package/dist/src/hooks/after-tool.js +3 -0
  113. package/dist/src/hooks/auto-context.js +68 -0
  114. package/dist/src/hooks/auto-impact-guard.js +94 -0
  115. package/dist/src/hooks/auto-retrieval.js +27 -0
  116. package/dist/src/hooks/before-agent.js +18 -0
  117. package/dist/src/hooks/before-compact.js +3 -0
  118. package/dist/src/hooks/before-edit.js +20 -0
  119. package/dist/src/hooks/index.js +11 -0
  120. package/dist/src/hooks/runtime.js +110 -0
  121. package/dist/src/hooks/session-end.js +3 -0
  122. package/dist/src/hooks/session-start.js +3 -0
  123. package/dist/src/index.js +22 -0
  124. package/dist/src/mcp/bootstrap.js +68 -0
  125. package/dist/src/mcp/context.js +1 -0
  126. package/dist/src/mcp/index.js +3 -0
  127. package/dist/src/mcp/server.js +45 -0
  128. package/dist/src/mcp/tools/analyze-impact.js +41 -0
  129. package/dist/src/mcp/tools/dead-code.js +55 -0
  130. package/dist/src/mcp/tools/find-callers.js +24 -0
  131. package/dist/src/mcp/tools/find-dependencies.js +57 -0
  132. package/dist/src/mcp/tools/find-symbol.js +7 -0
  133. package/dist/src/mcp/tools/get-architecture.js +11 -0
  134. package/dist/src/mcp/tools/graph-dependents.js +24 -0
  135. package/dist/src/mcp/tools/graph-neighborhood.js +58 -0
  136. package/dist/src/mcp/tools/graph-path.js +43 -0
  137. package/dist/src/mcp/tools/graph-query-utils.js +32 -0
  138. package/dist/src/mcp/tools/impact-guard.js +34 -0
  139. package/dist/src/mcp/tools/index-repository.js +1 -0
  140. package/dist/src/mcp/tools/index.js +20 -0
  141. package/dist/src/mcp/tools/memory-decisions.js +1 -0
  142. package/dist/src/mcp/tools/memory-forget.js +14 -0
  143. package/dist/src/mcp/tools/memory-recent.js +1 -0
  144. package/dist/src/mcp/tools/memory-remember.js +33 -0
  145. package/dist/src/mcp/tools/memory-rules.js +1 -0
  146. package/dist/src/mcp/tools/memory-search.js +18 -0
  147. package/dist/src/mcp/tools/memory-todos.js +1 -0
  148. package/dist/src/mcp/tools/project-context.js +25 -0
  149. package/dist/src/mcp/tools/search-code.js +21 -0
  150. package/dist/src/mcp/tools/semantic-code-search.js +33 -0
  151. package/dist/src/mcp/tools/snapshot-create.js +13 -0
  152. package/dist/src/mcp/tools/snapshot-list.js +7 -0
  153. package/dist/src/mcp/tools/snapshot-restore.js +13 -0
  154. package/dist/src/mcp/tools/trace-calls.js +37 -0
  155. package/dist/src/memory/activity.js +5 -0
  156. package/dist/src/memory/conflict-detector.js +105 -0
  157. package/dist/src/memory/consolidator.js +104 -0
  158. package/dist/src/memory/decay.js +59 -0
  159. package/dist/src/memory/decisions.js +3 -0
  160. package/dist/src/memory/deduplicate.js +25 -0
  161. package/dist/src/memory/index.js +11 -0
  162. package/dist/src/memory/maintenance.js +16 -0
  163. package/dist/src/memory/remember.js +3 -0
  164. package/dist/src/memory/rules.js +3 -0
  165. package/dist/src/memory/search.js +3 -0
  166. package/dist/src/memory/summaries.js +1 -0
  167. package/dist/src/memory/todos.js +3 -0
  168. package/dist/src/processor/decision-extractor.js +13 -0
  169. package/dist/src/processor/importance-scorer.js +22 -0
  170. package/dist/src/processor/index.js +7 -0
  171. package/dist/src/processor/memory-extractor.js +59 -0
  172. package/dist/src/processor/memory-processor.js +105 -0
  173. package/dist/src/processor/rule-extractor.js +13 -0
  174. package/dist/src/processor/summarizer.js +71 -0
  175. package/dist/src/processor/todo-extractor.js +12 -0
  176. package/dist/src/production/config-check.js +34 -0
  177. package/dist/src/production/doctor.js +58 -0
  178. package/dist/src/production/full-index.js +114 -0
  179. package/dist/src/production/health.js +58 -0
  180. package/dist/src/production/index-pipeline.js +151 -0
  181. package/dist/src/production/index.js +4 -0
  182. package/dist/src/production/project-lock.js +95 -0
  183. package/dist/src/production/project-status.js +16 -0
  184. package/dist/src/production/recovery.js +15 -0
  185. package/dist/src/production/snapshot-cli.js +58 -0
  186. package/dist/src/project-manual/cli.js +77 -0
  187. package/dist/src/project-manual/index.js +2 -0
  188. package/dist/src/project-manual/manager.js +147 -0
  189. package/dist/src/project-manual/types.js +1 -0
  190. package/dist/src/retrieval/bm25/index.js +1 -0
  191. package/dist/src/retrieval/bm25/search.js +1 -0
  192. package/dist/src/retrieval/bm25.js +30 -0
  193. package/dist/src/retrieval/context-budget.js +1 -0
  194. package/dist/src/retrieval/context-builder.js +22 -0
  195. package/dist/src/retrieval/graph/index.js +1 -0
  196. package/dist/src/retrieval/graph/search.js +1 -0
  197. package/dist/src/retrieval/hybrid/hybrid-search.js +1 -0
  198. package/dist/src/retrieval/hybrid/index.js +1 -0
  199. package/dist/src/retrieval/hybrid-search.js +68 -0
  200. package/dist/src/retrieval/importance.js +16 -0
  201. package/dist/src/retrieval/index.js +12 -0
  202. package/dist/src/retrieval/query-analyzer.js +32 -0
  203. package/dist/src/retrieval/query-classifier.js +1 -0
  204. package/dist/src/retrieval/query-expansion.js +1 -0
  205. package/dist/src/retrieval/recency.js +12 -0
  206. package/dist/src/retrieval/reranker/index.js +1 -0
  207. package/dist/src/retrieval/reranker/reranker.js +1 -0
  208. package/dist/src/retrieval/retrieval-engine.js +17 -0
  209. package/dist/src/retrieval/tokenizer.js +9 -0
  210. package/dist/src/retrieval/type-priority.js +10 -0
  211. package/dist/src/retrieval/types.js +1 -0
  212. package/dist/src/retrieval/vector/index.js +4 -0
  213. package/dist/src/retrieval/vector/metadata-filter.js +1 -0
  214. package/dist/src/retrieval/vector/persistence.js +67 -0
  215. package/dist/src/retrieval/vector/search.js +12 -0
  216. package/dist/src/retrieval/vector/test-persistence.js +36 -0
  217. package/dist/src/retrieval/vector/upsert.js +40 -0
  218. package/dist/src/retrieval/vector/vector-store.js +89 -0
  219. package/dist/src/retrieval/vector-hybrid-engine.js +91 -0
  220. package/dist/src/runtime/bootstrap.js +28 -0
  221. package/dist/src/runtime/graceful.js +17 -0
  222. package/dist/src/runtime/index.js +3 -0
  223. package/dist/src/runtime/toolnet-memory-runtime.js +156 -0
  224. package/dist/src/security/encryption.js +1 -0
  225. package/dist/src/security/file-filter.js +30 -0
  226. package/dist/src/security/index.js +3 -0
  227. package/dist/src/security/sanitizer.js +49 -0
  228. package/dist/src/security/secret-scanner.js +56 -0
  229. package/dist/src/session/agy/adapter.js +159 -0
  230. package/dist/src/session/agy/cli.js +87 -0
  231. package/dist/src/session/agy/hook-installer.js +62 -0
  232. package/dist/src/session/agy/hook.js +167 -0
  233. package/dist/src/session/agy/index.js +5 -0
  234. package/dist/src/session/agy/project-resolver.js +27 -0
  235. package/dist/src/session/agy/recovery.js +112 -0
  236. package/dist/src/session/agy/transcript.js +191 -0
  237. package/dist/src/session/codex/adapter.js +138 -0
  238. package/dist/src/session/codex/cli.js +236 -0
  239. package/dist/src/session/codex/context-hook-installer.js +70 -0
  240. package/dist/src/session/codex/context-hook.js +69 -0
  241. package/dist/src/session/codex/discovery.js +138 -0
  242. package/dist/src/session/codex/index.js +6 -0
  243. package/dist/src/session/codex/notify-installer.js +135 -0
  244. package/dist/src/session/codex/project-resolver.js +25 -0
  245. package/dist/src/session/codex/recovery.js +27 -0
  246. package/dist/src/session/codex/rollout.js +186 -0
  247. package/dist/src/session/core.js +239 -0
  248. package/dist/src/session/identity.js +57 -0
  249. package/dist/src/session/index.js +9 -0
  250. package/dist/src/session/learner/cli.js +62 -0
  251. package/dist/src/session/learner/extractor.js +461 -0
  252. package/dist/src/session/learner/index.js +4 -0
  253. package/dist/src/session/learner/journal.js +159 -0
  254. package/dist/src/session/learner/learner.js +124 -0
  255. package/dist/src/session/learner/types.js +1 -0
  256. package/dist/src/session/opencode/adapter.js +553 -0
  257. package/dist/src/session/opencode/cli.js +121 -0
  258. package/dist/src/session/opencode/index.js +2 -0
  259. package/dist/src/session/opencode/plugin-installer.js +230 -0
  260. package/dist/src/session/store.js +242 -0
  261. package/dist/src/session/types.js +1 -0
  262. package/dist/src/session/utils.js +47 -0
  263. package/dist/src/session/wal.js +303 -0
  264. package/dist/src/snapshot/index.js +2 -0
  265. package/dist/src/snapshot/manager.js +130 -0
  266. package/dist/src/snapshot/test-snapshot.js +28 -0
  267. package/dist/src/snapshot/types.js +1 -0
  268. package/dist/src/storage/architecture-store.js +37 -0
  269. package/dist/src/storage/cleanup-health.js +20 -0
  270. package/dist/src/storage/code-analysis-store.js +35 -0
  271. package/dist/src/storage/code-chunk-store.js +25 -0
  272. package/dist/src/storage/code-graph-store.js +24 -0
  273. package/dist/src/storage/code-manifest-store.js +24 -0
  274. package/dist/src/storage/code-vector-store.js +25 -0
  275. package/dist/src/storage/github/client.js +1 -0
  276. package/dist/src/storage/github/index.js +1 -0
  277. package/dist/src/storage/google-drive/client.js +1 -0
  278. package/dist/src/storage/google-drive/index.js +1 -0
  279. package/dist/src/storage/huggingface/client.js +116 -0
  280. package/dist/src/storage/huggingface/index.js +1 -0
  281. package/dist/src/storage/index.js +16 -0
  282. package/dist/src/storage/list-bucket-raw.js +14 -0
  283. package/dist/src/storage/list-bucket-tree.js +38 -0
  284. package/dist/src/storage/local/client.js +99 -0
  285. package/dist/src/storage/local/index.js +1 -0
  286. package/dist/src/storage/memory-store.js +30 -0
  287. package/dist/src/storage/project/cleanup-layout-v1.js +72 -0
  288. package/dist/src/storage/project/folder.js +15 -0
  289. package/dist/src/storage/project/index.js +3 -0
  290. package/dist/src/storage/project/layout.js +74 -0
  291. package/dist/src/storage/project/migrate-layout-v2.js +101 -0
  292. package/dist/src/storage/project/migrate.js +73 -0
  293. package/dist/src/storage/project/scoped-provider.js +165 -0
  294. package/dist/src/storage/provider.js +26 -0
  295. package/dist/src/storage/retrying-provider.js +23 -0
  296. package/dist/src/storage/test-connection.js +32 -0
  297. package/dist/src/storage/type-resolution-store.js +26 -0
  298. package/dist/src/storage/types.js +1 -0
  299. package/dist/src/storage/vector-store.js +30 -0
  300. package/dist/src/storage/visualization-store.js +25 -0
  301. package/dist/src/sync/compressor.js +1 -0
  302. package/dist/src/sync/conflict-resolver.js +1 -0
  303. package/dist/src/sync/downloader.js +1 -0
  304. package/dist/src/sync/index.js +1 -0
  305. package/dist/src/sync/sync-engine.js +1 -0
  306. package/dist/src/sync/upload-queue.js +1 -0
  307. package/dist/src/utils/retry.js +29 -0
  308. package/dist/src/visualization/server.js +270 -0
  309. package/dist/src/work-continuity/brief-cache.js +107 -0
  310. package/dist/src/work-continuity/brief.js +349 -0
  311. package/dist/src/work-continuity/cli.js +96 -0
  312. package/dist/src/work-continuity/context-cli.js +106 -0
  313. package/dist/src/work-continuity/context-runtime-cli.js +88 -0
  314. package/dist/src/work-continuity/extractor.js +339 -0
  315. package/dist/src/work-continuity/handoff.js +185 -0
  316. package/dist/src/work-continuity/index.js +14 -0
  317. package/dist/src/work-continuity/injection.js +84 -0
  318. package/dist/src/work-continuity/journal.js +49 -0
  319. package/dist/src/work-continuity/learner.js +127 -0
  320. package/dist/src/work-continuity/reducer.js +316 -0
  321. package/dist/src/work-continuity/semantic-extractor.js +360 -0
  322. package/dist/src/work-continuity/semantic-journal.js +55 -0
  323. package/dist/src/work-continuity/semantic-learner.js +125 -0
  324. package/dist/src/work-continuity/semantic-reducer.js +268 -0
  325. package/dist/src/work-continuity/semantic-types.js +1 -0
  326. package/dist/src/work-continuity/types.js +1 -0
  327. package/dist/visualization/public/index.html +2896 -0
  328. package/dist/visualization/public/vendor/3d-force-graph.min.js +5 -0
  329. package/package.json +69 -0
@@ -0,0 +1,57 @@
1
+ export class CodeGraphStore {
2
+ symbols = new Map();
3
+ edges = new Map();
4
+ addSymbol(symbol) {
5
+ this.symbols.set(symbol.id, symbol);
6
+ }
7
+ addEdge(edge) {
8
+ this.edges.set(edge.id, edge);
9
+ }
10
+ getSymbol(id) {
11
+ return this.symbols.get(id);
12
+ }
13
+ findByName(projectId, name) {
14
+ return [
15
+ ...this.symbols.values(),
16
+ ].filter((symbol) => symbol.projectId ===
17
+ projectId &&
18
+ (symbol.name ===
19
+ name ||
20
+ symbol.qualifiedName ===
21
+ name));
22
+ }
23
+ allSymbols(projectId) {
24
+ return [
25
+ ...this.symbols.values(),
26
+ ].filter((symbol) => symbol.projectId ===
27
+ projectId);
28
+ }
29
+ allEdges(projectId) {
30
+ return [
31
+ ...this.edges.values(),
32
+ ].filter((edge) => edge.projectId ===
33
+ projectId);
34
+ }
35
+ import(symbols, edges) {
36
+ for (const symbol of symbols) {
37
+ this.addSymbol(symbol);
38
+ }
39
+ for (const edge of edges) {
40
+ this.addEdge(edge);
41
+ }
42
+ }
43
+ clearProject(projectId) {
44
+ for (const [id, symbol] of this.symbols) {
45
+ if (symbol.projectId ===
46
+ projectId) {
47
+ this.symbols.delete(id);
48
+ }
49
+ }
50
+ for (const [id, edge] of this.edges) {
51
+ if (edge.projectId ===
52
+ projectId) {
53
+ this.edges.delete(id);
54
+ }
55
+ }
56
+ }
57
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ export class CallGraphTracer {
2
+ graph;
3
+ constructor(graph) {
4
+ this.graph = graph;
5
+ }
6
+ callees(projectId, symbolId, maxDepth = 3) {
7
+ return this.walk(projectId, symbolId, "out", maxDepth);
8
+ }
9
+ callers(projectId, symbolId, maxDepth = 3) {
10
+ return this.walk(projectId, symbolId, "in", maxDepth);
11
+ }
12
+ walk(projectId, startId, direction, maxDepth) {
13
+ const result = [];
14
+ const visited = new Set([
15
+ startId,
16
+ ]);
17
+ const queue = [
18
+ {
19
+ id: startId,
20
+ depth: 0,
21
+ },
22
+ ];
23
+ const edges = this.graph
24
+ .allEdges(projectId)
25
+ .filter((edge) => edge.type === "CALLS" ||
26
+ edge.type === "CALL_REFERENCE");
27
+ while (queue.length) {
28
+ const current = queue.shift();
29
+ if (current.depth >=
30
+ maxDepth) {
31
+ continue;
32
+ }
33
+ const related = direction ===
34
+ "out"
35
+ ? edges.filter((edge) => edge.from ===
36
+ current.id)
37
+ : edges.filter((edge) => edge.to ===
38
+ current.id);
39
+ for (const edge of related) {
40
+ const nextId = direction ===
41
+ "out"
42
+ ? edge.to
43
+ : edge.from;
44
+ if (visited.has(nextId)) {
45
+ continue;
46
+ }
47
+ visited.add(nextId);
48
+ const symbol = this.graph.getSymbol(nextId);
49
+ if (!symbol) {
50
+ continue;
51
+ }
52
+ const depth = current.depth + 1;
53
+ result.push({
54
+ symbol,
55
+ depth,
56
+ });
57
+ queue.push({
58
+ id: nextId,
59
+ depth,
60
+ });
61
+ }
62
+ }
63
+ return result;
64
+ }
65
+ }
@@ -0,0 +1,160 @@
1
+ const RELATION_WEIGHT = {
2
+ CALLS: 4,
3
+ CALL_REFERENCE: 6,
4
+ USES_TYPE: 4,
5
+ TESTS: 2,
6
+ ROUTE: 4,
7
+ WRITES: 3,
8
+ IMPORTS: 3,
9
+ INHERITS: 5,
10
+ IMPLEMENTS: 5,
11
+ DEFINES: 1,
12
+ };
13
+ function riskLevel(score) {
14
+ if (score >= 30) {
15
+ return "CRITICAL";
16
+ }
17
+ if (score >= 16) {
18
+ return "HIGH";
19
+ }
20
+ if (score >= 6) {
21
+ return "MEDIUM";
22
+ }
23
+ return "LOW";
24
+ }
25
+ function isTestFile(path) {
26
+ const value = path
27
+ .toLowerCase()
28
+ .replaceAll("\\", "/");
29
+ return (value.startsWith("tests/") ||
30
+ value.includes("/tests/") ||
31
+ value.includes("__tests__") ||
32
+ /\.(test|spec)\.[^.]+$/
33
+ .test(value));
34
+ }
35
+ export class BlastRadiusAnalyzer {
36
+ graph;
37
+ constructor(graph) {
38
+ this.graph = graph;
39
+ }
40
+ analyze(projectId, changedSymbols, maxDepth = 4) {
41
+ const edges = this.graph
42
+ .allEdges(projectId);
43
+ const impacted = new Map();
44
+ let score = 0;
45
+ for (const changed of changedSymbols) {
46
+ if (changed.fileStatus ===
47
+ "deleted") {
48
+ score += 8;
49
+ }
50
+ else if (changed.fileStatus ===
51
+ "renamed") {
52
+ score += 5;
53
+ }
54
+ else {
55
+ score += 2;
56
+ }
57
+ if (changed.symbol.type ===
58
+ "interface") {
59
+ score += 5;
60
+ }
61
+ if (changed.symbol.type ===
62
+ "class") {
63
+ score += 3;
64
+ }
65
+ const visited = new Set([
66
+ changed.symbol.id,
67
+ ]);
68
+ const queue = [
69
+ {
70
+ id: changed.symbol.id,
71
+ depth: 0,
72
+ },
73
+ ];
74
+ while (queue.length) {
75
+ const current = queue.shift();
76
+ if (current.depth >=
77
+ maxDepth) {
78
+ continue;
79
+ }
80
+ /*
81
+ * Blast radius đi NGƯỢC dependency:
82
+ *
83
+ * B thay đổi
84
+ * A CALLS B
85
+ * => A có nguy cơ ảnh hưởng.
86
+ */
87
+ const incoming = edges.filter((edge) => edge.to ===
88
+ current.id &&
89
+ [
90
+ "CALLS",
91
+ "CALL_REFERENCE",
92
+ "USES_TYPE",
93
+ "TESTS",
94
+ "ROUTE",
95
+ "WRITES",
96
+ "IMPORTS",
97
+ "INHERITS",
98
+ "IMPLEMENTS",
99
+ "DEFINES",
100
+ ].includes(edge.type));
101
+ for (const edge of incoming) {
102
+ if (visited.has(edge.from)) {
103
+ continue;
104
+ }
105
+ visited.add(edge.from);
106
+ const symbol = this.graph.getSymbol(edge.from);
107
+ if (!symbol) {
108
+ continue;
109
+ }
110
+ const depth = current.depth + 1;
111
+ const key = `${changed.symbol.id}:${symbol.id}`;
112
+ impacted.set(key, {
113
+ symbol,
114
+ relation: edge.type,
115
+ depth,
116
+ sourceSymbolId: changed.symbol.id,
117
+ });
118
+ const relationWeight = RELATION_WEIGHT[edge.type] ?? 1;
119
+ score +=
120
+ Math.max(1, relationWeight -
121
+ (depth - 1));
122
+ queue.push({
123
+ id: symbol.id,
124
+ depth,
125
+ });
126
+ }
127
+ }
128
+ }
129
+ const impactedList = [...impacted.values()]
130
+ .sort((a, b) => a.depth -
131
+ b.depth);
132
+ const impactedFiles = [
133
+ ...new Set(impactedList.map((item) => item.symbol
134
+ .filePath)),
135
+ ];
136
+ /*
137
+ * Nếu graph đã index tests,
138
+ * ưu tiên đề nghị đúng test liên quan.
139
+ */
140
+ const suggestedTests = impactedFiles.filter(isTestFile);
141
+ /*
142
+ * Nếu không tìm thấy test trực tiếp,
143
+ * trả file production cần verify.
144
+ */
145
+ if (suggestedTests.length ===
146
+ 0) {
147
+ suggestedTests.push(...impactedFiles
148
+ .filter((file) => !isTestFile(file))
149
+ .slice(0, 10));
150
+ }
151
+ return {
152
+ risk: riskLevel(score),
153
+ riskScore: score,
154
+ changedSymbols,
155
+ impacted: impactedList,
156
+ impactedFiles,
157
+ suggestedTests,
158
+ };
159
+ }
160
+ }
@@ -0,0 +1,69 @@
1
+ function intersects(symbol, file) {
2
+ /*
3
+ * Nếu không lấy được line diff:
4
+ * coi toàn file bị thay đổi.
5
+ */
6
+ if (file.ranges.length === 0) {
7
+ return true;
8
+ }
9
+ if (typeof symbol.startLine !==
10
+ "number" ||
11
+ typeof symbol.endLine !==
12
+ "number") {
13
+ return false;
14
+ }
15
+ return file.ranges.some((range) => symbol.startLine <=
16
+ range.endLine &&
17
+ symbol.endLine >=
18
+ range.startLine);
19
+ }
20
+ export class ChangeMapper {
21
+ graph;
22
+ constructor(graph) {
23
+ this.graph = graph;
24
+ }
25
+ map(projectId, changes) {
26
+ const symbols = this.graph.allSymbols(projectId);
27
+ const output = [];
28
+ for (const file of changes) {
29
+ const fileSymbols = symbols.filter((symbol) => symbol.filePath ===
30
+ file.filePath);
31
+ const semanticSymbols = fileSymbols.filter((symbol) => symbol.type !==
32
+ "file");
33
+ const direct = semanticSymbols.filter((symbol) => intersects(symbol, file));
34
+ /*
35
+ * Nếu diff nằm ngoài function/class,
36
+ * vẫn đưa file node vào blast radius.
37
+ */
38
+ if (direct.length === 0) {
39
+ const fileNode = fileSymbols.find((symbol) => symbol.type ===
40
+ "file");
41
+ if (fileNode) {
42
+ output.push({
43
+ symbol: fileNode,
44
+ fileStatus: file.status,
45
+ direct: true,
46
+ });
47
+ }
48
+ continue;
49
+ }
50
+ for (const symbol of direct) {
51
+ output.push({
52
+ symbol,
53
+ fileStatus: file.status,
54
+ direct: true,
55
+ });
56
+ }
57
+ }
58
+ return output;
59
+ }
60
+ mapFile(projectId, filePath) {
61
+ return this.map(projectId, [
62
+ {
63
+ filePath,
64
+ status: "modified",
65
+ ranges: [],
66
+ },
67
+ ]);
68
+ }
69
+ }
@@ -0,0 +1,92 @@
1
+ const EDIT_TOOL = /(^|[_-])(write|edit|patch|replace|multiedit|create|delete|remove|rename|move)([_-]|$)/i;
2
+ const PATH_KEYS = new Set([
3
+ "path",
4
+ "filepath",
5
+ "file_path",
6
+ "filename",
7
+ "file",
8
+ "target",
9
+ "targetpath",
10
+ "target_path",
11
+ "sourcepath",
12
+ "source_path",
13
+ ]);
14
+ function normalizePath(value) {
15
+ return value
16
+ .trim()
17
+ .replace(/^["']|["']$/g, "")
18
+ .replace(/^a\//, "")
19
+ .replace(/^b\//, "")
20
+ .replaceAll("\\", "/");
21
+ }
22
+ function looksLikeFile(value) {
23
+ return (value.length > 0 &&
24
+ value.length < 500 &&
25
+ !value.includes("\n") &&
26
+ /(^|\/)[^/]+\.[A-Za-z0-9]+$/
27
+ .test(value));
28
+ }
29
+ function extractPatchPaths(patch) {
30
+ const output = [];
31
+ const patterns = [
32
+ /^\*\*\* (?:Update|Add|Delete) File:\s*(.+)$/gm,
33
+ /^\+\+\+\s+b\/(.+)$/gm,
34
+ /^---\s+a\/(.+)$/gm,
35
+ ];
36
+ for (const regex of patterns) {
37
+ for (const match of patch.matchAll(regex)) {
38
+ if (match[1]) {
39
+ output.push(normalizePath(match[1]));
40
+ }
41
+ }
42
+ }
43
+ return output;
44
+ }
45
+ function walk(value, output, key) {
46
+ if (typeof value === "string") {
47
+ const normalizedKey = key?.toLowerCase();
48
+ if (normalizedKey &&
49
+ PATH_KEYS.has(normalizedKey) &&
50
+ looksLikeFile(value)) {
51
+ output.add(normalizePath(value));
52
+ }
53
+ /*
54
+ * apply_patch thường nhét đường dẫn
55
+ * bên trong một chuỗi patch lớn.
56
+ */
57
+ if (value.includes("*** Update File:") ||
58
+ value.includes("*** Add File:") ||
59
+ value.includes("*** Delete File:") ||
60
+ value.includes("+++ b/")) {
61
+ for (const path of extractPatchPaths(value)) {
62
+ output.add(path);
63
+ }
64
+ }
65
+ return;
66
+ }
67
+ if (Array.isArray(value)) {
68
+ for (const item of value) {
69
+ walk(item, output, key);
70
+ }
71
+ return;
72
+ }
73
+ if (value &&
74
+ typeof value === "object") {
75
+ for (const [childKey, child] of Object.entries(value)) {
76
+ walk(child, output, childKey);
77
+ }
78
+ }
79
+ }
80
+ export function isCodeMutationTool(tool) {
81
+ return EDIT_TOOL.test(tool.toLowerCase());
82
+ }
83
+ export function extractEditedFiles(tool, input) {
84
+ if (!isCodeMutationTool(tool)) {
85
+ return [];
86
+ }
87
+ const output = new Set();
88
+ walk(input, output);
89
+ return [
90
+ ...output,
91
+ ];
92
+ }
@@ -0,0 +1,60 @@
1
+ export class ImpactAnalyzer {
2
+ graph;
3
+ constructor(graph) {
4
+ this.graph = graph;
5
+ }
6
+ analyze(projectId, symbolId, maxDepth = 4) {
7
+ const edges = this.graph
8
+ .allEdges(projectId);
9
+ const visited = new Set([
10
+ symbolId,
11
+ ]);
12
+ const queue = [
13
+ {
14
+ id: symbolId,
15
+ depth: 0,
16
+ },
17
+ ];
18
+ const result = [];
19
+ while (queue.length) {
20
+ const current = queue.shift();
21
+ if (current.depth >=
22
+ maxDepth) {
23
+ continue;
24
+ }
25
+ /*
26
+ * Reverse relationships:
27
+ * "Ai phụ thuộc vào symbol này?"
28
+ */
29
+ const incoming = edges.filter((edge) => edge.to ===
30
+ current.id &&
31
+ [
32
+ "CALLS",
33
+ "IMPORTS",
34
+ "INHERITS",
35
+ "IMPLEMENTS",
36
+ ].includes(edge.type));
37
+ for (const edge of incoming) {
38
+ if (visited.has(edge.from)) {
39
+ continue;
40
+ }
41
+ visited.add(edge.from);
42
+ const symbol = this.graph.getSymbol(edge.from);
43
+ if (!symbol) {
44
+ continue;
45
+ }
46
+ const depth = current.depth + 1;
47
+ result.push({
48
+ symbol,
49
+ depth,
50
+ relation: edge.type,
51
+ });
52
+ queue.push({
53
+ id: edge.from,
54
+ depth,
55
+ });
56
+ }
57
+ }
58
+ return result;
59
+ }
60
+ }
@@ -0,0 +1,33 @@
1
+ import { readGitChanges, } from "../git/git-diff.js";
2
+ import { ChangeMapper, } from "./change-mapper.js";
3
+ import { BlastRadiusAnalyzer, } from "./blast-radius.js";
4
+ export class ImpactGuard {
5
+ graph;
6
+ mapper;
7
+ blast;
8
+ constructor(graph) {
9
+ this.graph = graph;
10
+ this.mapper =
11
+ new ChangeMapper(graph);
12
+ this.blast =
13
+ new BlastRadiusAnalyzer(graph);
14
+ }
15
+ async analyzeGitDiff(projectId, rootPath, options = {}) {
16
+ const changes = await readGitChanges(rootPath);
17
+ const changedSymbols = this.mapper.map(projectId, changes);
18
+ const result = this.blast.analyze(projectId, changedSymbols, options.maxDepth ?? 4);
19
+ return {
20
+ mode: "git_diff",
21
+ changes,
22
+ ...result,
23
+ };
24
+ }
25
+ analyzeFile(projectId, filePath, options = {}) {
26
+ const changedSymbols = this.mapper.mapFile(projectId, filePath);
27
+ return {
28
+ mode: "planned_file",
29
+ filePath,
30
+ ...this.blast.analyze(projectId, changedSymbols, options.maxDepth ?? 4),
31
+ };
32
+ }
33
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./impact-analyzer.js";
2
+ export * from "./change-mapper.js";
3
+ export * from "./blast-radius.js";
4
+ export * from "./impact-guard.js";
5
+ export * from "./edit-tool-detector.js";
@@ -0,0 +1,8 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ export async function hashFile(path) {
4
+ const content = await readFile(path);
5
+ return createHash("sha256")
6
+ .update(content)
7
+ .digest("hex");
8
+ }
@@ -0,0 +1,106 @@
1
+ import { PersistentCodeGraphStore, } from "../../storage/code-graph-store.js";
2
+ import { PersistentCodeManifestStore, } from "../../storage/code-manifest-store.js";
3
+ import { buildManifest } from "./manifest-builder.js";
4
+ import { diffManifest } from "./manifest.js";
5
+ import { parseTypeScriptFile } from "../parsers/typescript-parser.js";
6
+ import { GraphBuilder } from "../graph/graph-builder.js";
7
+ import { CodeGraphStore } from "../graph/graph-store.js";
8
+ export class IncrementalRepositoryIndexer {
9
+ storage;
10
+ constructor(storage) {
11
+ this.storage = storage;
12
+ }
13
+ async index(projectId, rootPath) {
14
+ const manifestStore = new PersistentCodeManifestStore(this.storage);
15
+ const graphStore = new PersistentCodeGraphStore(this.storage);
16
+ const previousManifest = await manifestStore.load(projectId);
17
+ const previousGraph = await graphStore.load(projectId);
18
+ const currentManifest = await buildManifest(projectId, rootPath);
19
+ const diff = diffManifest(previousManifest, currentManifest);
20
+ const changed = [
21
+ ...diff.added,
22
+ ...diff.modified,
23
+ ];
24
+ // Nothing changed.
25
+ if (previousManifest &&
26
+ previousGraph &&
27
+ changed.length === 0 &&
28
+ diff.deleted.length === 0) {
29
+ return {
30
+ firstRun: false,
31
+ scanned: Object.keys(currentManifest.files).length,
32
+ parsed: 0,
33
+ added: 0,
34
+ modified: 0,
35
+ deleted: 0,
36
+ unchanged: diff.unchanged.length,
37
+ symbols: previousGraph.symbols.length,
38
+ edges: previousGraph.edges.length,
39
+ saved: false,
40
+ };
41
+ }
42
+ /*
43
+ * Preserve parsed symbol data from unchanged files.
44
+ * Reparse only ADDED / MODIFIED files.
45
+ */
46
+ const preservedSymbols = previousGraph?.symbols.filter((symbol) => diff.unchanged.includes(symbol.filePath)) ?? [];
47
+ const parsed = [];
48
+ for (const filePath of changed) {
49
+ parsed.push(await parseTypeScriptFile(projectId, rootPath, filePath));
50
+ }
51
+ /*
52
+ * Build graph for changed files.
53
+ */
54
+ const changedGraph = new GraphBuilder().build(projectId, parsed);
55
+ /*
56
+ * Rebuild final graph from preserved + changed symbols.
57
+ *
58
+ * CALL relationships can cross file boundaries, so after collecting
59
+ * current symbols we reconstruct CALL edges from parsed data where
60
+ * available and preserve valid unchanged edges.
61
+ */
62
+ const finalGraph = new CodeGraphStore();
63
+ for (const symbol of preservedSymbols) {
64
+ finalGraph.addSymbol(symbol);
65
+ }
66
+ for (const symbol of changedGraph.allSymbols(projectId)) {
67
+ finalGraph.addSymbol(symbol);
68
+ }
69
+ const validIds = new Set(finalGraph
70
+ .allSymbols(projectId)
71
+ .map((symbol) => symbol.id));
72
+ for (const edge of previousGraph?.edges ?? []) {
73
+ if (validIds.has(edge.from) &&
74
+ validIds.has(edge.to) &&
75
+ !changed.includes(previousGraph.symbols.find((symbol) => symbol.id === edge.from)?.filePath ?? "") &&
76
+ !changed.includes(previousGraph.symbols.find((symbol) => symbol.id === edge.to)?.filePath ?? "")) {
77
+ finalGraph.addEdge(edge);
78
+ }
79
+ }
80
+ for (const edge of changedGraph.allEdges(projectId)) {
81
+ finalGraph.addEdge(edge);
82
+ }
83
+ const snapshot = {
84
+ version: 1,
85
+ projectId,
86
+ updatedAt: new Date().toISOString(),
87
+ files: Object.keys(currentManifest.files).length,
88
+ symbols: finalGraph.allSymbols(projectId),
89
+ edges: finalGraph.allEdges(projectId),
90
+ };
91
+ await graphStore.save(snapshot);
92
+ await manifestStore.save(currentManifest);
93
+ return {
94
+ firstRun: previousManifest === null,
95
+ scanned: Object.keys(currentManifest.files).length,
96
+ parsed: changed.length,
97
+ added: diff.added.length,
98
+ modified: diff.modified.length,
99
+ deleted: diff.deleted.length,
100
+ unchanged: diff.unchanged.length,
101
+ symbols: snapshot.symbols.length,
102
+ edges: snapshot.edges.length,
103
+ saved: true,
104
+ };
105
+ }
106
+ }
@@ -0,0 +1,19 @@
1
+ import { join } from "node:path";
2
+ import { scanRepository } from "../indexer/repository-scanner.js";
3
+ import { hashFile } from "./file-hash.js";
4
+ export async function buildManifest(projectId, rootPath) {
5
+ const paths = await scanRepository(rootPath);
6
+ const files = {};
7
+ for (const path of paths) {
8
+ files[path] = {
9
+ path,
10
+ hash: await hashFile(join(rootPath, path)),
11
+ };
12
+ }
13
+ return {
14
+ version: 1,
15
+ projectId,
16
+ updatedAt: new Date().toISOString(),
17
+ files,
18
+ };
19
+ }