@unerr-ai/unerr 0.0.1 → 0.1.1

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 (556) hide show
  1. package/README.md +215 -35
  2. package/dist/__tests__/architecture-guard.test.js +122 -0
  3. package/dist/__tests__/arg-validator.test.js +205 -0
  4. package/dist/__tests__/ast-extractor.test.js +203 -0
  5. package/dist/__tests__/auto-bootstrap.test.js +280 -0
  6. package/dist/__tests__/background-indexer.test.js +228 -0
  7. package/dist/__tests__/blast-radius-engine.test.js +200 -0
  8. package/dist/__tests__/bridge-isolation.test.js +37 -0
  9. package/dist/__tests__/budget-enforcer.test.js +53 -0
  10. package/dist/__tests__/cfg-test-detection-perf.test.js +82 -0
  11. package/dist/__tests__/change-narrative.test.js +190 -0
  12. package/dist/__tests__/check-commit.test.js +258 -0
  13. package/dist/__tests__/checksum.test.js +34 -0
  14. package/dist/__tests__/commit-watcher.test.js +154 -0
  15. package/dist/__tests__/community-detection.test.js +179 -0
  16. package/dist/__tests__/community-tools.test.js +299 -0
  17. package/dist/__tests__/components.test.js +449 -0
  18. package/dist/__tests__/compression-log.test.js +174 -0
  19. package/dist/__tests__/compression-quality-monitor.test.js +40 -0
  20. package/dist/__tests__/config-healer.test.js +165 -0
  21. package/dist/__tests__/context-ledger.test.js +58 -0
  22. package/dist/__tests__/convention-detector.test.js +99 -0
  23. package/dist/__tests__/convention-learner.test.js +86 -0
  24. package/dist/__tests__/correction-detector.test.js +330 -0
  25. package/dist/__tests__/daemon-autostart-install.test.js +283 -0
  26. package/dist/__tests__/daemon-bridge.test.js +222 -0
  27. package/dist/__tests__/daemon-dashboard.test.js +202 -0
  28. package/dist/__tests__/daemon-registry.test.js +240 -0
  29. package/dist/__tests__/daemon-supervisor.test.js +318 -0
  30. package/dist/__tests__/daemon-version-check.test.js +275 -0
  31. package/dist/__tests__/decision-point-detector.test.js +98 -0
  32. package/dist/__tests__/deep-link.test.js +143 -0
  33. package/dist/__tests__/disallowed-tools.test.js +115 -0
  34. package/dist/__tests__/drift-tracker.test.js +582 -0
  35. package/dist/__tests__/durability-scorer.test.js +152 -0
  36. package/dist/__tests__/efficiency-tracker.test.js +65 -0
  37. package/dist/__tests__/enrich.test.js +144 -0
  38. package/dist/__tests__/entity-rewind.test.js +248 -0
  39. package/dist/__tests__/ephemeral.test.js +111 -0
  40. package/dist/__tests__/exploration-cost.test.js +93 -0
  41. package/dist/__tests__/fact-generator.test.js +197 -0
  42. package/dist/__tests__/file-l0-graph.test.js +244 -0
  43. package/dist/__tests__/file-logger.test.js +82 -0
  44. package/dist/__tests__/file-outline.test.js +141 -0
  45. package/dist/__tests__/file-read-protocol.test.js +188 -0
  46. package/dist/__tests__/format-encoder.test.js +233 -0
  47. package/dist/__tests__/git-attribution.test.js +259 -0
  48. package/dist/__tests__/graph-temporal-joiner.test.js +219 -0
  49. package/dist/__tests__/health-grade-enhanced.test.js +138 -0
  50. package/dist/__tests__/health-map-data.test.js +173 -0
  51. package/dist/__tests__/helpers/mcp-harness.js +45 -0
  52. package/dist/__tests__/helpers/mcp-harness.test.js +68 -0
  53. package/dist/__tests__/hook-dedup.test.js +112 -0
  54. package/dist/__tests__/hook-runner.test.js +253 -0
  55. package/dist/__tests__/indexer-cfg.test.js +185 -0
  56. package/dist/__tests__/indexer-cross-file.test.js +172 -0
  57. package/dist/__tests__/indexer-extraction.test.js +245 -0
  58. package/dist/__tests__/indexer-incremental.test.js +232 -0
  59. package/dist/__tests__/indexer-language-expansion.test.js +165 -0
  60. package/dist/__tests__/init-push.test.js +131 -0
  61. package/dist/__tests__/instruction-writer.test.js +179 -0
  62. package/dist/__tests__/intelligence-integration.test.js +217 -0
  63. package/dist/__tests__/intent-correlator.test.js +175 -0
  64. package/dist/__tests__/intent-detector.test.js +235 -0
  65. package/dist/__tests__/intent-encoder.test.js +167 -0
  66. package/dist/__tests__/java-build-tool-detection.test.js +174 -0
  67. package/dist/__tests__/layer3-sprint-q.test.js +160 -0
  68. package/dist/__tests__/layer3-sprint-r.test.js +91 -0
  69. package/dist/__tests__/layer3-sprint-s.test.js +183 -0
  70. package/dist/__tests__/layer3-sprint-t.test.js +201 -0
  71. package/dist/__tests__/layer3-sprint-u.test.js +174 -0
  72. package/dist/__tests__/layer4-sprint-ba2.test.js +354 -0
  73. package/dist/__tests__/layer4-sprint-ba4.test.js +84 -0
  74. package/dist/__tests__/layer4-sprint-vs.test.js +105 -0
  75. package/dist/__tests__/ledger-chains.test.js +162 -0
  76. package/dist/__tests__/lifecycle-machine.test.js +226 -0
  77. package/dist/__tests__/local-chat-provider.test.js +170 -0
  78. package/dist/__tests__/local-convention-detector.test.js +308 -0
  79. package/dist/__tests__/local-embeddings.test.js +422 -0
  80. package/dist/__tests__/local-graph.test.js +540 -0
  81. package/dist/__tests__/local-indexer.test.js +228 -0
  82. package/dist/__tests__/local-intelligence-l3.test.js +332 -0
  83. package/dist/__tests__/local-llm.test.js +253 -0
  84. package/dist/__tests__/local-mode-offline.test.js +187 -0
  85. package/dist/__tests__/local-mode-stats.test.js +273 -0
  86. package/dist/__tests__/local-mode-tui.test.js +343 -0
  87. package/dist/__tests__/local-parse.test.js +199 -0
  88. package/dist/__tests__/log-tailer.test.js +208 -0
  89. package/dist/__tests__/loop-breaker.test.js +276 -0
  90. package/dist/__tests__/loop-miner.test.js +226 -0
  91. package/dist/__tests__/mcp-config.test.js +126 -0
  92. package/dist/__tests__/mcp-content-json.test.js +10 -0
  93. package/dist/__tests__/mcp-envelope.test.js +124 -0
  94. package/dist/__tests__/metrics-store.test.js +223 -0
  95. package/dist/__tests__/native-watcher.test.js +191 -0
  96. package/dist/__tests__/navigation-hooks-agent-aware.test.js +145 -0
  97. package/dist/__tests__/negative-knowledge.test.js +116 -0
  98. package/dist/__tests__/network-boundary.test.js +190 -0
  99. package/dist/__tests__/network-firewall.test.js +112 -0
  100. package/dist/__tests__/nudge-invariants.test.js +160 -0
  101. package/dist/__tests__/nudge-v2.test.js +225 -0
  102. package/dist/__tests__/offline-rewind.test.js +251 -0
  103. package/dist/__tests__/open-threads.test.js +89 -0
  104. package/dist/__tests__/output-compressor.test.js +93 -0
  105. package/dist/__tests__/pending-violations.test.js +112 -0
  106. package/dist/__tests__/persistence-effectiveness.test.js +143 -0
  107. package/dist/__tests__/provider-factory.test.js +42 -0
  108. package/dist/__tests__/providers.test.js +24 -0
  109. package/dist/__tests__/proxy.test.js +314 -0
  110. package/dist/__tests__/query-router.test.js +1018 -0
  111. package/dist/__tests__/reasoning-quality-route.test.js +138 -0
  112. package/dist/__tests__/redactor.test.js +120 -0
  113. package/dist/__tests__/resource-monitor.test.js +57 -0
  114. package/dist/__tests__/response-envelope.test.js +100 -0
  115. package/dist/__tests__/risk-classifier.test.js +101 -0
  116. package/dist/__tests__/risk-signal-scope.test.js +75 -0
  117. package/dist/__tests__/rule-evaluator.test.js +280 -0
  118. package/dist/__tests__/scip-decoder.test.js +49 -0
  119. package/dist/__tests__/scip-downloader.test.js +201 -0
  120. package/dist/__tests__/scip-merger.test.js +103 -0
  121. package/dist/__tests__/search-index.test.js +422 -0
  122. package/dist/__tests__/semantic-enrichment.test.js +360 -0
  123. package/dist/__tests__/session-brief-builder.test.js +187 -0
  124. package/dist/__tests__/session-context.test.js +221 -0
  125. package/dist/__tests__/session-continuity.test.js +144 -0
  126. package/dist/__tests__/session-dedup.test.js +74 -0
  127. package/dist/__tests__/session-event-wiring.test.js +206 -0
  128. package/dist/__tests__/session-events.test.js +149 -0
  129. package/dist/__tests__/session-legend.test.js +20 -0
  130. package/dist/__tests__/session-persistence.test.js +131 -0
  131. package/dist/__tests__/session-resume-block.test.js +107 -0
  132. package/dist/__tests__/session-resume.test.js +97 -0
  133. package/dist/__tests__/session-summary-writer.test.js +134 -0
  134. package/dist/__tests__/shadow-ledger.test.js +203 -0
  135. package/dist/__tests__/shell-classifier.test.js +151 -0
  136. package/dist/__tests__/shell-compression-floor.test.js +189 -0
  137. package/dist/__tests__/shell-compression-v2.test.js +339 -0
  138. package/dist/__tests__/shell-compressor.test.js +35 -0
  139. package/dist/__tests__/shell-hooks.test.js +128 -0
  140. package/dist/__tests__/shell-strategies.test.js +644 -0
  141. package/dist/__tests__/shell-tee.test.js +133 -0
  142. package/dist/__tests__/signal-dedup.test.js +158 -0
  143. package/dist/__tests__/signal-reinforcer.test.js +77 -0
  144. package/dist/__tests__/signal-scorer.test.js +251 -0
  145. package/dist/__tests__/signal-show-store.test.js +108 -0
  146. package/dist/__tests__/smart-truncate.test.js +215 -0
  147. package/dist/__tests__/snapshot-v2.test.js +113 -0
  148. package/dist/__tests__/sprint-l1-local-mode.test.js +130 -0
  149. package/dist/__tests__/sprint-l10-boot.test.js +220 -0
  150. package/dist/__tests__/sprint-l9-offline-commands.test.js +189 -0
  151. package/dist/__tests__/sprint-q-persistent-context.test.js +198 -0
  152. package/dist/__tests__/sprint-s1-wiring.test.js +215 -0
  153. package/dist/__tests__/sprint-s2-wiring.test.js +256 -0
  154. package/dist/__tests__/sprint-s3-wiring.test.js +195 -0
  155. package/dist/__tests__/sprint-s4-wiring.test.js +213 -0
  156. package/dist/__tests__/sprint-s6-hooks.test.js +222 -0
  157. package/dist/__tests__/sprint-s7-persistent.test.js +263 -0
  158. package/dist/__tests__/sprint-s8-value.test.js +167 -0
  159. package/dist/__tests__/sprint-s9-behavioral.test.js +179 -0
  160. package/dist/__tests__/sprint3-intelligence.test.js +297 -0
  161. package/dist/__tests__/sprint5-mcp-server.test.js +136 -0
  162. package/dist/__tests__/startup-display.test.js +302 -0
  163. package/dist/__tests__/startup-log-file.test.js +97 -0
  164. package/dist/__tests__/stash-manager.test.js +229 -0
  165. package/dist/__tests__/state-detector.test.js +92 -0
  166. package/dist/__tests__/status-dashboard.test.js +142 -0
  167. package/dist/__tests__/temporal-facts.test.js +292 -0
  168. package/dist/__tests__/temporal-routes.test.js +142 -0
  169. package/dist/__tests__/test-detector.test.js +174 -0
  170. package/dist/__tests__/theme.test.js +72 -0
  171. package/dist/__tests__/timeline-agents.test.js +122 -0
  172. package/dist/__tests__/timeline-bootstrap.test.js +176 -0
  173. package/dist/__tests__/timeline-filters.test.js +193 -0
  174. package/dist/__tests__/timeline-markers.test.js +151 -0
  175. package/dist/__tests__/timeline-routes.test.js +156 -0
  176. package/dist/__tests__/timeline-store.test.js +171 -0
  177. package/dist/__tests__/token-counter.test.js +86 -0
  178. package/dist/__tests__/token-estimator.test.js +96 -0
  179. package/dist/__tests__/token-flow-api.test.js +239 -0
  180. package/dist/__tests__/token-flow-instrumentation.test.js +437 -0
  181. package/dist/__tests__/token-flow-persistence.test.js +356 -0
  182. package/dist/__tests__/token-flow-routes.test.js +199 -0
  183. package/dist/__tests__/token-flow.test.js +695 -0
  184. package/dist/__tests__/tool-clusters.test.js +177 -0
  185. package/dist/__tests__/transport-mux.test.js +283 -0
  186. package/dist/__tests__/turn-segmenter.test.js +166 -0
  187. package/dist/__tests__/uninstall.test.js +141 -0
  188. package/dist/__tests__/warm-start-policy.test.js +271 -0
  189. package/dist/__tests__/wire-cap-nudge.test.js +77 -0
  190. package/dist/__tests__/worker-pool.test.js +101 -0
  191. package/dist/behaviors/agent-llm-bridge.js +166 -0
  192. package/dist/behaviors/architecture-guard.js +256 -0
  193. package/dist/behaviors/auto-doc.js +247 -0
  194. package/dist/behaviors/cascade-guard.js +289 -0
  195. package/dist/behaviors/change-narrative.js +270 -0
  196. package/dist/behaviors/convention-drift.js +290 -0
  197. package/dist/behaviors/framework.js +235 -0
  198. package/dist/behaviors/guard-formatter.js +44 -0
  199. package/dist/behaviors/incomplete-work.js +270 -0
  200. package/dist/behaviors/loop-breaker.js +300 -0
  201. package/dist/behaviors/session-continuity.js +208 -0
  202. package/dist/cli.js +6446 -2227
  203. package/dist/commands/branches.js +97 -0
  204. package/dist/commands/check-commit.js +225 -0
  205. package/dist/commands/compress-output.js +64 -0
  206. package/dist/commands/config-verify.js +243 -0
  207. package/dist/commands/daemon.js +905 -0
  208. package/dist/commands/dashboard.js +52 -0
  209. package/dist/commands/debug.js +200 -0
  210. package/dist/commands/enrich.js +184 -0
  211. package/dist/commands/exec.js +233 -0
  212. package/dist/commands/gain.js +156 -0
  213. package/dist/commands/hook.js +88 -0
  214. package/dist/commands/index.js +88 -0
  215. package/dist/commands/init.js +74 -0
  216. package/dist/commands/install.js +505 -0
  217. package/dist/commands/learn.js +116 -0
  218. package/dist/commands/manifest.js +193 -0
  219. package/dist/commands/rewind.js +103 -0
  220. package/dist/commands/serve.js +19 -0
  221. package/dist/commands/setup-wizard.js +414 -0
  222. package/dist/commands/skills.js +64 -0
  223. package/dist/commands/stats.js +20 -0
  224. package/dist/commands/status.js +654 -0
  225. package/dist/commands/timeline.js +139 -0
  226. package/dist/commands/uninstall.js +230 -0
  227. package/dist/components/App.js +109 -0
  228. package/dist/components/Banner.js +12 -0
  229. package/dist/components/ConfirmPrompt.js +25 -0
  230. package/dist/components/DriftSummary.js +23 -0
  231. package/dist/components/GradeBadge.js +15 -0
  232. package/dist/components/HealthCard.js +18 -0
  233. package/dist/components/InkSpinner.js +22 -0
  234. package/dist/components/InputBox.js +17 -0
  235. package/dist/components/KeyValue.js +13 -0
  236. package/dist/components/MessageList.js +14 -0
  237. package/dist/components/ProgressBar.js +26 -0
  238. package/dist/components/Section.js +16 -0
  239. package/dist/components/SessionSummaryCard.js +73 -0
  240. package/dist/components/StartupDisplay.js +24 -0
  241. package/dist/components/StatusDashboard.js +57 -0
  242. package/dist/components/StatusLine.js +8 -0
  243. package/dist/components/StepLine.js +22 -0
  244. package/dist/components/Theme.js +20 -0
  245. package/dist/components/ToolProgress.js +8 -0
  246. package/dist/components/ViolationList.js +21 -0
  247. package/dist/components/render.js +13 -0
  248. package/dist/config/agent-registry.js +237 -0
  249. package/dist/config/claude-settings-hooks.js +304 -0
  250. package/dist/config/hook-installer.js +65 -0
  251. package/dist/config/instruction-writer.js +388 -0
  252. package/dist/config/mcp-config-writer.js +266 -0
  253. package/dist/config/settings.js +174 -0
  254. package/dist/config/tool-detector.js +42 -0
  255. package/dist/config/value-surfacing.js +119 -0
  256. package/dist/core/context-assembly.js +108 -0
  257. package/dist/core/conversation.js +33 -0
  258. package/dist/core/local-chat-provider.js +475 -0
  259. package/dist/core/provider-factory.js +55 -0
  260. package/dist/core/providers.js +90 -0
  261. package/dist/core/query-engine.js +174 -0
  262. package/dist/daemon/api.js +312 -0
  263. package/dist/daemon/autostart.js +119 -0
  264. package/dist/daemon/bootstrap.js +39 -0
  265. package/dist/daemon/client.js +164 -0
  266. package/dist/daemon/detect-ci.js +81 -0
  267. package/dist/daemon/platform-linux.js +146 -0
  268. package/dist/daemon/platform-macos.js +134 -0
  269. package/dist/daemon/platform-windows.js +116 -0
  270. package/dist/daemon/process-manager.js +299 -0
  271. package/dist/daemon/protocol.js +23 -0
  272. package/dist/daemon/registry.js +270 -0
  273. package/dist/daemon/settings-schema.js +72 -0
  274. package/dist/daemon/system-health.js +134 -0
  275. package/dist/daemon/version-checker.js +262 -0
  276. package/dist/daemon/warm-start.js +223 -0
  277. package/dist/entrypoints/cli.js +1043 -0
  278. package/dist/entrypoints/daemon.js +380 -0
  279. package/dist/entrypoints/repl.js +147 -0
  280. package/dist/hooks/adapters/claude-code.js +90 -0
  281. package/dist/hooks/adapters/cline.js +100 -0
  282. package/dist/hooks/adapters/cursor.js +98 -0
  283. package/dist/hooks/hook-dedup.js +79 -0
  284. package/dist/hooks/hook-runner.js +113 -0
  285. package/dist/hooks/navigation-hooks.js +175 -0
  286. package/dist/hooks/prompt-hooks.js +63 -0
  287. package/dist/hooks/shell-hooks.js +47 -0
  288. package/dist/ignore.js +111 -0
  289. package/dist/intelligence/approach-suggester.js +61 -0
  290. package/dist/intelligence/ast-extractor.js +2615 -0
  291. package/dist/intelligence/ast-worker.js +34 -0
  292. package/dist/intelligence/background-indexer.js +121 -0
  293. package/dist/intelligence/blast-radius.js +200 -0
  294. package/dist/intelligence/community-detection.js +691 -0
  295. package/dist/intelligence/community-detector.js +184 -0
  296. package/dist/intelligence/computation-scheduler.js +75 -0
  297. package/dist/intelligence/confidence-propagation.js +47 -0
  298. package/dist/intelligence/convention-detector.js +242 -0
  299. package/dist/intelligence/convention-learner.js +205 -0
  300. package/dist/intelligence/convention-matcher.js +205 -0
  301. package/dist/intelligence/cozo-schema.js +376 -0
  302. package/dist/intelligence/decision-point-detector.js +90 -0
  303. package/dist/intelligence/deep-dive-tools.js +586 -0
  304. package/dist/intelligence/durability-scorer.js +84 -0
  305. package/dist/intelligence/exploration-cost.js +204 -0
  306. package/dist/intelligence/exploration-pattern-tracker.js +61 -0
  307. package/dist/intelligence/fact-generator.js +322 -0
  308. package/dist/intelligence/facts-schema.js +90 -0
  309. package/dist/intelligence/file-intelligence.js +59 -0
  310. package/dist/intelligence/graph-holder.js +220 -0
  311. package/dist/intelligence/graph-temporal-joiner.js +238 -0
  312. package/dist/intelligence/health-grade.js +423 -0
  313. package/dist/intelligence/health-grader.js +200 -0
  314. package/dist/intelligence/health-map-data.js +259 -0
  315. package/dist/intelligence/import-symbols.js +136 -0
  316. package/dist/intelligence/incremental-indexer.js +658 -0
  317. package/dist/intelligence/indexer/centrality.js +62 -0
  318. package/dist/intelligence/indexer/cfg-context.js +95 -0
  319. package/dist/intelligence/indexer/confidence.js +34 -0
  320. package/dist/intelligence/indexer/cross-file-resolver.js +104 -0
  321. package/dist/intelligence/indexer/edge-repair.js +89 -0
  322. package/dist/intelligence/indexer/entity-key.js +17 -0
  323. package/dist/intelligence/indexer/export-map.js +132 -0
  324. package/dist/intelligence/indexer/git-cochange.js +128 -0
  325. package/dist/intelligence/indexer/graph-patch.js +147 -0
  326. package/dist/intelligence/indexer/incremental.js +78 -0
  327. package/dist/intelligence/indexer/ingest.js +160 -0
  328. package/dist/intelligence/indexer/language-detect.js +226 -0
  329. package/dist/intelligence/indexer/metadata.js +63 -0
  330. package/dist/intelligence/indexer/mutation-tracker.js +79 -0
  331. package/dist/intelligence/indexer/orchestrator.js +155 -0
  332. package/dist/intelligence/indexer/plugin-interface.js +31 -0
  333. package/dist/intelligence/indexer/plugins/csharp.js +440 -0
  334. package/dist/intelligence/indexer/plugins/go.js +335 -0
  335. package/dist/intelligence/indexer/plugins/java.js +370 -0
  336. package/dist/intelligence/indexer/plugins/python.js +358 -0
  337. package/dist/intelligence/indexer/plugins/regex-fallback.js +82 -0
  338. package/dist/intelligence/indexer/plugins/ruby.js +290 -0
  339. package/dist/intelligence/indexer/plugins/rust.js +484 -0
  340. package/dist/intelligence/indexer/plugins/tier2-generic.js +310 -0
  341. package/dist/intelligence/indexer/plugins/typescript.js +456 -0
  342. package/dist/intelligence/indexer/resource-monitor.js +93 -0
  343. package/dist/intelligence/indexer/scip/decoder.js +253 -0
  344. package/dist/intelligence/indexer/scip/detector.js +232 -0
  345. package/dist/intelligence/indexer/scip/downloader.js +427 -0
  346. package/dist/intelligence/indexer/scip/fallback.js +34 -0
  347. package/dist/intelligence/indexer/scip/merger.js +109 -0
  348. package/dist/intelligence/indexer/scip/orchestrator.js +433 -0
  349. package/dist/intelligence/indexer/scip/runner.js +98 -0
  350. package/dist/intelligence/indexer/snapshot.js +66 -0
  351. package/dist/intelligence/indexer/test-detector.js +196 -0
  352. package/dist/intelligence/indexer/watch-integration.js +61 -0
  353. package/dist/intelligence/indexer/worker.js +85 -0
  354. package/dist/intelligence/local-convention-detector.js +437 -0
  355. package/dist/intelligence/local-embeddings.js +190 -0
  356. package/dist/intelligence/local-graph.js +1946 -0
  357. package/dist/intelligence/local-indexer.js +1575 -0
  358. package/dist/intelligence/local-llm.js +163 -0
  359. package/dist/intelligence/local-rule-generator.js +154 -0
  360. package/dist/intelligence/local-snapshot.js +213 -0
  361. package/dist/intelligence/negative-knowledge.js +103 -0
  362. package/dist/intelligence/persistent-db.js +85 -0
  363. package/dist/intelligence/query-router.js +2556 -0
  364. package/dist/intelligence/risk-classifier.js +116 -0
  365. package/dist/intelligence/rule-evaluator.js +380 -0
  366. package/dist/intelligence/rule-generator.js +49 -0
  367. package/dist/intelligence/search-index.js +173 -0
  368. package/dist/intelligence/semantic/docstring-extractor.js +67 -0
  369. package/dist/intelligence/semantic/embedding-store.js +52 -0
  370. package/dist/intelligence/semantic/enrichment-orchestrator.js +48 -0
  371. package/dist/intelligence/semantic/git-message-miner.js +114 -0
  372. package/dist/intelligence/semantic/identifier-tokenizer.js +51 -0
  373. package/dist/intelligence/semantic/node2vec-embeddings.js +71 -0
  374. package/dist/intelligence/semantic/node2vec-walks.js +103 -0
  375. package/dist/intelligence/semantic/path-domain-inference.js +112 -0
  376. package/dist/intelligence/semantic/similarity-engine.js +60 -0
  377. package/dist/intelligence/semantic/tfidf-vectors.js +88 -0
  378. package/dist/intelligence/session-brief-builder.js +159 -0
  379. package/dist/intelligence/session-context.js +221 -0
  380. package/dist/intelligence/session-health-monitor.js +211 -0
  381. package/dist/intelligence/session-narrative.js +197 -0
  382. package/dist/intelligence/session-pattern-analyzer.js +218 -0
  383. package/dist/intelligence/signal-scorer.js +390 -0
  384. package/dist/intelligence/signal-show-store.js +182 -0
  385. package/dist/intelligence/smart-truncate.js +158 -0
  386. package/dist/intelligence/subgraph-cache.js +88 -0
  387. package/dist/intelligence/temporal-facts.js +494 -0
  388. package/dist/intelligence/token-estimator.js +100 -0
  389. package/dist/intelligence/tool-injector.js +87 -0
  390. package/dist/intelligence/tree-sitter-loader.js +71 -0
  391. package/dist/intelligence/worker-pool.js +116 -0
  392. package/dist/proxy/arg-validator.js +79 -0
  393. package/dist/proxy/auto-bootstrap.js +167 -0
  394. package/dist/proxy/bridge.js +147 -0
  395. package/dist/proxy/budget-enforcer.js +70 -0
  396. package/dist/proxy/compression-quality-monitor.js +160 -0
  397. package/dist/proxy/compression-stats.js +51 -0
  398. package/dist/proxy/context-rot-detector.js +137 -0
  399. package/dist/proxy/drift-detector.js +139 -0
  400. package/dist/proxy/efficiency-tracker.js +79 -0
  401. package/dist/proxy/fact-ranking.js +154 -0
  402. package/dist/proxy/format-encoder.js +266 -0
  403. package/dist/proxy/http-transport.js +90 -0
  404. package/dist/proxy/lifecycle-actor.js +55 -0
  405. package/dist/proxy/lifecycle-machine.js +187 -0
  406. package/dist/proxy/log-tailer.js +265 -0
  407. package/dist/proxy/model-pricing.js +98 -0
  408. package/dist/proxy/network-firewall.js +141 -0
  409. package/dist/proxy/nudge-state.js +93 -0
  410. package/dist/proxy/output-compressor.js +185 -0
  411. package/dist/proxy/pid-lock.js +291 -0
  412. package/dist/proxy/proxy-context.js +11 -0
  413. package/dist/proxy/proxy.js +2633 -0
  414. package/dist/proxy/response-enrichment.js +32 -0
  415. package/dist/proxy/response-envelope.js +313 -0
  416. package/dist/proxy/session-dedup.js +82 -0
  417. package/dist/proxy/session-legend.js +30 -0
  418. package/dist/proxy/session-persistence.js +210 -0
  419. package/dist/proxy/session-resume.js +94 -0
  420. package/dist/proxy/session-stats.js +513 -0
  421. package/dist/proxy/shell-classifier.js +1346 -0
  422. package/dist/proxy/shell-compression-log.js +93 -0
  423. package/dist/proxy/shell-compressor.js +390 -0
  424. package/dist/proxy/shell-graph-boost.js +202 -0
  425. package/dist/proxy/shell-monitor-map.js +18 -0
  426. package/dist/proxy/shell-stats.js +54 -0
  427. package/dist/proxy/shell-strategies/cloud.js +215 -0
  428. package/dist/proxy/shell-strategies/diff.js +159 -0
  429. package/dist/proxy/shell-strategies/error-diagnostic.js +796 -0
  430. package/dist/proxy/shell-strategies/filter-dsl.js +358 -0
  431. package/dist/proxy/shell-strategies/git-status.js +177 -0
  432. package/dist/proxy/shell-strategies/key-value.js +193 -0
  433. package/dist/proxy/shell-strategies/log-text.js +154 -0
  434. package/dist/proxy/shell-strategies/omni.js +188 -0
  435. package/dist/proxy/shell-strategies/progress.js +55 -0
  436. package/dist/proxy/shell-strategies/redact.js +76 -0
  437. package/dist/proxy/shell-strategies/structured.js +241 -0
  438. package/dist/proxy/shell-strategies/tabular.js +243 -0
  439. package/dist/proxy/shell-strategies/test-results-types.js +13 -0
  440. package/dist/proxy/shell-strategies/test-results.js +784 -0
  441. package/dist/proxy/shell-strategies/tree-paths.js +144 -0
  442. package/dist/proxy/shell-strategies/yaml.js +182 -0
  443. package/dist/proxy/shell-tee.js +111 -0
  444. package/dist/proxy/signal-dedup.js +171 -0
  445. package/dist/proxy/startup-renderer.js +158 -0
  446. package/dist/proxy/task-token-display.js +38 -0
  447. package/dist/proxy/token-counter.js +61 -0
  448. package/dist/proxy/tool-clusters.js +273 -0
  449. package/dist/proxy/tool-definitions.js +525 -0
  450. package/dist/proxy/transport-mux.js +229 -0
  451. package/dist/proxy/wire-cap.js +268 -0
  452. package/dist/schemas/api/skills.js +19 -0
  453. package/dist/schemas/common/errors.js +7 -0
  454. package/dist/schemas/common/headers.js +5 -0
  455. package/dist/schemas/entities/edge.js +25 -0
  456. package/dist/schemas/entities/entity.js +22 -0
  457. package/dist/schemas/entities/rule.js +18 -0
  458. package/dist/schemas/index.js +14 -0
  459. package/dist/server/event-bus.js +59 -0
  460. package/dist/server/http.js +156 -0
  461. package/dist/server/middleware.js +70 -0
  462. package/dist/server/routes/drift.js +97 -0
  463. package/dist/server/routes/intelligence.js +1217 -0
  464. package/dist/server/routes/reasoning-quality.js +444 -0
  465. package/dist/server/routes/session.js +86 -0
  466. package/dist/server/routes/stream.js +120 -0
  467. package/dist/server/routes/system.js +73 -0
  468. package/dist/server/routes/temporal.js +170 -0
  469. package/dist/server/routes/timeline.js +232 -0
  470. package/dist/server/routes/token-flow.js +403 -0
  471. package/dist/skills/effectiveness-tracker.js +93 -0
  472. package/dist/skills/local-pack.js +380 -0
  473. package/dist/skills/resolver.js +495 -0
  474. package/dist/state-detector.js +83 -0
  475. package/dist/timeline/intent-detector.js +263 -0
  476. package/dist/timeline/loop-miner.js +140 -0
  477. package/dist/timeline/open-threads.js +49 -0
  478. package/dist/timeline/signal-reinforcer.js +62 -0
  479. package/dist/timeline/timeline-bootstrap.js +151 -0
  480. package/dist/timeline/timeline-store.js +618 -0
  481. package/dist/tools/coding/bash.js +49 -0
  482. package/dist/tools/coding/file-edit.js +72 -0
  483. package/dist/tools/coding/file-outline.js +227 -0
  484. package/dist/tools/coding/file-read-protocol.js +425 -0
  485. package/dist/tools/coding/file-read.js +35 -0
  486. package/dist/tools/coding/file-write.js +43 -0
  487. package/dist/tools/coding/glob-tool.js +109 -0
  488. package/dist/tools/coding/grep.js +162 -0
  489. package/dist/tools/coding/index.js +27 -0
  490. package/dist/tools/intelligence/index.js +269 -0
  491. package/dist/tools/intelligence/record-fact.js +48 -0
  492. package/dist/tools/intelligence/timeline-markers.js +130 -0
  493. package/dist/tools/registry.js +47 -0
  494. package/dist/tools/types.js +8 -0
  495. package/dist/tracking/auto-snapshot-triggers.js +246 -0
  496. package/dist/tracking/branch-context.js +115 -0
  497. package/dist/tracking/branch-snapshot.js +217 -0
  498. package/dist/tracking/causal-bridge.js +317 -0
  499. package/dist/tracking/circuit-breaker.js +147 -0
  500. package/dist/tracking/commit-watcher.js +114 -0
  501. package/dist/tracking/context-ledger.js +119 -0
  502. package/dist/tracking/correction-detector.js +324 -0
  503. package/dist/tracking/drift-tracker.js +874 -0
  504. package/dist/tracking/durability-tracker.js +94 -0
  505. package/dist/tracking/entity-rewind.js +200 -0
  506. package/dist/tracking/file-hash-state.js +114 -0
  507. package/dist/tracking/git-attribution.js +132 -0
  508. package/dist/tracking/git-trailers.js +171 -0
  509. package/dist/tracking/intelligence-counter.js +46 -0
  510. package/dist/tracking/intent-correlator.js +202 -0
  511. package/dist/tracking/intent-encoder.js +52 -0
  512. package/dist/tracking/intent-token-tracker.js +159 -0
  513. package/dist/tracking/ledger-archiver.js +94 -0
  514. package/dist/tracking/ledger-chains.js +245 -0
  515. package/dist/tracking/metrics-store.js +361 -0
  516. package/dist/tracking/native-watcher.js +131 -0
  517. package/dist/tracking/offline-rewind.js +295 -0
  518. package/dist/tracking/pending-violations.js +74 -0
  519. package/dist/tracking/persistence-effectiveness.js +167 -0
  520. package/dist/tracking/prompt-durability.js +202 -0
  521. package/dist/tracking/quality-signals.js +213 -0
  522. package/dist/tracking/redactor.js +73 -0
  523. package/dist/tracking/rewind-engine.js +161 -0
  524. package/dist/tracking/session-history.js +128 -0
  525. package/dist/tracking/session-receipt.js +88 -0
  526. package/dist/tracking/session-summary-writer.js +157 -0
  527. package/dist/tracking/shadow-ledger.js +321 -0
  528. package/dist/tracking/stash-manager.js +258 -0
  529. package/dist/tracking/timeline-fork.js +213 -0
  530. package/dist/tracking/timeline.js +69 -0
  531. package/dist/tracking/token-flow.js +276 -0
  532. package/dist/tracking/turn-segmenter.js +122 -0
  533. package/dist/tracking/weekly-accumulator.js +179 -0
  534. package/dist/tracking/working-snapshots.js +188 -0
  535. package/dist/tracking/workspace-manifest.js +176 -0
  536. package/dist/transport/http.js +102 -0
  537. package/dist/ui/assets/index-7gl3mIuY.css +1 -0
  538. package/dist/ui/assets/index-BsMTQdhX.js +10 -0
  539. package/dist/ui/index.html +2 -2
  540. package/dist/utils/counterfactual.js +65 -0
  541. package/dist/utils/deep-link.js +34 -0
  542. package/dist/utils/detect.js +193 -0
  543. package/dist/utils/exec.js +73 -0
  544. package/dist/utils/file-logger.js +87 -0
  545. package/dist/utils/format-error.js +29 -0
  546. package/dist/utils/git.js +181 -0
  547. package/dist/utils/log.js +57 -0
  548. package/dist/utils/logger.js +35 -0
  549. package/dist/utils/mcp-content-json.js +8 -0
  550. package/dist/utils/session-logger.js +154 -0
  551. package/dist/utils/startup-log.js +512 -0
  552. package/dist/utils/ui.js +56 -0
  553. package/package.json +5 -3
  554. package/scripts/postinstall.mjs +299 -0
  555. package/dist/ui/assets/index-BISLlJyc.js +0 -10
  556. package/dist/ui/assets/index-BUChTv4H.css +0 -1
