@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,582 @@
1
+ /**
2
+ * P10-TEST-03: Drift tracker tests — drift computation, overlay management.
3
+ */
4
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
8
+ import { entityKey } from "../intelligence/ast-extractor.js";
9
+ import { DRIFT_WATCHER_OPTIONS, DriftTracker, MtimeCache, determineOrigin, } from "../tracking/drift-tracker.js";
10
+ import { FileHashManager, contentSha256 } from "../tracking/file-hash-state.js";
11
+ let tempDir;
12
+ let projectRoot;
13
+ let unerrDir;
14
+ beforeEach(() => {
15
+ tempDir = join(tmpdir(), `unerr-drift-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
16
+ projectRoot = join(tempDir, "project");
17
+ unerrDir = join(projectRoot, ".unerr");
18
+ mkdirSync(join(unerrDir, "state"), { recursive: true });
19
+ mkdirSync(join(projectRoot, "src"), { recursive: true });
20
+ });
21
+ afterEach(() => {
22
+ try {
23
+ rmSync(tempDir, { recursive: true, force: true });
24
+ }
25
+ catch {
26
+ /* ignore */
27
+ }
28
+ });
29
+ /** Create a mock CozoGraphStore with in-memory drift overlay */
30
+ function createMockGraph(baseEntities = []) {
31
+ const driftOverlay = new Map();
32
+ return {
33
+ driftOverlay,
34
+ getEntity: (key) => baseEntities.find((e) => e.key === key) ?? null,
35
+ getCallersOf: () => [],
36
+ getCalleesOf: () => [],
37
+ getEntitiesByFile: (fp) => baseEntities.filter((e) => e.file_path === fp),
38
+ searchEntities: () => [],
39
+ getImports: () => [],
40
+ healthCheck: () => ({ status: "up", latencyMs: 0 }),
41
+ isLoaded: () => true,
42
+ loadSnapshot: () => { },
43
+ hasRules: () => false,
44
+ getRules: () => [],
45
+ getPatterns: () => [],
46
+ loadRules: () => { },
47
+ loadPatterns: () => { },
48
+ hasJustifications: () => false,
49
+ getBusinessContext: () => null,
50
+ getConventions: () => [],
51
+ loadJustifications: () => { },
52
+ upsertDriftEntity: (entity) => {
53
+ driftOverlay.set(entity.key, entity);
54
+ },
55
+ removeDriftEntity: (key) => {
56
+ driftOverlay.delete(key);
57
+ },
58
+ getDriftEntitiesForFile: (fp) => {
59
+ const result = [];
60
+ for (const [, e] of driftOverlay) {
61
+ if (e.file_path === fp)
62
+ result.push(e);
63
+ }
64
+ return result;
65
+ },
66
+ clearDriftOverlay: () => {
67
+ driftOverlay.clear();
68
+ },
69
+ findEntityByName: (name) => baseEntities.find((e) => e.name === name) ?? null,
70
+ getAllDriftEdges: () => [],
71
+ upsertDriftEdge: () => { },
72
+ clearDriftEdges: () => { },
73
+ getDriftSummary: () => {
74
+ const summary = {
75
+ added: 0,
76
+ modified: 0,
77
+ deleted: 0,
78
+ dependency_changed: 0,
79
+ total: 0,
80
+ };
81
+ for (const [, e] of driftOverlay) {
82
+ if (e.drift_status === "added")
83
+ summary.added++;
84
+ else if (e.drift_status === "modified")
85
+ summary.modified++;
86
+ else if (e.drift_status === "deleted")
87
+ summary.deleted++;
88
+ else if (e.drift_status === "dependency_changed")
89
+ summary.dependency_changed++;
90
+ }
91
+ summary.total =
92
+ summary.added +
93
+ summary.modified +
94
+ summary.deleted +
95
+ summary.dependency_changed;
96
+ return summary;
97
+ },
98
+ };
99
+ }
100
+ /**
101
+ * Create a mock graph with caller edge relationships.
102
+ * `callerMap` maps callee keys → array of caller LocalEntity objects.
103
+ */
104
+ function createMockGraphWithCallers(baseEntities, callerMap) {
105
+ const base = createMockGraph(baseEntities);
106
+ // Override getCallersOf to return from the caller map
107
+ base.getCallersOf = (key) => callerMap.get(key) ?? [];
108
+ return base;
109
+ }
110
+ describe("DriftTracker", () => {
111
+ it("detects added entities (new file)", async () => {
112
+ const graph = createMockGraph();
113
+ const fileHashManager = new FileHashManager(unerrDir);
114
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
115
+ // Write a TypeScript file with a function
116
+ writeFileSync(join(projectRoot, "src/new-feature.ts"), `export function newFeature() {
117
+ return "hello"
118
+ }`);
119
+ const result = await tracker.processFile("src/new-feature.ts", "abc123");
120
+ expect(result.filesProcessed).toBe(1);
121
+ expect(result.entitiesAdded).toBe(1);
122
+ expect(result.entitiesModified).toBe(0);
123
+ expect(result.entitiesDeleted).toBe(0);
124
+ expect(graph.driftOverlay.size).toBe(1);
125
+ const driftEntity = [...graph.driftOverlay.values()][0];
126
+ expect(driftEntity.name).toBe("newFeature");
127
+ expect(driftEntity.drift_status).toBe("added");
128
+ expect(driftEntity.file_path).toBe("src/new-feature.ts");
129
+ });
130
+ it("detects modified entities (content changed)", async () => {
131
+ // Base entity
132
+ const baseEntities = [
133
+ {
134
+ key: "test-key-1",
135
+ kind: "function",
136
+ name: "existingFn",
137
+ file_path: "src/existing.ts",
138
+ start_line: 1,
139
+ end_line: 0,
140
+ signature: "()",
141
+ body: "function existingFn() {\n return 1\n}",
142
+ fan_in: 0,
143
+ fan_out: 0,
144
+ risk_level: "normal",
145
+ community: -1,
146
+ },
147
+ ];
148
+ const graph = createMockGraph(baseEntities);
149
+ const fileHashManager = new FileHashManager(unerrDir);
150
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
151
+ // Write modified file
152
+ writeFileSync(join(projectRoot, "src/existing.ts"), `function existingFn() {
153
+ return 2
154
+ }`);
155
+ const result = await tracker.processFile("src/existing.ts", "abc123");
156
+ expect(result.filesProcessed).toBe(1);
157
+ // Either modified or added depending on key match
158
+ expect(result.entitiesModified + result.entitiesAdded).toBeGreaterThanOrEqual(1);
159
+ });
160
+ it("skips unchanged files", async () => {
161
+ const graph = createMockGraph();
162
+ const fileHashManager = new FileHashManager(unerrDir);
163
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
164
+ const content = "export function unchanged() { return 1 }";
165
+ writeFileSync(join(projectRoot, "src/unchanged.ts"), content);
166
+ // First process — should process
167
+ const r1 = await tracker.processFile("src/unchanged.ts", "abc123");
168
+ expect(r1.filesProcessed).toBe(1);
169
+ // Mark as processed manually
170
+ const sha = contentSha256(content);
171
+ fileHashManager.markProcessed("src/unchanged.ts", sha, "abc123");
172
+ fileHashManager.save();
173
+ // Second process — should skip
174
+ const r2 = await tracker.processFile("src/unchanged.ts", "abc123");
175
+ expect(r2.filesSkipped).toBe(1);
176
+ expect(r2.filesProcessed).toBe(0);
177
+ });
178
+ it("handles deleted files", async () => {
179
+ const baseEntities = [
180
+ {
181
+ key: "del-key-1",
182
+ kind: "function",
183
+ name: "deletedFn",
184
+ file_path: "src/deleted.ts",
185
+ start_line: 1,
186
+ end_line: 0,
187
+ signature: "",
188
+ body: "",
189
+ fan_in: 2,
190
+ fan_out: 1,
191
+ risk_level: "normal",
192
+ community: -1,
193
+ },
194
+ ];
195
+ const graph = createMockGraph(baseEntities);
196
+ const fileHashManager = new FileHashManager(unerrDir);
197
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
198
+ // Don't create the file — it's "deleted"
199
+ const result = await tracker.processFile("src/deleted.ts", "abc123");
200
+ expect(result.filesProcessed).toBe(1);
201
+ expect(result.entitiesDeleted).toBe(1);
202
+ const deletedEntity = [...graph.driftOverlay.values()][0];
203
+ expect(deletedEntity.drift_status).toBe("deleted");
204
+ expect(deletedEntity.name).toBe("deletedFn");
205
+ });
206
+ it("skips unsupported languages", async () => {
207
+ const graph = createMockGraph();
208
+ const fileHashManager = new FileHashManager(unerrDir);
209
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
210
+ writeFileSync(join(projectRoot, "data.json"), '{"key": "value"}');
211
+ const result = await tracker.processFile("data.json", "abc123");
212
+ expect(result.filesProcessed).toBe(0);
213
+ expect(result.filesSkipped).toBe(0);
214
+ expect(graph.driftOverlay.size).toBe(0);
215
+ });
216
+ it("processes batch of files", async () => {
217
+ const graph = createMockGraph();
218
+ const fileHashManager = new FileHashManager(unerrDir);
219
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
220
+ writeFileSync(join(projectRoot, "src/a.ts"), "export function alpha() { return 1 }");
221
+ writeFileSync(join(projectRoot, "src/b.ts"), "export function beta() { return 2 }");
222
+ const result = await tracker.processFiles(["src/a.ts", "src/b.ts"], "abc123");
223
+ expect(result.filesProcessed).toBe(2);
224
+ expect(result.entitiesAdded).toBe(2);
225
+ });
226
+ it("drift summary aggregates correctly", async () => {
227
+ const baseEntities = [
228
+ {
229
+ key: "mod-key-1",
230
+ kind: "function",
231
+ name: "oldFn",
232
+ file_path: "src/old.ts",
233
+ start_line: 1,
234
+ end_line: 0,
235
+ signature: "",
236
+ body: "function oldFn() { return 1 }",
237
+ fan_in: 0,
238
+ fan_out: 0,
239
+ risk_level: "normal",
240
+ community: -1,
241
+ },
242
+ ];
243
+ const graph = createMockGraph(baseEntities);
244
+ const fileHashManager = new FileHashManager(unerrDir);
245
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
246
+ // Add a new file
247
+ writeFileSync(join(projectRoot, "src/new.ts"), "export function newOne() { return 1 }");
248
+ await tracker.processFile("src/new.ts", "abc123");
249
+ // Delete an existing file (don't write it)
250
+ await tracker.processFile("src/old.ts", "abc123");
251
+ const summary = await tracker.getDriftSummary();
252
+ expect(summary.total).toBeGreaterThan(0);
253
+ expect(summary.added + summary.modified + summary.deleted).toBe(summary.total);
254
+ });
255
+ it("clears overlay on branch switch", async () => {
256
+ const graph = createMockGraph();
257
+ const fileHashManager = new FileHashManager(unerrDir);
258
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
259
+ writeFileSync(join(projectRoot, "src/a.ts"), "export function alpha() { return 1 }");
260
+ await tracker.processFile("src/a.ts", "abc123");
261
+ expect(graph.driftOverlay.size).toBe(1);
262
+ // Branch switch — clears and recomputes
263
+ writeFileSync(join(projectRoot, "src/b.ts"), "export function beta() { return 1 }");
264
+ await tracker.onBranchSwitch(["src/b.ts"], "def456");
265
+ // Old overlay should be cleared, new file processed
266
+ const summary = await tracker.getDriftSummary();
267
+ expect(summary.added).toBe(1);
268
+ });
269
+ it("saves drift summary to disk", async () => {
270
+ const graph = createMockGraph();
271
+ const fileHashManager = new FileHashManager(unerrDir);
272
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
273
+ writeFileSync(join(projectRoot, "src/a.ts"), "export function alpha() { return 1 }");
274
+ await tracker.processFiles(["src/a.ts"], "abc123");
275
+ const summaryPath = join(unerrDir, "drift", "drift_summary.json");
276
+ expect(existsSync(summaryPath)).toBe(true);
277
+ });
278
+ it("mtime cache skips file when mtime unchanged", async () => {
279
+ const graph = createMockGraph();
280
+ const fileHashManager = new FileHashManager(unerrDir);
281
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
282
+ const filePath = join(projectRoot, "src/stable.ts");
283
+ writeFileSync(filePath, "export function stable() { return 1 }");
284
+ // First call — processes the file (mtime is new)
285
+ const r1 = await tracker.processFile("src/stable.ts", "abc123");
286
+ expect(r1.filesProcessed).toBe(1);
287
+ // Second call — mtime unchanged, should skip via mtime cache
288
+ const r2 = await tracker.processFile("src/stable.ts", "abc123");
289
+ expect(r2.filesSkipped).toBe(1);
290
+ expect(r2.filesProcessed).toBe(0);
291
+ });
292
+ it("mtime cache processes file when content changes", async () => {
293
+ const graph = createMockGraph();
294
+ const fileHashManager = new FileHashManager(unerrDir);
295
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
296
+ const filePath = join(projectRoot, "src/changing.ts");
297
+ writeFileSync(filePath, "export function changing() { return 1 }");
298
+ // First call — processes
299
+ await tracker.processFile("src/changing.ts", "abc123");
300
+ // Modify file — mtime changes
301
+ writeFileSync(filePath, "export function changing() { return 2 }");
302
+ // Second call — mtime changed, should process
303
+ const r2 = await tracker.processFile("src/changing.ts", "abc123");
304
+ expect(r2.filesProcessed).toBe(1);
305
+ });
306
+ it("mtime cache clears on branch switch", async () => {
307
+ const graph = createMockGraph();
308
+ const fileHashManager = new FileHashManager(unerrDir);
309
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
310
+ const filePath = join(projectRoot, "src/branched.ts");
311
+ writeFileSync(filePath, "export function branched() { return 1 }");
312
+ // Process once to populate mtime cache
313
+ await tracker.processFile("src/branched.ts", "abc123");
314
+ // Branch switch clears mtime cache
315
+ await tracker.onBranchSwitch(["src/branched.ts"], "def456");
316
+ // After branch switch, same file should be reprocessed (mtime cache was cleared)
317
+ const r = await tracker.processFile("src/branched.ts", "def456");
318
+ // Will be processed (mtime cache cleared) but may be skipped by file hash manager
319
+ // since content hasn't changed. The point is mtime cache doesn't block it.
320
+ expect(r.filesProcessed + r.filesSkipped).toBe(1);
321
+ });
322
+ });
323
+ describe("MtimeCache", () => {
324
+ it("returns true for first check (new file)", () => {
325
+ const cache = new MtimeCache();
326
+ const filePath = join(projectRoot, "src/first.ts");
327
+ writeFileSync(filePath, "content");
328
+ expect(cache.check(filePath)).toBe(true);
329
+ });
330
+ it("returns false for unchanged file", () => {
331
+ const cache = new MtimeCache();
332
+ const filePath = join(projectRoot, "src/unchanged.ts");
333
+ writeFileSync(filePath, "content");
334
+ cache.check(filePath); // populate
335
+ expect(cache.check(filePath)).toBe(false);
336
+ });
337
+ it("returns true after file modification", () => {
338
+ const cache = new MtimeCache();
339
+ const filePath = join(projectRoot, "src/modified.ts");
340
+ writeFileSync(filePath, "content v1");
341
+ cache.check(filePath); // populate
342
+ // Modify — mtime changes
343
+ writeFileSync(filePath, "content v2");
344
+ expect(cache.check(filePath)).toBe(true);
345
+ });
346
+ it("returns true for non-existent file and evicts cache", () => {
347
+ const cache = new MtimeCache();
348
+ expect(cache.check("/nonexistent/file.ts")).toBe(true);
349
+ expect(cache.size).toBe(0);
350
+ });
351
+ it("evict removes file from cache", () => {
352
+ const cache = new MtimeCache();
353
+ const filePath = join(projectRoot, "src/evicted.ts");
354
+ writeFileSync(filePath, "content");
355
+ cache.check(filePath); // populate
356
+ expect(cache.size).toBe(1);
357
+ cache.evict(filePath);
358
+ expect(cache.size).toBe(0);
359
+ // Next check should return true (new entry)
360
+ expect(cache.check(filePath)).toBe(true);
361
+ });
362
+ it("clear removes all entries", () => {
363
+ const cache = new MtimeCache();
364
+ const file1 = join(projectRoot, "src/a.ts");
365
+ const file2 = join(projectRoot, "src/b.ts");
366
+ writeFileSync(file1, "a");
367
+ writeFileSync(file2, "b");
368
+ cache.check(file1);
369
+ cache.check(file2);
370
+ expect(cache.size).toBe(2);
371
+ cache.clear();
372
+ expect(cache.size).toBe(0);
373
+ });
374
+ });
375
+ describe("DRIFT_WATCHER_OPTIONS", () => {
376
+ it("has awaitWriteFinish with stabilityThreshold 300ms", () => {
377
+ expect(DRIFT_WATCHER_OPTIONS.awaitWriteFinish.stabilityThreshold).toBe(300);
378
+ expect(DRIFT_WATCHER_OPTIONS.awaitWriteFinish.pollInterval).toBe(100);
379
+ });
380
+ });
381
+ describe("determineOrigin", () => {
382
+ it("returns 'human' when lastSyncTimestamp is 0 (no sync)", () => {
383
+ expect(determineOrigin(0)).toBe("human");
384
+ });
385
+ it("returns 'ai' when <10s after sync", () => {
386
+ const recentSync = Date.now() - 5_000; // 5s ago
387
+ expect(determineOrigin(recentSync)).toBe("ai");
388
+ });
389
+ it("returns 'mixed' when 10-60s after sync", () => {
390
+ const mediumSync = Date.now() - 30_000; // 30s ago
391
+ expect(determineOrigin(mediumSync)).toBe("mixed");
392
+ });
393
+ it("returns 'human' when >60s after sync", () => {
394
+ const oldSync = Date.now() - 120_000; // 2min ago
395
+ expect(determineOrigin(oldSync)).toBe("human");
396
+ });
397
+ it("returns 'ai' at exactly 0ms after sync", () => {
398
+ const justNow = Date.now();
399
+ expect(determineOrigin(justNow)).toBe("ai");
400
+ });
401
+ });
402
+ describe("DriftTracker origin attribution", () => {
403
+ it("sets origin on added entities based on sync timestamp", async () => {
404
+ const graph = createMockGraph();
405
+ const fileHashManager = new FileHashManager(unerrDir);
406
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
407
+ // Simulate recent AI sync
408
+ tracker.setLastSyncTimestamp(Date.now() - 3_000);
409
+ writeFileSync(join(projectRoot, "src/ai-created.ts"), "export function aiCreated() { return 1 }");
410
+ await tracker.processFile("src/ai-created.ts", "abc123");
411
+ const entity = [...graph.driftOverlay.values()][0];
412
+ expect(entity?.origin).toBe("ai");
413
+ });
414
+ it("sets origin to 'human' when no sync has occurred", async () => {
415
+ const graph = createMockGraph();
416
+ const fileHashManager = new FileHashManager(unerrDir);
417
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
418
+ // No setLastSyncTimestamp call — default is 0
419
+ writeFileSync(join(projectRoot, "src/human-created.ts"), "export function humanCreated() { return 1 }");
420
+ await tracker.processFile("src/human-created.ts", "abc123");
421
+ const entity = [...graph.driftOverlay.values()][0];
422
+ expect(entity?.origin).toBe("human");
423
+ });
424
+ it("sets origin on deleted entities", async () => {
425
+ const baseEntities = [
426
+ {
427
+ key: "del-origin-key",
428
+ kind: "function",
429
+ name: "aboutToDelete",
430
+ file_path: "src/will-delete.ts",
431
+ start_line: 1,
432
+ end_line: 0,
433
+ signature: "",
434
+ body: "",
435
+ fan_in: 0,
436
+ fan_out: 0,
437
+ risk_level: "normal",
438
+ community: -1,
439
+ },
440
+ ];
441
+ const graph = createMockGraph(baseEntities);
442
+ const fileHashManager = new FileHashManager(unerrDir);
443
+ const tracker = new DriftTracker({ projectRoot, repoId: "repo1", unerrDir }, graph, fileHashManager);
444
+ // Simulate mixed timing
445
+ tracker.setLastSyncTimestamp(Date.now() - 25_000);
446
+ // File doesn't exist — deleted
447
+ await tracker.processFile("src/will-delete.ts", "abc123");
448
+ const entity = [...graph.driftOverlay.values()][0];
449
+ expect(entity?.origin).toBe("mixed");
450
+ expect(entity?.drift_status).toBe("deleted");
451
+ });
452
+ });
453
+ describe("Cross-file drift invalidation", () => {
454
+ const repoId = "repo1";
455
+ /** Build a base entity with a computable key */
456
+ function makeBaseEntity(filePath, name, kind, body) {
457
+ const key = entityKey(repoId, filePath, kind, name, "()");
458
+ return {
459
+ key,
460
+ kind,
461
+ name,
462
+ file_path: filePath,
463
+ start_line: 1,
464
+ end_line: 0,
465
+ signature: "()",
466
+ body,
467
+ fan_in: 0,
468
+ fan_out: 0,
469
+ risk_level: "normal",
470
+ community: -1,
471
+ };
472
+ }
473
+ it("propagates dependency_changed to callers in other files", async () => {
474
+ // File A has function `helperFn`, File B has function `callerFn` that calls it
475
+ const helperEntity = makeBaseEntity("src/helper.ts", "helperFn", "function", "function helperFn() {\n return 1\n}");
476
+ const callerEntity = makeBaseEntity("src/caller.ts", "callerFn", "function", "function callerFn() {\n return helperFn()\n}");
477
+ const callerMap = new Map();
478
+ callerMap.set(helperEntity.key, [callerEntity]);
479
+ const graph = createMockGraphWithCallers([helperEntity, callerEntity], callerMap);
480
+ const fileHashManager = new FileHashManager(unerrDir);
481
+ const tracker = new DriftTracker({ projectRoot, repoId, unerrDir }, graph, fileHashManager);
482
+ // Write modified helper (body changed)
483
+ writeFileSync(join(projectRoot, "src/helper.ts"), "export function helperFn() {\n return 999\n}");
484
+ // Write caller file (unchanged, but needs to exist)
485
+ writeFileSync(join(projectRoot, "src/caller.ts"), "export function callerFn() {\n return helperFn()\n}");
486
+ const result = await tracker.processFile("src/helper.ts", "abc123");
487
+ expect(result.crossFileInvalidated).toBe(1);
488
+ // callerFn should have dependency_changed in the overlay
489
+ const callerDrift = graph.driftOverlay.get(callerEntity.key);
490
+ expect(callerDrift).toBeDefined();
491
+ expect(callerDrift?.drift_status).toBe("dependency_changed");
492
+ expect(callerDrift?.file_path).toBe("src/caller.ts");
493
+ });
494
+ it("does NOT invalidate callers in the same file", async () => {
495
+ // Both entities in the same file
496
+ const entity1 = makeBaseEntity("src/same-file.ts", "baseFunc", "function", "function baseFunc() {\n return 1\n}");
497
+ const entity2 = makeBaseEntity("src/same-file.ts", "callerFunc", "function", "function callerFunc() {\n return baseFunc()\n}");
498
+ const callerMap = new Map();
499
+ callerMap.set(entity1.key, [entity2]); // callerFunc calls baseFunc, same file
500
+ const graph = createMockGraphWithCallers([entity1, entity2], callerMap);
501
+ const fileHashManager = new FileHashManager(unerrDir);
502
+ const tracker = new DriftTracker({ projectRoot, repoId, unerrDir }, graph, fileHashManager);
503
+ writeFileSync(join(projectRoot, "src/same-file.ts"), "export function baseFunc() {\n return 999\n}\nexport function callerFunc() {\n return baseFunc()\n}");
504
+ const result = await tracker.processFile("src/same-file.ts", "abc123");
505
+ // Same-file callers should NOT be cross-file invalidated
506
+ expect(result.crossFileInvalidated).toBe(0);
507
+ });
508
+ it("does NOT overwrite stronger drift status with dependency_changed", async () => {
509
+ const helperEntity = makeBaseEntity("src/dep.ts", "depFn", "function", "function depFn() {\n return 1\n}");
510
+ const callerEntity = makeBaseEntity("src/consumer.ts", "consumerFn", "function", "function consumerFn() {\n return depFn()\n}");
511
+ const callerMap = new Map();
512
+ callerMap.set(helperEntity.key, [callerEntity]);
513
+ const graph = createMockGraphWithCallers([helperEntity, callerEntity], callerMap);
514
+ const fileHashManager = new FileHashManager(unerrDir);
515
+ const tracker = new DriftTracker({ projectRoot, repoId, unerrDir }, graph, fileHashManager);
516
+ // Pre-populate overlay with a "modified" status for the caller
517
+ graph.driftOverlay.set(callerEntity.key, {
518
+ key: callerEntity.key,
519
+ name: "consumerFn",
520
+ kind: "function",
521
+ signature: "()",
522
+ body: "function consumerFn() { return depFn() }",
523
+ file_path: "src/consumer.ts",
524
+ line_start: 1,
525
+ line_end: 3,
526
+ content_hash: "abc",
527
+ drift_status: "modified",
528
+ intent_id: "",
529
+ modified_at: new Date().toISOString(),
530
+ origin: "human",
531
+ previous_body: "",
532
+ previous_signature: "",
533
+ });
534
+ // Modify the dependency
535
+ writeFileSync(join(projectRoot, "src/dep.ts"), "export function depFn() {\n return 999\n}");
536
+ const result = await tracker.processFile("src/dep.ts", "abc123");
537
+ // Should NOT overwrite the "modified" status
538
+ expect(result.crossFileInvalidated).toBe(0);
539
+ const callerDrift = graph.driftOverlay.get(callerEntity.key);
540
+ expect(callerDrift?.drift_status).toBe("modified");
541
+ });
542
+ it("propagates dependency_changed when entity is deleted", async () => {
543
+ const deletedEntity = makeBaseEntity("src/removed.ts", "removedFn", "function", "function removedFn() {\n return 1\n}");
544
+ const callerEntity = makeBaseEntity("src/uses-removed.ts", "usesFn", "function", "function usesFn() {\n return removedFn()\n}");
545
+ const callerMap = new Map();
546
+ callerMap.set(deletedEntity.key, [callerEntity]);
547
+ const graph = createMockGraphWithCallers([deletedEntity, callerEntity], callerMap);
548
+ const fileHashManager = new FileHashManager(unerrDir);
549
+ const tracker = new DriftTracker({ projectRoot, repoId, unerrDir }, graph, fileHashManager);
550
+ // Write the caller file (exists), but don't write removed.ts (deleted)
551
+ writeFileSync(join(projectRoot, "src/uses-removed.ts"), "export function usesFn() {\n return removedFn()\n}");
552
+ // Process the deleted file — entities should propagate dependency_changed
553
+ const result = await tracker.processFile("src/removed.ts", "abc123");
554
+ expect(result.entitiesDeleted).toBe(1);
555
+ // The caller in another file should get dependency_changed
556
+ const callerDrift = graph.driftOverlay.get(callerEntity.key);
557
+ expect(callerDrift).toBeDefined();
558
+ expect(callerDrift?.drift_status).toBe("dependency_changed");
559
+ });
560
+ it("counts crossFileInvalidated correctly in batch", async () => {
561
+ const entityA = makeBaseEntity("src/a.ts", "funcA", "function", "function funcA() {\n return 1\n}");
562
+ const callerB = makeBaseEntity("src/b.ts", "funcB", "function", "function funcB() {\n return funcA()\n}");
563
+ const callerC = makeBaseEntity("src/c.ts", "funcC", "function", "function funcC() {\n return funcA()\n}");
564
+ const callerMap = new Map();
565
+ callerMap.set(entityA.key, [callerB, callerC]);
566
+ const graph = createMockGraphWithCallers([entityA, callerB, callerC], callerMap);
567
+ const fileHashManager = new FileHashManager(unerrDir);
568
+ const tracker = new DriftTracker({ projectRoot, repoId, unerrDir }, graph, fileHashManager);
569
+ // Modify source entity
570
+ writeFileSync(join(projectRoot, "src/a.ts"), "export function funcA() {\n return 999\n}");
571
+ writeFileSync(join(projectRoot, "src/b.ts"), "export function funcB() {\n return funcA()\n}");
572
+ writeFileSync(join(projectRoot, "src/c.ts"), "export function funcC() {\n return funcA()\n}");
573
+ const result = await tracker.processFiles(["src/a.ts"], "abc123");
574
+ expect(result.crossFileInvalidated).toBe(2);
575
+ // Both callers should be in the overlay
576
+ expect(graph.driftOverlay.get(callerB.key)?.drift_status).toBe("dependency_changed");
577
+ expect(graph.driftOverlay.get(callerC.key)?.drift_status).toBe("dependency_changed");
578
+ // Summary should reflect it
579
+ const summary = await tracker.getDriftSummary();
580
+ expect(summary.dependency_changed).toBe(2);
581
+ });
582
+ });