@@ -0,0 +1,874 @@
1
+ /**
2
+ * Drift Tracker Engine — detects workspace drift between local files and graph.
3
+ *
4
+ * Processing pipeline (per changed file):
5
+ * 1. Content SHA → skip if unchanged (via FileHashManager)
6
+ * 2. Extract entities via AST extractor (regex-based, fast)
7
+ * 3. Diff against CozoDB base entities for same file_path
8
+ * 4. Upsert drift_overlay: added / modified / deleted
9
+ * 5. Update file hash state
10
+ *
11
+ * Runs within the proxy loop, triggered by file watcher or on-demand.
12
+ * All logging to stderr. Never touches stdout.
13
+ */
14
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { detectLanguage, entityKey, extractEntitiesAsync, } from "../intelligence/ast-extractor.js";
17
+ import { BranchSnapshotManager } from "./branch-snapshot.js";
18
+ import { contentSha256 } from "./file-hash-state.js";
19
+ import { StashManager } from "./stash-manager.js";
20
+ /** Thresholds for AI attribution heuristic (ms). */
21
+ const AI_THRESHOLD_MS = 10_000; // <10s after sync = AI
22
+ const MIXED_THRESHOLD_MS = 60_000; // 10-60s = mixed, >60s = human
23
+ /**
24
+ * Determine change origin based on time since last sync_local_diff.
25
+ * - <10s after sync → "ai" (agent just wrote code)
26
+ * - 10-60s → "mixed" (ambiguous, agent wrote + human may have edited)
27
+ * - >60s → "human" (no recent agent activity)
28
+ */
29
+ export function determineOrigin(lastSyncTimestamp) {
30
+ if (lastSyncTimestamp === 0)
31
+ return "human";
32
+ const elapsed = Date.now() - lastSyncTimestamp;
33
+ if (elapsed < AI_THRESHOLD_MS)
34
+ return "ai";
35
+ if (elapsed < MIXED_THRESHOLD_MS)
36
+ return "mixed";
37
+ return "human";
38
+ }
39
+ /**
40
+ * Chokidar `awaitWriteFinish` config for IDE auto-save noise filtering.
41
+ * Use when setting up a file watcher that feeds into DriftTracker.
42
+ *
43
+ * stabilityThreshold: wait 300ms after last write before emitting 'change'
44
+ * pollInterval: check every 100ms during the stability window
45
+ */
46
+ export const DRIFT_WATCHER_OPTIONS = {
47
+ awaitWriteFinish: {
48
+ stabilityThreshold: 300,
49
+ pollInterval: 100,
50
+ },
51
+ };
52
+ /**
53
+ * In-memory mtime cache for fast rejection of unchanged files.
54
+ * Avoids reading file content + computing SHA-256 when mtime hasn't changed.
55
+ */
56
+ export class MtimeCache {
57
+ cache = new Map();
58
+ /**
59
+ * Returns true if the file's mtime has changed (or is new).
60
+ * Updates the cache entry on change.
61
+ */
62
+ check(filePath) {
63
+ try {
64
+ const stat = statSync(filePath);
65
+ const lastMtime = this.cache.get(filePath);
66
+ if (lastMtime !== undefined && stat.mtimeMs === lastMtime) {
67
+ return false; // unchanged
68
+ }
69
+ this.cache.set(filePath, stat.mtimeMs);
70
+ return true; // changed or new
71
+ }
72
+ catch {
73
+ // File doesn't exist or stat failed — evict from cache, let caller handle
74
+ this.cache.delete(filePath);
75
+ return true;
76
+ }
77
+ }
78
+ /** Remove a file from the cache (e.g., on delete). */
79
+ evict(filePath) {
80
+ this.cache.delete(filePath);
81
+ }
82
+ /** Clear entire cache (e.g., on branch switch). */
83
+ clear() {
84
+ this.cache.clear();
85
+ }
86
+ /** Number of cached entries. */
87
+ get size() {
88
+ return this.cache.size;
89
+ }
90
+ }
91
+ /** stderr logger */
92
+ const _log = {
93
+ info: (msg) => process.stderr.write(`[unerr:drift] ${msg}\n`),
94
+ warn: (msg) => process.stderr.write(`[unerr:drift] WARN: ${msg}\n`),
95
+ };
96
+ export class DriftTracker {
97
+ config;
98
+ localGraph;
99
+ fileHashManager;
100
+ mtimeCache = new MtimeCache();
101
+ /** Timestamp of last sync_local_diff, set by proxy for attribution heuristic. */
102
+ _lastSyncTimestamp = 0;
103
+ /** Optional rule evaluator for push-based violation detection (Task 7.3). */
104
+ ruleEvaluator = null;
105
+ /** Optional violation store — shared with QueryRouter (Task 7.3). */
106
+ violationStore = null;
107
+ /** Local Mode incremental re-index hook (L2.5). Kept but disabled — full reindex preferred. */
108
+ localReindexFn = null;
109
+ /** File change notification callback — wired to GraphHolder.notifyFileChange(). */
110
+ fileChangeNotifier = null;
111
+ /** Stash manager for save/restore on git stash/pop (Task 7.1). */
112
+ stashManager = null;
113
+ /** Branch snapshot manager for save/restore on branch switch (Task 6.2). */
114
+ branchSnapshotManager = null;
115
+ /** Layer 7: Optional sink for dashboard SSE (same-process, no IPC). */
116
+ driftSink = null;
117
+ constructor(config, localGraph, fileHashManager) {
118
+ this.config = config;
119
+ this.localGraph = localGraph;
120
+ this.fileHashManager = fileHashManager;
121
+ }
122
+ /**
123
+ * Enable push-based rule enforcement (Task 7.3).
124
+ * When set, file changes trigger automatic rule evaluation.
125
+ */
126
+ setRuleEnforcement(evaluator, store) {
127
+ this.ruleEvaluator = evaluator;
128
+ this.violationStore = store;
129
+ }
130
+ /**
131
+ * Enable Local Mode incremental re-indexing (L2.5).
132
+ * DISABLED — kept for reference. Full reindex is used instead (8s for 450 files).
133
+ */
134
+ setLocalReindex(reindexFn) {
135
+ this.localReindexFn = reindexFn;
136
+ }
137
+ /**
138
+ * Wire the file change notifier (from GraphHolder.notifyFileChange).
139
+ * Called on every drift-processed file change to signal the GraphHolder's idle timer.
140
+ */
141
+ setFileChangeNotifier(notifier) {
142
+ this.fileChangeNotifier = notifier;
143
+ }
144
+ /**
145
+ * Swap the graph reference atomically (called by GraphHolder on rebuild completion).
146
+ * All subsequent drift detection will use the new graph instance.
147
+ */
148
+ swapGraph(newGraph) {
149
+ this.localGraph = newGraph;
150
+ }
151
+ /** Update the last sync timestamp (called by proxy on sync_local_diff). */
152
+ setLastSyncTimestamp(ts) {
153
+ this._lastSyncTimestamp = ts;
154
+ }
155
+ /**
156
+ * Layer 7: Wire dashboard event bus — emits when drift counts change for a file.
157
+ */
158
+ setDriftEventSink(sink) {
159
+ this.driftSink = sink;
160
+ }
161
+ maybeEmitDrift(relPath, result) {
162
+ if (!this.driftSink)
163
+ return;
164
+ const activity = result.entitiesAdded +
165
+ result.entitiesModified +
166
+ result.entitiesDeleted +
167
+ result.crossFileInvalidated +
168
+ result.edgesExtracted;
169
+ if (activity === 0)
170
+ return;
171
+ this.driftSink({ file: relPath, ...result });
172
+ }
173
+ /**
174
+ * Process a single file for drift detection.
175
+ * Returns the drift result for this file.
176
+ */
177
+ async processFile(filePath, headSha, intentId) {
178
+ const result = {
179
+ filesProcessed: 0,
180
+ filesSkipped: 0,
181
+ entitiesAdded: 0,
182
+ entitiesModified: 0,
183
+ entitiesDeleted: 0,
184
+ crossFileInvalidated: 0,
185
+ edgesExtracted: 0,
186
+ };
187
+ // Resolve absolute path
188
+ const absPath = filePath.startsWith("/")
189
+ ? filePath
190
+ : join(this.config.projectRoot, filePath);
191
+ // Relative path for entity storage (from project root)
192
+ const relPath = filePath.startsWith("/")
193
+ ? filePath.slice(this.config.projectRoot.length + 1)
194
+ : filePath;
195
+ // Check if language is supported
196
+ const language = detectLanguage(relPath);
197
+ if (!language)
198
+ return result;
199
+ // Fast mtime rejection — avoid reading content + SHA if mtime unchanged
200
+ if (existsSync(absPath) && !this.mtimeCache.check(absPath)) {
201
+ result.filesSkipped = 1;
202
+ return result;
203
+ }
204
+ // Check if file exists
205
+ if (!existsSync(absPath)) {
206
+ // File was deleted — mark all entities from this file as deleted
207
+ const baseEntities = await this.localGraph.getEntitiesByFile(relPath);
208
+ this.markFileDeleted(relPath, intentId);
209
+ result.filesProcessed = 1;
210
+ result.entitiesDeleted = baseEntities.length;
211
+ // Cross-file invalidation: callers of deleted entities need notification
212
+ const now = new Date().toISOString();
213
+ const origin = determineOrigin(this._lastSyncTimestamp);
214
+ for (const entity of baseEntities) {
215
+ const callers = await this.localGraph.getCallersOf(entity.key);
216
+ for (const caller of callers) {
217
+ if (caller.file_path === relPath)
218
+ continue;
219
+ const existing = (await this.localGraph.getDriftEntitiesForFile(caller.file_path)).find((e) => e.key === caller.key);
220
+ if (existing && existing.drift_status !== "dependency_changed")
221
+ continue;
222
+ const drift = {
223
+ key: caller.key,
224
+ name: caller.name,
225
+ kind: caller.kind,
226
+ signature: caller.signature ?? "",
227
+ body: caller.body ?? "",
228
+ file_path: caller.file_path,
229
+ line_start: caller.start_line ?? 0,
230
+ line_end: caller.start_line ?? 0,
231
+ content_hash: "",
232
+ drift_status: "dependency_changed",
233
+ intent_id: intentId ?? "",
234
+ modified_at: now,
235
+ origin,
236
+ previous_body: "",
237
+ previous_signature: "",
238
+ };
239
+ await this.localGraph.upsertDriftEntity(drift);
240
+ result.crossFileInvalidated++;
241
+ }
242
+ }
243
+ this.maybeEmitDrift(relPath, result);
244
+ return result;
245
+ }
246
+ // Read content and compute hash
247
+ const content = readFileSync(absPath, "utf-8");
248
+ const sha = contentSha256(content);
249
+ // Skip if unchanged
250
+ const decision = this.fileHashManager.shouldProcess(relPath, sha, headSha);
251
+ if (decision === "skip") {
252
+ result.filesSkipped = 1;
253
+ return result;
254
+ }
255
+ result.filesProcessed = 1;
256
+ // Extract entities from local file (tree-sitter WASM with regex fallback)
257
+ const localEntities = await extractEntitiesAsync(content, relPath);
258
+ // Get base entities from CozoDB for this file
259
+ const baseEntities = await this.localGraph.getEntitiesByFile(relPath);
260
+ // Build lookup maps
261
+ const localByKey = new Map();
262
+ for (const entity of localEntities) {
263
+ const key = entityKey(this.config.repoId, relPath, entity.kind, entity.name, entity.signature);
264
+ localByKey.set(key, entity);
265
+ }
266
+ const baseByKey = new Map();
267
+ for (const entity of baseEntities) {
268
+ baseByKey.set(entity.key, entity);
269
+ }
270
+ const now = new Date().toISOString();
271
+ const origin = determineOrigin(this._lastSyncTimestamp);
272
+ // Find added and modified entities
273
+ for (const [key, local] of localByKey) {
274
+ const base = baseByKey.get(key);
275
+ if (!base) {
276
+ // New entity — not in base graph
277
+ const drift = {
278
+ key,
279
+ name: local.name,
280
+ kind: local.kind,
281
+ signature: local.signature,
282
+ body: extractBodyLines(content, local.line_start, local.line_end),
283
+ file_path: relPath,
284
+ line_start: local.line_start,
285
+ line_end: local.line_end,
286
+ content_hash: local.content_hash,
287
+ drift_status: "added",
288
+ intent_id: intentId ?? "",
289
+ modified_at: now,
290
+ origin,
291
+ previous_body: "",
292
+ previous_signature: "",
293
+ };
294
+ await this.localGraph.upsertDriftEntity(drift);
295
+ result.entitiesAdded++;
296
+ }
297
+ else if (local.content_hash !== contentSha256(base.body || "").slice(0, 16)) {
298
+ // Content changed — mark as modified (preserve previous body for rewind)
299
+ const drift = {
300
+ key,
301
+ name: local.name,
302
+ kind: local.kind,
303
+ signature: local.signature,
304
+ body: extractBodyLines(content, local.line_start, local.line_end),
305
+ file_path: relPath,
306
+ line_start: local.line_start,
307
+ line_end: local.line_end,
308
+ content_hash: local.content_hash,
309
+ drift_status: "modified",
310
+ intent_id: intentId ?? "",
311
+ modified_at: now,
312
+ origin,
313
+ previous_body: base.body || "",
314
+ previous_signature: base.signature || "",
315
+ };
316
+ await this.localGraph.upsertDriftEntity(drift);
317
+ result.entitiesModified++;
318
+ }
319
+ }
320
+ // Find deleted entities (in base but not in local)
321
+ for (const [key, base] of baseByKey) {
322
+ if (!localByKey.has(key)) {
323
+ const drift = {
324
+ key,
325
+ name: base.name,
326
+ kind: base.kind,
327
+ signature: base.signature,
328
+ body: "",
329
+ file_path: relPath,
330
+ line_start: base.start_line,
331
+ line_end: base.start_line,
332
+ content_hash: "",
333
+ drift_status: "deleted",
334
+ intent_id: intentId ?? "",
335
+ modified_at: now,
336
+ origin,
337
+ previous_body: base.body || "",
338
+ previous_signature: base.signature || "",
339
+ };
340
+ await this.localGraph.upsertDriftEntity(drift);
341
+ result.entitiesDeleted++;
342
+ }
343
+ }
344
+ // Cross-file drift invalidation: notify callers of modified/deleted entities
345
+ result.crossFileInvalidated = await this.invalidateCrossFileCallers(localByKey, baseByKey, relPath, now, origin, intentId);
346
+ // Task 6.4: Extract drift edges (imports + function calls)
347
+ result.edgesExtracted = await this.extractDriftEdges(content, relPath, localByKey, now);
348
+ // Notify GraphHolder of file change — resets idle timer for swap-on-idle rebuild.
349
+ // GraphHolder handles debouncing + full reindex + atomic graph swap.
350
+ this.fileChangeNotifier?.();
351
+ // Task 7.3: Push-based rule enforcement — evaluate rules on changed file
352
+ if (this.ruleEvaluator &&
353
+ this.violationStore &&
354
+ (await this.localGraph.hasRules())) {
355
+ this.runRuleCheck(relPath, content);
356
+ }
357
+ // Update file hash state
358
+ this.fileHashManager.markProcessed(relPath, sha, headSha);
359
+ this.maybeEmitDrift(relPath, result);
360
+ return result;
361
+ }
362
+ /**
363
+ * Process multiple files for drift detection (batch).
364
+ */
365
+ async processFiles(filePaths, headSha, intentId) {
366
+ const aggregate = {
367
+ filesProcessed: 0,
368
+ filesSkipped: 0,
369
+ entitiesAdded: 0,
370
+ entitiesModified: 0,
371
+ entitiesDeleted: 0,
372
+ crossFileInvalidated: 0,
373
+ edgesExtracted: 0,
374
+ };
375
+ for (const filePath of filePaths) {
376
+ const result = await this.processFile(filePath, headSha, intentId);
377
+ aggregate.filesProcessed += result.filesProcessed;
378
+ aggregate.filesSkipped += result.filesSkipped;
379
+ aggregate.entitiesAdded += result.entitiesAdded;
380
+ aggregate.entitiesModified += result.entitiesModified;
381
+ aggregate.entitiesDeleted += result.entitiesDeleted;
382
+ aggregate.crossFileInvalidated += result.crossFileInvalidated;
383
+ aggregate.edgesExtracted += result.edgesExtracted;
384
+ }
385
+ // Persist file hash state after batch
386
+ this.fileHashManager.save();
387
+ // Update drift summary on disk
388
+ await this.saveDriftSummary();
389
+ return aggregate;
390
+ }
391
+ /**
392
+ * Initialize branch snapshot manager (Task 6.2).
393
+ * Returns the manager for external use (e.g., GC on startup).
394
+ */
395
+ initBranchSnapshots() {
396
+ this.branchSnapshotManager = new BranchSnapshotManager(this.config.unerrDir, this.config.projectRoot);
397
+ return this.branchSnapshotManager;
398
+ }
399
+ /**
400
+ * Handle branch switch: save outgoing branch overlay, restore incoming.
401
+ *
402
+ * With BranchSnapshotManager (Task 6.2):
403
+ * 1. Save current overlay + file hashes for outgoing branch
404
+ * 2. Clear overlay + hashes + mtime cache
405
+ * 3. Attempt restore from snapshot (fast, <10ms)
406
+ * 4. If no snapshot (first visit), recompute from scratch
407
+ *
408
+ * Without BranchSnapshotManager: falls back to clear + recompute.
409
+ */
410
+ async onBranchSwitch(changedFiles, headSha, fromBranch, toBranch) {
411
+ // Save outgoing branch snapshot
412
+ if (this.branchSnapshotManager && fromBranch) {
413
+ const fileHashState = this.fileHashManager.getState();
414
+ await this.branchSnapshotManager.saveSnapshot(fromBranch, this.localGraph, {
415
+ ...fileHashState,
416
+ });
417
+ }
418
+ // Clear current state
419
+ await this.localGraph.clearDriftOverlay();
420
+ this.fileHashManager.clearAll();
421
+ this.mtimeCache.clear();
422
+ // Attempt restore from snapshot
423
+ if (this.branchSnapshotManager && toBranch) {
424
+ const snapshot = await this.branchSnapshotManager.restoreSnapshot(toBranch, this.localGraph);
425
+ if (snapshot) {
426
+ // Restored from snapshot — skip recompute
427
+ // Restore file hash state so subsequent processFile() calls
428
+ // correctly skip unchanged files
429
+ this.fileHashManager.restoreState(snapshot.fileHashes);
430
+ return {
431
+ filesProcessed: 0,
432
+ filesSkipped: 0,
433
+ entitiesAdded: snapshot.entities.length,
434
+ entitiesModified: 0,
435
+ entitiesDeleted: 0,
436
+ crossFileInvalidated: 0,
437
+ edgesExtracted: snapshot.edges?.length ?? 0,
438
+ };
439
+ }
440
+ }
441
+ // No snapshot — first visit, recompute from scratch
442
+ return this.processFiles(changedFiles, headSha);
443
+ }
444
+ /**
445
+ * Get the current drift summary from CozoDB.
446
+ */
447
+ async getDriftSummary() {
448
+ return await this.localGraph.getDriftSummary();
449
+ }
450
+ /**
451
+ * Initialize stash awareness (Task 7.1).
452
+ * Returns the StashManager for use in polling.
453
+ */
454
+ initStashManager() {
455
+ this.stashManager = new StashManager(this.config.unerrDir, this.config.projectRoot);
456
+ return this.stashManager;
457
+ }
458
+ /**
459
+ * Handle git stash push: save current overlay + file hashes to snapshot.
460
+ */
461
+ async onStashSave() {
462
+ if (!this.stashManager)
463
+ return null;
464
+ const fileHashState = this.fileHashManager.getState();
465
+ return await this.stashManager.saveSnapshot(this.localGraph, {
466
+ ...fileHashState,
467
+ });
468
+ }
469
+ /**
470
+ * Handle git stash pop: restore overlay from most recent snapshot.
471
+ */
472
+ async onStashPop() {
473
+ if (!this.stashManager)
474
+ return 0;
475
+ return await this.stashManager.restoreSnapshot(this.localGraph);
476
+ }
477
+ async markFileDeleted(filePath, intentId) {
478
+ // Evict from mtime cache — file no longer exists
479
+ const absPath = filePath.startsWith("/")
480
+ ? filePath
481
+ : join(this.config.projectRoot, filePath);
482
+ this.mtimeCache.evict(absPath);
483
+ const baseEntities = await this.localGraph.getEntitiesByFile(filePath);
484
+ const now = new Date().toISOString();
485
+ const origin = determineOrigin(this._lastSyncTimestamp);
486
+ for (const entity of baseEntities) {
487
+ const drift = {
488
+ key: entity.key,
489
+ name: entity.name,
490
+ kind: entity.kind,
491
+ signature: entity.signature,
492
+ body: "",
493
+ file_path: filePath,
494
+ line_start: entity.start_line,
495
+ line_end: entity.start_line,
496
+ content_hash: "",
497
+ drift_status: "deleted",
498
+ intent_id: intentId ?? "",
499
+ modified_at: now,
500
+ origin,
501
+ previous_body: entity.body || "",
502
+ previous_signature: entity.signature || "",
503
+ };
504
+ await this.localGraph.upsertDriftEntity(drift);
505
+ }
506
+ }
507
+ /**
508
+ * For modified/deleted entities in this file, find callers in OTHER files
509
+ * and mark them as "dependency_changed" in the drift overlay.
510
+ */
511
+ async invalidateCrossFileCallers(localByKey, baseByKey, filePath, now, origin, intentId) {
512
+ // Collect keys of entities that were modified or deleted
513
+ const changedKeys = [];
514
+ for (const [key, local] of localByKey) {
515
+ const base = baseByKey.get(key);
516
+ if (!base) {
517
+ // Added entity — no callers to invalidate yet
518
+ continue;
519
+ }
520
+ if (local.content_hash !== contentSha256(base.body || "").slice(0, 16)) {
521
+ changedKeys.push(key);
522
+ }
523
+ }
524
+ for (const [key] of baseByKey) {
525
+ if (!localByKey.has(key)) {
526
+ changedKeys.push(key); // deleted
527
+ }
528
+ }
529
+ if (changedKeys.length === 0)
530
+ return 0;
531
+ let invalidated = 0;
532
+ for (const key of changedKeys) {
533
+ const callers = await this.localGraph.getCallersOf(key);
534
+ for (const caller of callers) {
535
+ // Skip callers in the same file — already handled by normal diff
536
+ if (caller.file_path === filePath)
537
+ continue;
538
+ // Don't overwrite a stronger drift status (added/modified/deleted)
539
+ const existingEntities = await this.localGraph.getDriftEntitiesForFile(caller.file_path);
540
+ const existing = existingEntities.find((e) => e.key === caller.key);
541
+ if (existing && existing.drift_status !== "dependency_changed") {
542
+ continue;
543
+ }
544
+ const drift = {
545
+ key: caller.key,
546
+ name: caller.name,
547
+ kind: caller.kind,
548
+ signature: caller.signature ?? "",
549
+ body: caller.body ?? "",
550
+ file_path: caller.file_path,
551
+ line_start: caller.start_line ?? 0,
552
+ line_end: caller.start_line ?? 0,
553
+ content_hash: "",
554
+ drift_status: "dependency_changed",
555
+ intent_id: intentId ?? "",
556
+ modified_at: now,
557
+ origin,
558
+ previous_body: "",
559
+ previous_signature: "",
560
+ };
561
+ await this.localGraph.upsertDriftEntity(drift);
562
+ invalidated++;
563
+ }
564
+ }
565
+ return invalidated;
566
+ }
567
+ async saveDriftSummary() {
568
+ const driftDir = join(this.config.unerrDir, "drift");
569
+ if (!existsSync(driftDir)) {
570
+ mkdirSync(driftDir, { recursive: true });
571
+ }
572
+ const summary = await this.getDriftSummary();
573
+ writeFileSync(join(driftDir, "drift_summary.json"), JSON.stringify(summary, null, 2), "utf-8");
574
+ }
575
+ /**
576
+ * Task 7.3: Run rule evaluation on a changed file and store violations.
577
+ * Non-blocking: fires and forgets. If evaluation takes >10ms, times out silently.
578
+ */
579
+ /**
580
+ * Task 6.4: Extract import and function call edges from file content.
581
+ * Upserts them into drift_edges. Approximate — no full scope resolution.
582
+ */
583
+ async extractDriftEdges(content, filePath, localByKey, now) {
584
+ let count = 0;
585
+ // Extract import edges: import { X } from './module'
586
+ const importEdges = extractImportEdges(content, filePath);
587
+ for (const imp of importEdges) {
588
+ // Resolve target: find entity key matching imported name in target file
589
+ const targetKey = await this.resolveImportTarget(imp.importedName, imp.targetPath);
590
+ if (!targetKey)
591
+ continue;
592
+ // Find a source entity that is the "file module" or first entity in this file
593
+ const sourceKey = this.resolveImportSource(filePath, localByKey);
594
+ if (!sourceKey)
595
+ continue;
596
+ await this.localGraph.upsertDriftEdge({
597
+ from_key: sourceKey,
598
+ to_key: targetKey,
599
+ type: "imports",
600
+ drift_status: "added",
601
+ modified_at: now,
602
+ });
603
+ count++;
604
+ }
605
+ // Extract function call edges within entities in this file.
606
+ // Only callable kinds can emit "calls" edges — variables, interfaces, types
607
+ // are not call sites, and scanning the full file body for them would attribute
608
+ // unrelated calls in the same file to non-callable entities (false positives in
609
+ // get_references). Class is included because class bodies can contain static
610
+ // initializers and field initializers that perform calls.
611
+ const CALLABLE_KINDS = new Set(["function", "method", "class"]);
612
+ for (const [callerKey, entity] of localByKey) {
613
+ if (!CALLABLE_KINDS.has(entity.kind))
614
+ continue;
615
+ const callEdges = extractCallEdges(content, entity.name, callerKey);
616
+ for (const call of callEdges) {
617
+ // Find target entity by name in any file
618
+ const targetKey = await this.resolveCallTarget(call.calledName);
619
+ if (!targetKey || targetKey === callerKey)
620
+ continue;
621
+ await this.localGraph.upsertDriftEdge({
622
+ from_key: callerKey,
623
+ to_key: targetKey,
624
+ type: "calls",
625
+ drift_status: "added",
626
+ modified_at: now,
627
+ });
628
+ count++;
629
+ }
630
+ }
631
+ return count;
632
+ }
633
+ /**
634
+ * Resolve an imported name to an entity key in the target file.
635
+ */
636
+ async resolveImportTarget(name, targetPath) {
637
+ // Look in base entities first
638
+ const baseEntities = await this.localGraph.getEntitiesByFile(targetPath);
639
+ for (const entity of baseEntities) {
640
+ if (entity.name === name)
641
+ return entity.key;
642
+ }
643
+ // Check drift overlay
644
+ const driftEntities = await this.localGraph.getDriftEntitiesForFile(targetPath);
645
+ for (const entity of driftEntities) {
646
+ if (entity.name === name && entity.drift_status !== "deleted")
647
+ return entity.key;
648
+ }
649
+ return null;
650
+ }
651
+ /**
652
+ * Find the first entity in this file to use as import source.
653
+ */
654
+ resolveImportSource(filePath, localByKey) {
655
+ // Use first entity from local extraction as the module representative
656
+ for (const [key] of localByKey) {
657
+ return key;
658
+ }
659
+ return null;
660
+ }
661
+ /**
662
+ * Resolve a called function name to an entity key.
663
+ * Searches base entities + drift overlay across all files.
664
+ */
665
+ async resolveCallTarget(name) {
666
+ // Search base entities by name
667
+ const entity = await this.localGraph.findEntityByName(name);
668
+ if (entity)
669
+ return entity.key;
670
+ return null;
671
+ }
672
+ async runRuleCheck(filePath, content) {
673
+ if (!this.ruleEvaluator || !this.violationStore)
674
+ return;
675
+ const rules = await this.localGraph.getRules();
676
+ if (rules.length === 0)
677
+ return;
678
+ const evaluator = this.ruleEvaluator;
679
+ const store = this.violationStore;
680
+ // Run async rule evaluation — fire and forget, don't block drift processing
681
+ const t0 = performance.now();
682
+ evaluator(rules, filePath, content, this.localGraph)
683
+ .then((result) => {
684
+ const elapsed = performance.now() - t0;
685
+ if (elapsed > 10) {
686
+ _log.warn(`Rule evaluation for ${filePath} took ${elapsed.toFixed(1)}ms (>10ms budget)`);
687
+ }
688
+ store.addViolations(filePath, result.violations);
689
+ })
690
+ .catch(() => {
691
+ // Rule evaluation failed — skip silently, don't break drift processing
692
+ });
693
+ }
694
+ }
695
+ function extractBodyLines(content, lineStart, lineEnd) {
696
+ const lines = content.split("\n");
697
+ return lines.slice(lineStart - 1, lineEnd).join("\n");
698
+ }
699
+ /** Regex for ES import statements: import { X, Y } from './path' */
700
+ const IMPORT_REGEX = /import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+['"]([^'"]+)['"]/g;
701
+ /**
702
+ * Extract import edges from file content.
703
+ * Resolves relative import paths to project-relative paths.
704
+ */
705
+ function extractImportEdges(content, filePath) {
706
+ const edges = [];
707
+ // Reset lastIndex for global regex
708
+ IMPORT_REGEX.lastIndex = 0;
709
+ for (let match = IMPORT_REGEX.exec(content); match !== null; match = IMPORT_REGEX.exec(content)) {
710
+ const namedImports = match[1]; // { X, Y }
711
+ const defaultImport = match[2]; // default import
712
+ const importPath = match[3] ?? "";
713
+ // Only handle relative imports (local project files)
714
+ if (!importPath.startsWith("."))
715
+ continue;
716
+ // Resolve target path relative to current file
717
+ const targetPath = resolveImportPath(filePath, importPath);
718
+ if (!targetPath)
719
+ continue;
720
+ if (namedImports) {
721
+ // Split named imports: { X, Y as Z } → ["X", "Y"]
722
+ for (const name of namedImports.split(",")) {
723
+ const trimmed = name
724
+ .trim()
725
+ .split(/\s+as\s+/)[0]
726
+ ?.trim();
727
+ if (trimmed) {
728
+ edges.push({ importedName: trimmed, targetPath });
729
+ }
730
+ }
731
+ }
732
+ if (defaultImport) {
733
+ edges.push({ importedName: defaultImport, targetPath });
734
+ }
735
+ }
736
+ return edges;
737
+ }
738
+ /**
739
+ * Resolve a relative import path to a project-relative file path.
740
+ * './service' from 'src/auth/handler.ts' → 'src/auth/service.ts'
741
+ */
742
+ function resolveImportPath(fromFile, importPath) {
743
+ // Remove file extension from current file to get directory
744
+ const dir = fromFile.replace(/\/[^/]+$/, "");
745
+ // Normalize the import path
746
+ let resolved = importPath;
747
+ if (resolved.startsWith("./")) {
748
+ resolved = `${dir}/${resolved.slice(2)}`;
749
+ }
750
+ else if (resolved.startsWith("../")) {
751
+ const parts = dir.split("/");
752
+ let rel = resolved;
753
+ while (rel.startsWith("../")) {
754
+ parts.pop();
755
+ rel = rel.slice(3);
756
+ }
757
+ resolved = [...parts, rel].join("/");
758
+ }
759
+ // Remove .js extension (NodeNext resolution: imports use .js but files are .ts)
760
+ resolved = resolved.replace(/\.js$/, "");
761
+ // Try common extensions
762
+ for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
763
+ const candidate = resolved + ext;
764
+ // Return project-relative path (we don't check existence here —
765
+ // the caller will look up entities in that file path)
766
+ return candidate;
767
+ }
768
+ return null;
769
+ }
770
+ /**
771
+ * Extract function call edges from within an entity's body.
772
+ * Simple regex: matches `name(` patterns that look like function calls.
773
+ */
774
+ function extractCallEdges(content, _entityName, callerKey) {
775
+ const edges = [];
776
+ const seen = new Set();
777
+ // Match function/method calls: identifier( or this.identifier( or obj.identifier(
778
+ const CALL_REGEX = /(?:this\.|[\w]+\.)?(\w+)\s*\(/g;
779
+ for (let match = CALL_REGEX.exec(content); match !== null; match = CALL_REGEX.exec(content)) {
780
+ const name = match[1];
781
+ if (!name || name.length < 2)
782
+ continue;
783
+ // Skip common built-ins and keywords
784
+ if (BUILTIN_NAMES.has(name))
785
+ continue;
786
+ if (seen.has(name))
787
+ continue;
788
+ seen.add(name);
789
+ edges.push({ callerKey, calledName: name });
790
+ }
791
+ return edges;
792
+ }
793
+ /** Names to skip during call extraction (language builtins, common patterns). */
794
+ const BUILTIN_NAMES = new Set([
795
+ "if",
796
+ "for",
797
+ "while",
798
+ "switch",
799
+ "catch",
800
+ "return",
801
+ "throw",
802
+ "new",
803
+ "typeof",
804
+ "instanceof",
805
+ "delete",
806
+ "void",
807
+ "require",
808
+ "import",
809
+ "console",
810
+ "log",
811
+ "warn",
812
+ "error",
813
+ "info",
814
+ "debug",
815
+ "parseInt",
816
+ "parseFloat",
817
+ "isNaN",
818
+ "isFinite",
819
+ "setTimeout",
820
+ "setInterval",
821
+ "clearTimeout",
822
+ "clearInterval",
823
+ "Promise",
824
+ "resolve",
825
+ "reject",
826
+ "then",
827
+ "catch",
828
+ "finally",
829
+ "Array",
830
+ "Object",
831
+ "String",
832
+ "Number",
833
+ "Boolean",
834
+ "Map",
835
+ "Set",
836
+ "JSON",
837
+ "parse",
838
+ "stringify",
839
+ "Math",
840
+ "Date",
841
+ "push",
842
+ "pop",
843
+ "shift",
844
+ "unshift",
845
+ "splice",
846
+ "slice",
847
+ "concat",
848
+ "map",
849
+ "filter",
850
+ "reduce",
851
+ "forEach",
852
+ "find",
853
+ "some",
854
+ "every",
855
+ "join",
856
+ "split",
857
+ "replace",
858
+ "match",
859
+ "test",
860
+ "exec",
861
+ "keys",
862
+ "values",
863
+ "entries",
864
+ "from",
865
+ "of",
866
+ "trim",
867
+ "includes",
868
+ "startsWith",
869
+ "endsWith",
870
+ "indexOf",
871
+ "length",
872
+ "toString",
873
+ "valueOf",
874
+ ]);