@unerr-ai/unerr 0.1.0 → 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 (553) hide show
  1. package/dist/__tests__/architecture-guard.test.js +122 -0
  2. package/dist/__tests__/arg-validator.test.js +205 -0
  3. package/dist/__tests__/ast-extractor.test.js +203 -0
  4. package/dist/__tests__/auto-bootstrap.test.js +280 -0
  5. package/dist/__tests__/background-indexer.test.js +228 -0
  6. package/dist/__tests__/blast-radius-engine.test.js +200 -0
  7. package/dist/__tests__/bridge-isolation.test.js +37 -0
  8. package/dist/__tests__/budget-enforcer.test.js +53 -0
  9. package/dist/__tests__/cfg-test-detection-perf.test.js +82 -0
  10. package/dist/__tests__/change-narrative.test.js +190 -0
  11. package/dist/__tests__/check-commit.test.js +258 -0
  12. package/dist/__tests__/checksum.test.js +34 -0
  13. package/dist/__tests__/commit-watcher.test.js +154 -0
  14. package/dist/__tests__/community-detection.test.js +179 -0
  15. package/dist/__tests__/community-tools.test.js +299 -0
  16. package/dist/__tests__/components.test.js +449 -0
  17. package/dist/__tests__/compression-log.test.js +174 -0
  18. package/dist/__tests__/compression-quality-monitor.test.js +40 -0
  19. package/dist/__tests__/config-healer.test.js +165 -0
  20. package/dist/__tests__/context-ledger.test.js +58 -0
  21. package/dist/__tests__/convention-detector.test.js +99 -0
  22. package/dist/__tests__/convention-learner.test.js +86 -0
  23. package/dist/__tests__/correction-detector.test.js +330 -0
  24. package/dist/__tests__/daemon-autostart-install.test.js +283 -0
  25. package/dist/__tests__/daemon-bridge.test.js +222 -0
  26. package/dist/__tests__/daemon-dashboard.test.js +202 -0
  27. package/dist/__tests__/daemon-registry.test.js +240 -0
  28. package/dist/__tests__/daemon-supervisor.test.js +318 -0
  29. package/dist/__tests__/daemon-version-check.test.js +275 -0
  30. package/dist/__tests__/decision-point-detector.test.js +98 -0
  31. package/dist/__tests__/deep-link.test.js +143 -0
  32. package/dist/__tests__/disallowed-tools.test.js +115 -0
  33. package/dist/__tests__/drift-tracker.test.js +582 -0
  34. package/dist/__tests__/durability-scorer.test.js +152 -0
  35. package/dist/__tests__/efficiency-tracker.test.js +65 -0
  36. package/dist/__tests__/enrich.test.js +144 -0
  37. package/dist/__tests__/entity-rewind.test.js +248 -0
  38. package/dist/__tests__/ephemeral.test.js +111 -0
  39. package/dist/__tests__/exploration-cost.test.js +93 -0
  40. package/dist/__tests__/fact-generator.test.js +197 -0
  41. package/dist/__tests__/file-l0-graph.test.js +244 -0
  42. package/dist/__tests__/file-logger.test.js +82 -0
  43. package/dist/__tests__/file-outline.test.js +141 -0
  44. package/dist/__tests__/file-read-protocol.test.js +188 -0
  45. package/dist/__tests__/format-encoder.test.js +233 -0
  46. package/dist/__tests__/git-attribution.test.js +259 -0
  47. package/dist/__tests__/graph-temporal-joiner.test.js +219 -0
  48. package/dist/__tests__/health-grade-enhanced.test.js +138 -0
  49. package/dist/__tests__/health-map-data.test.js +173 -0
  50. package/dist/__tests__/helpers/mcp-harness.js +45 -0
  51. package/dist/__tests__/helpers/mcp-harness.test.js +68 -0
  52. package/dist/__tests__/hook-dedup.test.js +112 -0
  53. package/dist/__tests__/hook-runner.test.js +253 -0
  54. package/dist/__tests__/indexer-cfg.test.js +185 -0
  55. package/dist/__tests__/indexer-cross-file.test.js +172 -0
  56. package/dist/__tests__/indexer-extraction.test.js +245 -0
  57. package/dist/__tests__/indexer-incremental.test.js +232 -0
  58. package/dist/__tests__/indexer-language-expansion.test.js +165 -0
  59. package/dist/__tests__/init-push.test.js +131 -0
  60. package/dist/__tests__/instruction-writer.test.js +179 -0
  61. package/dist/__tests__/intelligence-integration.test.js +217 -0
  62. package/dist/__tests__/intent-correlator.test.js +175 -0
  63. package/dist/__tests__/intent-detector.test.js +235 -0
  64. package/dist/__tests__/intent-encoder.test.js +167 -0
  65. package/dist/__tests__/java-build-tool-detection.test.js +174 -0
  66. package/dist/__tests__/layer3-sprint-q.test.js +160 -0
  67. package/dist/__tests__/layer3-sprint-r.test.js +91 -0
  68. package/dist/__tests__/layer3-sprint-s.test.js +183 -0
  69. package/dist/__tests__/layer3-sprint-t.test.js +201 -0
  70. package/dist/__tests__/layer3-sprint-u.test.js +174 -0
  71. package/dist/__tests__/layer4-sprint-ba2.test.js +354 -0
  72. package/dist/__tests__/layer4-sprint-ba4.test.js +84 -0
  73. package/dist/__tests__/layer4-sprint-vs.test.js +105 -0
  74. package/dist/__tests__/ledger-chains.test.js +162 -0
  75. package/dist/__tests__/lifecycle-machine.test.js +226 -0
  76. package/dist/__tests__/local-chat-provider.test.js +170 -0
  77. package/dist/__tests__/local-convention-detector.test.js +308 -0
  78. package/dist/__tests__/local-embeddings.test.js +422 -0
  79. package/dist/__tests__/local-graph.test.js +540 -0
  80. package/dist/__tests__/local-indexer.test.js +228 -0
  81. package/dist/__tests__/local-intelligence-l3.test.js +332 -0
  82. package/dist/__tests__/local-llm.test.js +253 -0
  83. package/dist/__tests__/local-mode-offline.test.js +187 -0
  84. package/dist/__tests__/local-mode-stats.test.js +273 -0
  85. package/dist/__tests__/local-mode-tui.test.js +343 -0
  86. package/dist/__tests__/local-parse.test.js +199 -0
  87. package/dist/__tests__/log-tailer.test.js +208 -0
  88. package/dist/__tests__/loop-breaker.test.js +276 -0
  89. package/dist/__tests__/loop-miner.test.js +226 -0
  90. package/dist/__tests__/mcp-config.test.js +126 -0
  91. package/dist/__tests__/mcp-content-json.test.js +10 -0
  92. package/dist/__tests__/mcp-envelope.test.js +124 -0
  93. package/dist/__tests__/metrics-store.test.js +223 -0
  94. package/dist/__tests__/native-watcher.test.js +191 -0
  95. package/dist/__tests__/navigation-hooks-agent-aware.test.js +145 -0
  96. package/dist/__tests__/negative-knowledge.test.js +116 -0
  97. package/dist/__tests__/network-boundary.test.js +190 -0
  98. package/dist/__tests__/network-firewall.test.js +112 -0
  99. package/dist/__tests__/nudge-invariants.test.js +160 -0
  100. package/dist/__tests__/nudge-v2.test.js +225 -0
  101. package/dist/__tests__/offline-rewind.test.js +251 -0
  102. package/dist/__tests__/open-threads.test.js +89 -0
  103. package/dist/__tests__/output-compressor.test.js +93 -0
  104. package/dist/__tests__/pending-violations.test.js +112 -0
  105. package/dist/__tests__/persistence-effectiveness.test.js +143 -0
  106. package/dist/__tests__/provider-factory.test.js +42 -0
  107. package/dist/__tests__/providers.test.js +24 -0
  108. package/dist/__tests__/proxy.test.js +314 -0
  109. package/dist/__tests__/query-router.test.js +1018 -0
  110. package/dist/__tests__/reasoning-quality-route.test.js +138 -0
  111. package/dist/__tests__/redactor.test.js +120 -0
  112. package/dist/__tests__/resource-monitor.test.js +57 -0
  113. package/dist/__tests__/response-envelope.test.js +100 -0
  114. package/dist/__tests__/risk-classifier.test.js +101 -0
  115. package/dist/__tests__/risk-signal-scope.test.js +75 -0
  116. package/dist/__tests__/rule-evaluator.test.js +280 -0
  117. package/dist/__tests__/scip-decoder.test.js +49 -0
  118. package/dist/__tests__/scip-downloader.test.js +201 -0
  119. package/dist/__tests__/scip-merger.test.js +103 -0
  120. package/dist/__tests__/search-index.test.js +422 -0
  121. package/dist/__tests__/semantic-enrichment.test.js +360 -0
  122. package/dist/__tests__/session-brief-builder.test.js +187 -0
  123. package/dist/__tests__/session-context.test.js +221 -0
  124. package/dist/__tests__/session-continuity.test.js +144 -0
  125. package/dist/__tests__/session-dedup.test.js +74 -0
  126. package/dist/__tests__/session-event-wiring.test.js +206 -0
  127. package/dist/__tests__/session-events.test.js +149 -0
  128. package/dist/__tests__/session-legend.test.js +20 -0
  129. package/dist/__tests__/session-persistence.test.js +131 -0
  130. package/dist/__tests__/session-resume-block.test.js +107 -0
  131. package/dist/__tests__/session-resume.test.js +97 -0
  132. package/dist/__tests__/session-summary-writer.test.js +134 -0
  133. package/dist/__tests__/shadow-ledger.test.js +203 -0
  134. package/dist/__tests__/shell-classifier.test.js +151 -0
  135. package/dist/__tests__/shell-compression-floor.test.js +189 -0
  136. package/dist/__tests__/shell-compression-v2.test.js +339 -0
  137. package/dist/__tests__/shell-compressor.test.js +35 -0
  138. package/dist/__tests__/shell-hooks.test.js +128 -0
  139. package/dist/__tests__/shell-strategies.test.js +644 -0
  140. package/dist/__tests__/shell-tee.test.js +133 -0
  141. package/dist/__tests__/signal-dedup.test.js +158 -0
  142. package/dist/__tests__/signal-reinforcer.test.js +77 -0
  143. package/dist/__tests__/signal-scorer.test.js +251 -0
  144. package/dist/__tests__/signal-show-store.test.js +108 -0
  145. package/dist/__tests__/smart-truncate.test.js +215 -0
  146. package/dist/__tests__/snapshot-v2.test.js +113 -0
  147. package/dist/__tests__/sprint-l1-local-mode.test.js +130 -0
  148. package/dist/__tests__/sprint-l10-boot.test.js +220 -0
  149. package/dist/__tests__/sprint-l9-offline-commands.test.js +189 -0
  150. package/dist/__tests__/sprint-q-persistent-context.test.js +198 -0
  151. package/dist/__tests__/sprint-s1-wiring.test.js +215 -0
  152. package/dist/__tests__/sprint-s2-wiring.test.js +256 -0
  153. package/dist/__tests__/sprint-s3-wiring.test.js +195 -0
  154. package/dist/__tests__/sprint-s4-wiring.test.js +213 -0
  155. package/dist/__tests__/sprint-s6-hooks.test.js +222 -0
  156. package/dist/__tests__/sprint-s7-persistent.test.js +263 -0
  157. package/dist/__tests__/sprint-s8-value.test.js +167 -0
  158. package/dist/__tests__/sprint-s9-behavioral.test.js +179 -0
  159. package/dist/__tests__/sprint3-intelligence.test.js +297 -0
  160. package/dist/__tests__/sprint5-mcp-server.test.js +136 -0
  161. package/dist/__tests__/startup-display.test.js +302 -0
  162. package/dist/__tests__/startup-log-file.test.js +97 -0
  163. package/dist/__tests__/stash-manager.test.js +229 -0
  164. package/dist/__tests__/state-detector.test.js +92 -0
  165. package/dist/__tests__/status-dashboard.test.js +142 -0
  166. package/dist/__tests__/temporal-facts.test.js +292 -0
  167. package/dist/__tests__/temporal-routes.test.js +142 -0
  168. package/dist/__tests__/test-detector.test.js +174 -0
  169. package/dist/__tests__/theme.test.js +72 -0
  170. package/dist/__tests__/timeline-agents.test.js +122 -0
  171. package/dist/__tests__/timeline-bootstrap.test.js +176 -0
  172. package/dist/__tests__/timeline-filters.test.js +193 -0
  173. package/dist/__tests__/timeline-markers.test.js +151 -0
  174. package/dist/__tests__/timeline-routes.test.js +156 -0
  175. package/dist/__tests__/timeline-store.test.js +171 -0
  176. package/dist/__tests__/token-counter.test.js +86 -0
  177. package/dist/__tests__/token-estimator.test.js +96 -0
  178. package/dist/__tests__/token-flow-api.test.js +239 -0
  179. package/dist/__tests__/token-flow-instrumentation.test.js +437 -0
  180. package/dist/__tests__/token-flow-persistence.test.js +356 -0
  181. package/dist/__tests__/token-flow-routes.test.js +199 -0
  182. package/dist/__tests__/token-flow.test.js +695 -0
  183. package/dist/__tests__/tool-clusters.test.js +177 -0
  184. package/dist/__tests__/transport-mux.test.js +283 -0
  185. package/dist/__tests__/turn-segmenter.test.js +166 -0
  186. package/dist/__tests__/uninstall.test.js +141 -0
  187. package/dist/__tests__/warm-start-policy.test.js +271 -0
  188. package/dist/__tests__/wire-cap-nudge.test.js +77 -0
  189. package/dist/__tests__/worker-pool.test.js +101 -0
  190. package/dist/behaviors/agent-llm-bridge.js +166 -0
  191. package/dist/behaviors/architecture-guard.js +256 -0
  192. package/dist/behaviors/auto-doc.js +247 -0
  193. package/dist/behaviors/cascade-guard.js +289 -0
  194. package/dist/behaviors/change-narrative.js +270 -0
  195. package/dist/behaviors/convention-drift.js +290 -0
  196. package/dist/behaviors/framework.js +235 -0
  197. package/dist/behaviors/guard-formatter.js +44 -0
  198. package/dist/behaviors/incomplete-work.js +270 -0
  199. package/dist/behaviors/loop-breaker.js +300 -0
  200. package/dist/behaviors/session-continuity.js +208 -0
  201. package/dist/cli.js +992 -706
  202. package/dist/commands/branches.js +97 -0
  203. package/dist/commands/check-commit.js +225 -0
  204. package/dist/commands/compress-output.js +64 -0
  205. package/dist/commands/config-verify.js +243 -0
  206. package/dist/commands/daemon.js +905 -0
  207. package/dist/commands/dashboard.js +52 -0
  208. package/dist/commands/debug.js +200 -0
  209. package/dist/commands/enrich.js +184 -0
  210. package/dist/commands/exec.js +233 -0
  211. package/dist/commands/gain.js +156 -0
  212. package/dist/commands/hook.js +88 -0
  213. package/dist/commands/index.js +88 -0
  214. package/dist/commands/init.js +74 -0
  215. package/dist/commands/install.js +505 -0
  216. package/dist/commands/learn.js +116 -0
  217. package/dist/commands/manifest.js +193 -0
  218. package/dist/commands/rewind.js +103 -0
  219. package/dist/commands/serve.js +19 -0
  220. package/dist/commands/setup-wizard.js +414 -0
  221. package/dist/commands/skills.js +64 -0
  222. package/dist/commands/stats.js +20 -0
  223. package/dist/commands/status.js +654 -0
  224. package/dist/commands/timeline.js +139 -0
  225. package/dist/commands/uninstall.js +230 -0
  226. package/dist/components/App.js +109 -0
  227. package/dist/components/Banner.js +12 -0
  228. package/dist/components/ConfirmPrompt.js +25 -0
  229. package/dist/components/DriftSummary.js +23 -0
  230. package/dist/components/GradeBadge.js +15 -0
  231. package/dist/components/HealthCard.js +18 -0
  232. package/dist/components/InkSpinner.js +22 -0
  233. package/dist/components/InputBox.js +17 -0
  234. package/dist/components/KeyValue.js +13 -0
  235. package/dist/components/MessageList.js +14 -0
  236. package/dist/components/ProgressBar.js +26 -0
  237. package/dist/components/Section.js +16 -0
  238. package/dist/components/SessionSummaryCard.js +73 -0
  239. package/dist/components/StartupDisplay.js +24 -0
  240. package/dist/components/StatusDashboard.js +57 -0
  241. package/dist/components/StatusLine.js +8 -0
  242. package/dist/components/StepLine.js +22 -0
  243. package/dist/components/Theme.js +20 -0
  244. package/dist/components/ToolProgress.js +8 -0
  245. package/dist/components/ViolationList.js +21 -0
  246. package/dist/components/render.js +13 -0
  247. package/dist/config/agent-registry.js +237 -0
  248. package/dist/config/claude-settings-hooks.js +304 -0
  249. package/dist/config/hook-installer.js +65 -0
  250. package/dist/config/instruction-writer.js +388 -0
  251. package/dist/config/mcp-config-writer.js +266 -0
  252. package/dist/config/settings.js +174 -0
  253. package/dist/config/tool-detector.js +42 -0
  254. package/dist/config/value-surfacing.js +119 -0
  255. package/dist/core/context-assembly.js +108 -0
  256. package/dist/core/conversation.js +33 -0
  257. package/dist/core/local-chat-provider.js +475 -0
  258. package/dist/core/provider-factory.js +55 -0
  259. package/dist/core/providers.js +90 -0
  260. package/dist/core/query-engine.js +174 -0
  261. package/dist/daemon/api.js +312 -0
  262. package/dist/daemon/autostart.js +119 -0
  263. package/dist/daemon/bootstrap.js +39 -0
  264. package/dist/daemon/client.js +164 -0
  265. package/dist/daemon/detect-ci.js +81 -0
  266. package/dist/daemon/platform-linux.js +146 -0
  267. package/dist/daemon/platform-macos.js +134 -0
  268. package/dist/daemon/platform-windows.js +116 -0
  269. package/dist/daemon/process-manager.js +299 -0
  270. package/dist/daemon/protocol.js +23 -0
  271. package/dist/daemon/registry.js +270 -0
  272. package/dist/daemon/settings-schema.js +72 -0
  273. package/dist/daemon/system-health.js +134 -0
  274. package/dist/daemon/version-checker.js +262 -0
  275. package/dist/daemon/warm-start.js +223 -0
  276. package/dist/entrypoints/cli.js +1043 -0
  277. package/dist/entrypoints/daemon.js +380 -0
  278. package/dist/entrypoints/repl.js +147 -0
  279. package/dist/hooks/adapters/claude-code.js +90 -0
  280. package/dist/hooks/adapters/cline.js +100 -0
  281. package/dist/hooks/adapters/cursor.js +98 -0
  282. package/dist/hooks/hook-dedup.js +79 -0
  283. package/dist/hooks/hook-runner.js +113 -0
  284. package/dist/hooks/navigation-hooks.js +175 -0
  285. package/dist/hooks/prompt-hooks.js +63 -0
  286. package/dist/hooks/shell-hooks.js +47 -0
  287. package/dist/ignore.js +111 -0
  288. package/dist/intelligence/approach-suggester.js +61 -0
  289. package/dist/intelligence/ast-extractor.js +2615 -0
  290. package/dist/intelligence/ast-worker.js +34 -0
  291. package/dist/intelligence/background-indexer.js +121 -0
  292. package/dist/intelligence/blast-radius.js +200 -0
  293. package/dist/intelligence/community-detection.js +691 -0
  294. package/dist/intelligence/community-detector.js +184 -0
  295. package/dist/intelligence/computation-scheduler.js +75 -0
  296. package/dist/intelligence/confidence-propagation.js +47 -0
  297. package/dist/intelligence/convention-detector.js +242 -0
  298. package/dist/intelligence/convention-learner.js +205 -0
  299. package/dist/intelligence/convention-matcher.js +205 -0
  300. package/dist/intelligence/cozo-schema.js +376 -0
  301. package/dist/intelligence/decision-point-detector.js +90 -0
  302. package/dist/intelligence/deep-dive-tools.js +586 -0
  303. package/dist/intelligence/durability-scorer.js +84 -0
  304. package/dist/intelligence/exploration-cost.js +204 -0
  305. package/dist/intelligence/exploration-pattern-tracker.js +61 -0
  306. package/dist/intelligence/fact-generator.js +322 -0
  307. package/dist/intelligence/facts-schema.js +90 -0
  308. package/dist/intelligence/file-intelligence.js +59 -0
  309. package/dist/intelligence/graph-holder.js +220 -0
  310. package/dist/intelligence/graph-temporal-joiner.js +238 -0
  311. package/dist/intelligence/health-grade.js +423 -0
  312. package/dist/intelligence/health-grader.js +200 -0
  313. package/dist/intelligence/health-map-data.js +259 -0
  314. package/dist/intelligence/import-symbols.js +136 -0
  315. package/dist/intelligence/incremental-indexer.js +658 -0
  316. package/dist/intelligence/indexer/centrality.js +62 -0
  317. package/dist/intelligence/indexer/cfg-context.js +95 -0
  318. package/dist/intelligence/indexer/confidence.js +34 -0
  319. package/dist/intelligence/indexer/cross-file-resolver.js +104 -0
  320. package/dist/intelligence/indexer/edge-repair.js +89 -0
  321. package/dist/intelligence/indexer/entity-key.js +17 -0
  322. package/dist/intelligence/indexer/export-map.js +132 -0
  323. package/dist/intelligence/indexer/git-cochange.js +128 -0
  324. package/dist/intelligence/indexer/graph-patch.js +147 -0
  325. package/dist/intelligence/indexer/incremental.js +78 -0
  326. package/dist/intelligence/indexer/ingest.js +160 -0
  327. package/dist/intelligence/indexer/language-detect.js +226 -0
  328. package/dist/intelligence/indexer/metadata.js +63 -0
  329. package/dist/intelligence/indexer/mutation-tracker.js +79 -0
  330. package/dist/intelligence/indexer/orchestrator.js +155 -0
  331. package/dist/intelligence/indexer/plugin-interface.js +31 -0
  332. package/dist/intelligence/indexer/plugins/csharp.js +440 -0
  333. package/dist/intelligence/indexer/plugins/go.js +335 -0
  334. package/dist/intelligence/indexer/plugins/java.js +370 -0
  335. package/dist/intelligence/indexer/plugins/python.js +358 -0
  336. package/dist/intelligence/indexer/plugins/regex-fallback.js +82 -0
  337. package/dist/intelligence/indexer/plugins/ruby.js +290 -0
  338. package/dist/intelligence/indexer/plugins/rust.js +484 -0
  339. package/dist/intelligence/indexer/plugins/tier2-generic.js +310 -0
  340. package/dist/intelligence/indexer/plugins/typescript.js +456 -0
  341. package/dist/intelligence/indexer/resource-monitor.js +93 -0
  342. package/dist/intelligence/indexer/scip/decoder.js +253 -0
  343. package/dist/intelligence/indexer/scip/detector.js +232 -0
  344. package/dist/intelligence/indexer/scip/downloader.js +427 -0
  345. package/dist/intelligence/indexer/scip/fallback.js +34 -0
  346. package/dist/intelligence/indexer/scip/merger.js +109 -0
  347. package/dist/intelligence/indexer/scip/orchestrator.js +433 -0
  348. package/dist/intelligence/indexer/scip/runner.js +98 -0
  349. package/dist/intelligence/indexer/snapshot.js +66 -0
  350. package/dist/intelligence/indexer/test-detector.js +196 -0
  351. package/dist/intelligence/indexer/watch-integration.js +61 -0
  352. package/dist/intelligence/indexer/worker.js +85 -0
  353. package/dist/intelligence/local-convention-detector.js +437 -0
  354. package/dist/intelligence/local-embeddings.js +190 -0
  355. package/dist/intelligence/local-graph.js +1946 -0
  356. package/dist/intelligence/local-indexer.js +1575 -0
  357. package/dist/intelligence/local-llm.js +163 -0
  358. package/dist/intelligence/local-rule-generator.js +154 -0
  359. package/dist/intelligence/local-snapshot.js +213 -0
  360. package/dist/intelligence/negative-knowledge.js +103 -0
  361. package/dist/intelligence/persistent-db.js +85 -0
  362. package/dist/intelligence/query-router.js +2556 -0
  363. package/dist/intelligence/risk-classifier.js +116 -0
  364. package/dist/intelligence/rule-evaluator.js +380 -0
  365. package/dist/intelligence/rule-generator.js +49 -0
  366. package/dist/intelligence/search-index.js +173 -0
  367. package/dist/intelligence/semantic/docstring-extractor.js +67 -0
  368. package/dist/intelligence/semantic/embedding-store.js +52 -0
  369. package/dist/intelligence/semantic/enrichment-orchestrator.js +48 -0
  370. package/dist/intelligence/semantic/git-message-miner.js +114 -0
  371. package/dist/intelligence/semantic/identifier-tokenizer.js +51 -0
  372. package/dist/intelligence/semantic/node2vec-embeddings.js +71 -0
  373. package/dist/intelligence/semantic/node2vec-walks.js +103 -0
  374. package/dist/intelligence/semantic/path-domain-inference.js +112 -0
  375. package/dist/intelligence/semantic/similarity-engine.js +60 -0
  376. package/dist/intelligence/semantic/tfidf-vectors.js +88 -0
  377. package/dist/intelligence/session-brief-builder.js +159 -0
  378. package/dist/intelligence/session-context.js +221 -0
  379. package/dist/intelligence/session-health-monitor.js +211 -0
  380. package/dist/intelligence/session-narrative.js +197 -0
  381. package/dist/intelligence/session-pattern-analyzer.js +218 -0
  382. package/dist/intelligence/signal-scorer.js +390 -0
  383. package/dist/intelligence/signal-show-store.js +182 -0
  384. package/dist/intelligence/smart-truncate.js +158 -0
  385. package/dist/intelligence/subgraph-cache.js +88 -0
  386. package/dist/intelligence/temporal-facts.js +494 -0
  387. package/dist/intelligence/token-estimator.js +100 -0
  388. package/dist/intelligence/tool-injector.js +87 -0
  389. package/dist/intelligence/tree-sitter-loader.js +71 -0
  390. package/dist/intelligence/worker-pool.js +116 -0
  391. package/dist/proxy/arg-validator.js +79 -0
  392. package/dist/proxy/auto-bootstrap.js +167 -0
  393. package/dist/proxy/bridge.js +147 -0
  394. package/dist/proxy/budget-enforcer.js +70 -0
  395. package/dist/proxy/compression-quality-monitor.js +160 -0
  396. package/dist/proxy/compression-stats.js +51 -0
  397. package/dist/proxy/context-rot-detector.js +137 -0
  398. package/dist/proxy/drift-detector.js +139 -0
  399. package/dist/proxy/efficiency-tracker.js +79 -0
  400. package/dist/proxy/fact-ranking.js +154 -0
  401. package/dist/proxy/format-encoder.js +266 -0
  402. package/dist/proxy/http-transport.js +90 -0
  403. package/dist/proxy/lifecycle-actor.js +55 -0
  404. package/dist/proxy/lifecycle-machine.js +187 -0
  405. package/dist/proxy/log-tailer.js +265 -0
  406. package/dist/proxy/model-pricing.js +98 -0
  407. package/dist/proxy/network-firewall.js +141 -0
  408. package/dist/proxy/nudge-state.js +93 -0
  409. package/dist/proxy/output-compressor.js +185 -0
  410. package/dist/proxy/pid-lock.js +291 -0
  411. package/dist/proxy/proxy-context.js +11 -0
  412. package/dist/proxy/proxy.js +2633 -0
  413. package/dist/proxy/response-enrichment.js +32 -0
  414. package/dist/proxy/response-envelope.js +313 -0
  415. package/dist/proxy/session-dedup.js +82 -0
  416. package/dist/proxy/session-legend.js +30 -0
  417. package/dist/proxy/session-persistence.js +210 -0
  418. package/dist/proxy/session-resume.js +94 -0
  419. package/dist/proxy/session-stats.js +513 -0
  420. package/dist/proxy/shell-classifier.js +1346 -0
  421. package/dist/proxy/shell-compression-log.js +93 -0
  422. package/dist/proxy/shell-compressor.js +390 -0
  423. package/dist/proxy/shell-graph-boost.js +202 -0
  424. package/dist/proxy/shell-monitor-map.js +18 -0
  425. package/dist/proxy/shell-stats.js +54 -0
  426. package/dist/proxy/shell-strategies/cloud.js +215 -0
  427. package/dist/proxy/shell-strategies/diff.js +159 -0
  428. package/dist/proxy/shell-strategies/error-diagnostic.js +796 -0
  429. package/dist/proxy/shell-strategies/filter-dsl.js +358 -0
  430. package/dist/proxy/shell-strategies/git-status.js +177 -0
  431. package/dist/proxy/shell-strategies/key-value.js +193 -0
  432. package/dist/proxy/shell-strategies/log-text.js +154 -0
  433. package/dist/proxy/shell-strategies/omni.js +188 -0
  434. package/dist/proxy/shell-strategies/progress.js +55 -0
  435. package/dist/proxy/shell-strategies/redact.js +76 -0
  436. package/dist/proxy/shell-strategies/structured.js +241 -0
  437. package/dist/proxy/shell-strategies/tabular.js +243 -0
  438. package/dist/proxy/shell-strategies/test-results-types.js +13 -0
  439. package/dist/proxy/shell-strategies/test-results.js +784 -0
  440. package/dist/proxy/shell-strategies/tree-paths.js +144 -0
  441. package/dist/proxy/shell-strategies/yaml.js +182 -0
  442. package/dist/proxy/shell-tee.js +111 -0
  443. package/dist/proxy/signal-dedup.js +171 -0
  444. package/dist/proxy/startup-renderer.js +158 -0
  445. package/dist/proxy/task-token-display.js +38 -0
  446. package/dist/proxy/token-counter.js +61 -0
  447. package/dist/proxy/tool-clusters.js +273 -0
  448. package/dist/proxy/tool-definitions.js +525 -0
  449. package/dist/proxy/transport-mux.js +229 -0
  450. package/dist/proxy/wire-cap.js +268 -0
  451. package/dist/schemas/api/skills.js +19 -0
  452. package/dist/schemas/common/errors.js +7 -0
  453. package/dist/schemas/common/headers.js +5 -0
  454. package/dist/schemas/entities/edge.js +25 -0
  455. package/dist/schemas/entities/entity.js +22 -0
  456. package/dist/schemas/entities/rule.js +18 -0
  457. package/dist/schemas/index.js +14 -0
  458. package/dist/server/event-bus.js +59 -0
  459. package/dist/server/http.js +156 -0
  460. package/dist/server/middleware.js +70 -0
  461. package/dist/server/routes/drift.js +97 -0
  462. package/dist/server/routes/intelligence.js +1217 -0
  463. package/dist/server/routes/reasoning-quality.js +444 -0
  464. package/dist/server/routes/session.js +86 -0
  465. package/dist/server/routes/stream.js +120 -0
  466. package/dist/server/routes/system.js +73 -0
  467. package/dist/server/routes/temporal.js +170 -0
  468. package/dist/server/routes/timeline.js +232 -0
  469. package/dist/server/routes/token-flow.js +403 -0
  470. package/dist/skills/effectiveness-tracker.js +93 -0
  471. package/dist/skills/local-pack.js +380 -0
  472. package/dist/skills/resolver.js +495 -0
  473. package/dist/state-detector.js +83 -0
  474. package/dist/timeline/intent-detector.js +263 -0
  475. package/dist/timeline/loop-miner.js +140 -0
  476. package/dist/timeline/open-threads.js +49 -0
  477. package/dist/timeline/signal-reinforcer.js +62 -0
  478. package/dist/timeline/timeline-bootstrap.js +151 -0
  479. package/dist/timeline/timeline-store.js +618 -0
  480. package/dist/tools/coding/bash.js +49 -0
  481. package/dist/tools/coding/file-edit.js +72 -0
  482. package/dist/tools/coding/file-outline.js +227 -0
  483. package/dist/tools/coding/file-read-protocol.js +425 -0
  484. package/dist/tools/coding/file-read.js +35 -0
  485. package/dist/tools/coding/file-write.js +43 -0
  486. package/dist/tools/coding/glob-tool.js +109 -0
  487. package/dist/tools/coding/grep.js +162 -0
  488. package/dist/tools/coding/index.js +27 -0
  489. package/dist/tools/intelligence/index.js +269 -0
  490. package/dist/tools/intelligence/record-fact.js +48 -0
  491. package/dist/tools/intelligence/timeline-markers.js +130 -0
  492. package/dist/tools/registry.js +47 -0
  493. package/dist/tools/types.js +8 -0
  494. package/dist/tracking/auto-snapshot-triggers.js +246 -0
  495. package/dist/tracking/branch-context.js +115 -0
  496. package/dist/tracking/branch-snapshot.js +217 -0
  497. package/dist/tracking/causal-bridge.js +317 -0
  498. package/dist/tracking/circuit-breaker.js +147 -0
  499. package/dist/tracking/commit-watcher.js +114 -0
  500. package/dist/tracking/context-ledger.js +119 -0
  501. package/dist/tracking/correction-detector.js +324 -0
  502. package/dist/tracking/drift-tracker.js +874 -0
  503. package/dist/tracking/durability-tracker.js +94 -0
  504. package/dist/tracking/entity-rewind.js +200 -0
  505. package/dist/tracking/file-hash-state.js +114 -0
  506. package/dist/tracking/git-attribution.js +132 -0
  507. package/dist/tracking/git-trailers.js +171 -0
  508. package/dist/tracking/intelligence-counter.js +46 -0
  509. package/dist/tracking/intent-correlator.js +202 -0
  510. package/dist/tracking/intent-encoder.js +52 -0
  511. package/dist/tracking/intent-token-tracker.js +159 -0
  512. package/dist/tracking/ledger-archiver.js +94 -0
  513. package/dist/tracking/ledger-chains.js +245 -0
  514. package/dist/tracking/metrics-store.js +361 -0
  515. package/dist/tracking/native-watcher.js +131 -0
  516. package/dist/tracking/offline-rewind.js +295 -0
  517. package/dist/tracking/pending-violations.js +74 -0
  518. package/dist/tracking/persistence-effectiveness.js +167 -0
  519. package/dist/tracking/prompt-durability.js +202 -0
  520. package/dist/tracking/quality-signals.js +213 -0
  521. package/dist/tracking/redactor.js +73 -0
  522. package/dist/tracking/rewind-engine.js +161 -0
  523. package/dist/tracking/session-history.js +128 -0
  524. package/dist/tracking/session-receipt.js +88 -0
  525. package/dist/tracking/session-summary-writer.js +157 -0
  526. package/dist/tracking/shadow-ledger.js +321 -0
  527. package/dist/tracking/stash-manager.js +258 -0
  528. package/dist/tracking/timeline-fork.js +213 -0
  529. package/dist/tracking/timeline.js +69 -0
  530. package/dist/tracking/token-flow.js +276 -0
  531. package/dist/tracking/turn-segmenter.js +122 -0
  532. package/dist/tracking/weekly-accumulator.js +179 -0
  533. package/dist/tracking/working-snapshots.js +188 -0
  534. package/dist/tracking/workspace-manifest.js +176 -0
  535. package/dist/transport/http.js +102 -0
  536. package/dist/ui/assets/index-BsMTQdhX.js +10 -0
  537. package/dist/ui/index.html +1 -1
  538. package/dist/utils/counterfactual.js +65 -0
  539. package/dist/utils/deep-link.js +34 -0
  540. package/dist/utils/detect.js +193 -0
  541. package/dist/utils/exec.js +73 -0
  542. package/dist/utils/file-logger.js +87 -0
  543. package/dist/utils/format-error.js +29 -0
  544. package/dist/utils/git.js +181 -0
  545. package/dist/utils/log.js +57 -0
  546. package/dist/utils/logger.js +35 -0
  547. package/dist/utils/mcp-content-json.js +8 -0
  548. package/dist/utils/session-logger.js +154 -0
  549. package/dist/utils/startup-log.js +512 -0
  550. package/dist/utils/ui.js +56 -0
  551. package/package.json +5 -3
  552. package/scripts/postinstall.mjs +299 -0
  553. package/dist/ui/assets/index-B-0HTtUR.js +0 -10
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Sprint L10: Unified Boot State Machine & Session Logging Tests
3
+ *
4
+ * Tests:
5
+ * 1. Session logger — file creation, NDJSON format, cleanup, module loggers
6
+ * 2. Preflight — lm:false denial, lm:true proceed, field absent, timeout, zero logs
7
+ * 3. Setup wizard — WizardResult types, config file generation, repo ID generation
8
+ * 4. Command visibility — only chat/status/debug shown in help
9
+ */
10
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync, } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
14
+ let tempDir;
15
+ beforeEach(() => {
16
+ tempDir = join(tmpdir(), `unerr-l10-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
17
+ mkdirSync(tempDir, { recursive: true });
18
+ });
19
+ afterEach(async () => {
20
+ // Give pino async transport time to flush before removing temp dir
21
+ await new Promise((resolve) => setTimeout(resolve, 150));
22
+ try {
23
+ rmSync(tempDir, { recursive: true, force: true });
24
+ }
25
+ catch {
26
+ /* ignore */
27
+ }
28
+ });
29
+ // ── 1. Session Logger ─────────────────────────────────────────────
30
+ describe("Session Logger", () => {
31
+ it("creates log file in .unerr/logs/ with NDJSON format", async () => {
32
+ // Reset module state for fresh logger
33
+ vi.resetModules();
34
+ const { initSessionLogger, getSessionLogPath, flushSessionLogger } = await import("../utils/session-logger.js");
35
+ const logger = initSessionLogger({ cwd: tempDir, level: "info" });
36
+ logger.info({ module: "test", msg: "hello world" });
37
+ flushSessionLogger();
38
+ // Give pino async transport a moment to flush
39
+ await new Promise((resolve) => setTimeout(resolve, 100));
40
+ const logPath = getSessionLogPath();
41
+ expect(logPath).toBeTruthy();
42
+ expect(existsSync(logPath)).toBe(true);
43
+ // Verify path is under .unerr/logs/
44
+ expect(logPath).toContain(join(".unerr", "logs", "session-"));
45
+ expect(logPath).toMatch(/\.log$/);
46
+ // Verify NDJSON format — each line is valid JSON
47
+ const content = readFileSync(logPath, "utf-8").trim();
48
+ const lines = content.split("\n").filter((l) => l.trim());
49
+ expect(lines.length).toBeGreaterThanOrEqual(1);
50
+ for (const line of lines) {
51
+ const parsed = JSON.parse(line);
52
+ expect(parsed).toHaveProperty("level");
53
+ expect(parsed).toHaveProperty("session_id");
54
+ }
55
+ });
56
+ it("returns same logger instance on repeated init calls", async () => {
57
+ vi.resetModules();
58
+ const { initSessionLogger } = await import("../utils/session-logger.js");
59
+ const logger1 = initSessionLogger({ cwd: tempDir });
60
+ const logger2 = initSessionLogger({ cwd: tempDir });
61
+ expect(logger1).toBe(logger2);
62
+ });
63
+ it("createSessionModuleLogger produces child with module field", async () => {
64
+ vi.resetModules();
65
+ const { initSessionLogger, createSessionModuleLogger, getSessionLogPath, flushSessionLogger, } = await import("../utils/session-logger.js");
66
+ initSessionLogger({ cwd: tempDir, level: "info" });
67
+ const modLog = createSessionModuleLogger("boot");
68
+ modLog.info({ msg: "boot started" });
69
+ flushSessionLogger();
70
+ await new Promise((resolve) => setTimeout(resolve, 100));
71
+ const logPath = getSessionLogPath();
72
+ const content = readFileSync(logPath, "utf-8").trim();
73
+ const lines = content.split("\n").filter((l) => l.trim());
74
+ const bootLine = lines.find((l) => {
75
+ const parsed = JSON.parse(l);
76
+ return parsed.tag === "boot" && parsed.msg.includes("boot started");
77
+ });
78
+ expect(bootLine).toBeTruthy();
79
+ });
80
+ it("getSessionId returns a UUID-format string", async () => {
81
+ vi.resetModules();
82
+ const { getSessionId } = await import("../utils/session-logger.js");
83
+ const id = getSessionId();
84
+ expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
85
+ });
86
+ it("cleanup removes files older than retention period", async () => {
87
+ vi.resetModules();
88
+ // Create fake old log files
89
+ const logsDir = join(tempDir, ".unerr", "logs");
90
+ mkdirSync(logsDir, { recursive: true });
91
+ // Create 12 fake log files (exceeds MAX_FILES of 10)
92
+ for (let i = 0; i < 12; i++) {
93
+ const fakePath = join(logsDir, `session-2020-01-${String(i + 1).padStart(2, "0")}-120000.log`);
94
+ writeFileSync(fakePath, `{"level":"info","msg":"old"}\n`);
95
+ // Set mtime to 60 days ago
96
+ const oldTime = new Date(Date.now() - 60 * 24 * 60 * 60 * 1000);
97
+ const { utimesSync } = await import("node:fs");
98
+ utimesSync(fakePath, oldTime, oldTime);
99
+ }
100
+ // Init logger triggers cleanup
101
+ const { initSessionLogger, flushSessionLogger } = await import("../utils/session-logger.js");
102
+ initSessionLogger({ cwd: tempDir });
103
+ flushSessionLogger();
104
+ await new Promise((resolve) => setTimeout(resolve, 100));
105
+ // Old files should be cleaned up (>30 days)
106
+ const remaining = readdirSync(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log"));
107
+ // Should have at most MAX_FILES (10) + 1 new session log, but old ones (>30 days) deleted
108
+ // The 12 old files are all >30 days, so they get deleted. Only the new session log remains.
109
+ expect(remaining.length).toBeLessThanOrEqual(2); // new session + maybe 1 surviving
110
+ });
111
+ });
112
+ // ── 3. Setup Wizard Types & Config Generation ─────────────────────
113
+ describe("Setup Wizard", () => {
114
+ it("exports wizard functions", async () => {
115
+ const mod = await import("../commands/setup-wizard.js");
116
+ expect(typeof mod.enterLocalModeSetup).toBe("function");
117
+ expect(typeof mod.promptLocalOrExit).toBe("function");
118
+ });
119
+ it("generates deterministic local repo ID from cwd", async () => {
120
+ // The wizard uses createHash("sha256").update(identifier).digest("hex").slice(0,12)
121
+ // with "local-" prefix. Verify the pattern.
122
+ const { createHash } = await import("node:crypto");
123
+ const testPath = "/tmp/test-repo";
124
+ const hash = createHash("sha256")
125
+ .update(testPath)
126
+ .digest("hex")
127
+ .slice(0, 12);
128
+ const repoId = `local-${hash}`;
129
+ expect(repoId).toMatch(/^local-[0-9a-f]{12}$/);
130
+ // Same input → same output
131
+ const hash2 = createHash("sha256")
132
+ .update(testPath)
133
+ .digest("hex")
134
+ .slice(0, 12);
135
+ expect(`local-${hash2}`).toBe(repoId);
136
+ });
137
+ it("config.json written by wizard has expected shape", () => {
138
+ // Simulate what enterLocalModeSetup writes
139
+ const configDir = join(tempDir, ".unerr");
140
+ mkdirSync(configDir, { recursive: true });
141
+ const config = { repoId: "local-abc123def456" };
142
+ writeFileSync(join(configDir, "config.json"), `${JSON.stringify(config, null, 2)}\n`);
143
+ const parsed = JSON.parse(readFileSync(join(configDir, "config.json"), "utf-8"));
144
+ expect(parsed.repoId).toMatch(/^local-/);
145
+ });
146
+ it("settings.json written by wizard has expected shape", () => {
147
+ const configDir = join(tempDir, ".unerr");
148
+ mkdirSync(configDir, { recursive: true });
149
+ const settings = {
150
+ localLlm: {
151
+ provider: "ollama",
152
+ baseUrl: "http://localhost:11434",
153
+ embeddingModel: "nomic-embed-text",
154
+ chatModel: "llama3",
155
+ },
156
+ };
157
+ writeFileSync(join(configDir, "settings.json"), `${JSON.stringify(settings, null, 2)}\n`);
158
+ const parsed = JSON.parse(readFileSync(join(configDir, "settings.json"), "utf-8"));
159
+ expect(parsed.localLlm.provider).toBe("ollama");
160
+ expect(parsed.localLlm.baseUrl).toBe("http://localhost:11434");
161
+ });
162
+ });
163
+ // ── 4. Command Visibility ─────────────────────────────────────────
164
+ describe("Command Visibility", () => {
165
+ it("only chat, status, debug are visible in Commander", async () => {
166
+ // We test this by importing Commander and checking _hidden flags
167
+ // The cli.ts module calls program.parse() on import, so we test the logic directly
168
+ const { Command } = await import("commander");
169
+ // Simulate the visibility logic from cli.ts
170
+ const program = new Command();
171
+ program.command("chat").description("Chat");
172
+ program.command("status").description("Status");
173
+ program.command("debug").description("Debug");
174
+ program.command("auth").description("Auth");
175
+ program.command("push").description("Push");
176
+ program.command("pull").description("Pull");
177
+ program.command("sync").description("Sync");
178
+ program.command("init").description("Init");
179
+ const visibleCommands = new Set(["chat", "status", "debug"]);
180
+ for (const cmd of program.commands) {
181
+ if (!visibleCommands.has(cmd.name())) {
182
+ cmd._hidden = true;
183
+ }
184
+ }
185
+ // Verify visible commands
186
+ const visible = program.commands.filter((cmd) => !cmd._hidden);
187
+ const hidden = program.commands.filter((cmd) => cmd._hidden);
188
+ expect(visible.map((c) => c.name()).sort()).toEqual(["chat", "debug", "status"].sort());
189
+ expect(hidden.length).toBe(5); // auth, push, pull, sync, init
190
+ expect(hidden.every((c) => !visibleCommands.has(c.name()))).toBe(true);
191
+ });
192
+ it("hidden commands are still registered and callable", async () => {
193
+ const { Command } = await import("commander");
194
+ const program = new Command();
195
+ program.command("auth").description("Auth");
196
+ program.commands[0]._hidden = true;
197
+ // The command still exists even though hidden
198
+ const authCmd = program.commands.find((c) => c.name() === "auth");
199
+ expect(authCmd).toBeTruthy();
200
+ });
201
+ });
202
+ // ── 5. Boot State Machine Logic ───────────────────────────────────
203
+ describe("Boot State Machine", () => {
204
+ it("readLocalConfig returns null when no .unerr/config.json exists", async () => {
205
+ // Test the logic from cli.ts — config detection
206
+ const configPath = join(tempDir, ".unerr", "config.json");
207
+ expect(existsSync(configPath)).toBe(false);
208
+ });
209
+ it("readLocalConfig returns parsed config when .unerr/config.json exists", () => {
210
+ const configDir = join(tempDir, ".unerr");
211
+ mkdirSync(configDir, { recursive: true });
212
+ const config = { repoId: "local-abc123" };
213
+ writeFileSync(join(configDir, "config.json"), JSON.stringify(config));
214
+ // Replicate readLocalConfig logic
215
+ const configPath = join(tempDir, ".unerr", "config.json");
216
+ expect(existsSync(configPath)).toBe(true);
217
+ const parsed = JSON.parse(readFileSync(configPath, "utf-8"));
218
+ expect(parsed.repoId).toBe("local-abc123");
219
+ });
220
+ });
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Tests for Sprint L9 — Offline Command Replacements.
3
+ *
4
+ * L9.1: unerr branches local implementation
5
+ * L9.2: unerr timeline local implementation
6
+ * L9.3: RewindReconciler explicit Local Mode guard
7
+ * L9.4: sync_local_diff drift overlay writes (tested via QueryRouter integration)
8
+ */
9
+ import { mkdtempSync, writeFileSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { describe, expect, it } from "vitest";
13
+ // ── L9.2: Timeline Tests ────────────────────────────────────────
14
+ // Note: We mirror formatRelativeTime and readShadowLedger here to avoid
15
+ // importing timeline.ts which transitively imports auth.ts (esbuild issue).
16
+ import { existsSync, readFileSync } from "node:fs";
17
+ function formatRelativeTime(isoTs) {
18
+ const diff = Date.now() - new Date(isoTs).getTime();
19
+ const seconds = Math.floor(diff / 1000);
20
+ if (seconds < 60)
21
+ return `${seconds}s ago`;
22
+ const minutes = Math.floor(seconds / 60);
23
+ if (minutes < 60)
24
+ return `${minutes} min ago`;
25
+ const hours = Math.floor(minutes / 60);
26
+ if (hours < 24)
27
+ return `${hours}h ago`;
28
+ const days = Math.floor(hours / 24);
29
+ return `${days}d ago`;
30
+ }
31
+ function readShadowLedger(ledgerPath) {
32
+ if (!existsSync(ledgerPath))
33
+ return [];
34
+ try {
35
+ const raw = readFileSync(ledgerPath, "utf-8");
36
+ const entries = [];
37
+ for (const line of raw.split("\n")) {
38
+ const trimmed = line.trim();
39
+ if (!trimmed)
40
+ continue;
41
+ try {
42
+ entries.push(JSON.parse(trimmed));
43
+ }
44
+ catch {
45
+ // Skip malformed lines
46
+ }
47
+ }
48
+ return entries;
49
+ }
50
+ catch {
51
+ return [];
52
+ }
53
+ }
54
+ describe("formatRelativeTime (L9.2)", () => {
55
+ it("formats seconds ago", () => {
56
+ const ts = new Date(Date.now() - 30_000).toISOString();
57
+ expect(formatRelativeTime(ts)).toBe("30s ago");
58
+ });
59
+ it("formats minutes ago", () => {
60
+ const ts = new Date(Date.now() - 5 * 60_000).toISOString();
61
+ expect(formatRelativeTime(ts)).toBe("5 min ago");
62
+ });
63
+ it("formats hours ago", () => {
64
+ const ts = new Date(Date.now() - 3 * 3600_000).toISOString();
65
+ expect(formatRelativeTime(ts)).toBe("3h ago");
66
+ });
67
+ it("formats days ago", () => {
68
+ const ts = new Date(Date.now() - 2 * 86400_000).toISOString();
69
+ expect(formatRelativeTime(ts)).toBe("2d ago");
70
+ });
71
+ });
72
+ describe("readShadowLedger (L9.2)", () => {
73
+ it("reads JSONL entries from ledger file", () => {
74
+ const dir = mkdtempSync(join(tmpdir(), "unerr-l9-"));
75
+ const ledgerPath = join(dir, "shadow.jsonl");
76
+ const entries = [
77
+ {
78
+ id: "abc123",
79
+ ts: "2025-01-01T10:00:00Z",
80
+ tool: "get_function",
81
+ args_summary: { key: "processPayment" },
82
+ result_summary: { found: true },
83
+ branch: "main",
84
+ head_sha: "deadbeef",
85
+ session_id: "sess1",
86
+ correlation_id: null,
87
+ },
88
+ {
89
+ id: "def456",
90
+ ts: "2025-01-01T10:05:00Z",
91
+ tool: "check_rules",
92
+ args_summary: { file_path: "src/payment.ts" },
93
+ result_summary: { violations: 0 },
94
+ branch: "main",
95
+ head_sha: "deadbeef",
96
+ session_id: "sess1",
97
+ correlation_id: "abc123",
98
+ },
99
+ ];
100
+ writeFileSync(ledgerPath, entries.map((e) => JSON.stringify(e)).join("\n"));
101
+ const result = readShadowLedger(ledgerPath);
102
+ expect(result).toHaveLength(2);
103
+ expect(result[0]?.tool).toBe("get_function");
104
+ expect(result[1]?.tool).toBe("check_rules");
105
+ expect(result[0]?.branch).toBe("main");
106
+ });
107
+ it("returns empty array for missing file", () => {
108
+ const result = readShadowLedger("/nonexistent/path/shadow.jsonl");
109
+ expect(result).toHaveLength(0);
110
+ });
111
+ it("skips malformed lines", () => {
112
+ const dir = mkdtempSync(join(tmpdir(), "unerr-l9-"));
113
+ const ledgerPath = join(dir, "shadow.jsonl");
114
+ writeFileSync(ledgerPath, [
115
+ JSON.stringify({
116
+ id: "abc",
117
+ ts: "2025-01-01T10:00:00Z",
118
+ tool: "get_function",
119
+ args_summary: {},
120
+ result_summary: {},
121
+ branch: "main",
122
+ head_sha: "abc",
123
+ session_id: "s1",
124
+ correlation_id: null,
125
+ }),
126
+ "not json",
127
+ JSON.stringify({
128
+ id: "def",
129
+ ts: "2025-01-01T10:01:00Z",
130
+ tool: "get_class",
131
+ args_summary: {},
132
+ result_summary: {},
133
+ branch: "main",
134
+ head_sha: "def",
135
+ session_id: "s1",
136
+ correlation_id: null,
137
+ }),
138
+ ].join("\n"));
139
+ const result = readShadowLedger(ledgerPath);
140
+ expect(result).toHaveLength(2);
141
+ expect(result[0]?.id).toBe("abc");
142
+ expect(result[1]?.id).toBe("def");
143
+ });
144
+ it("supports branch filter pattern", () => {
145
+ const dir = mkdtempSync(join(tmpdir(), "unerr-l9-"));
146
+ const ledgerPath = join(dir, "shadow.jsonl");
147
+ const entries = [
148
+ {
149
+ id: "a1",
150
+ ts: "2025-01-01T10:00:00Z",
151
+ tool: "get_function",
152
+ args_summary: {},
153
+ result_summary: {},
154
+ branch: "main",
155
+ head_sha: "a",
156
+ session_id: "s1",
157
+ correlation_id: null,
158
+ },
159
+ {
160
+ id: "b1",
161
+ ts: "2025-01-01T10:01:00Z",
162
+ tool: "get_class",
163
+ args_summary: {},
164
+ result_summary: {},
165
+ branch: "feature/auth",
166
+ head_sha: "b",
167
+ session_id: "s1",
168
+ correlation_id: null,
169
+ },
170
+ {
171
+ id: "c1",
172
+ ts: "2025-01-01T10:02:00Z",
173
+ tool: "check_rules",
174
+ args_summary: {},
175
+ result_summary: {},
176
+ branch: "main",
177
+ head_sha: "c",
178
+ session_id: "s1",
179
+ correlation_id: null,
180
+ },
181
+ ];
182
+ writeFileSync(ledgerPath, entries.map((e) => JSON.stringify(e)).join("\n"));
183
+ const all = readShadowLedger(ledgerPath);
184
+ const mainOnly = all.filter((e) => e.branch === "main");
185
+ expect(mainOnly).toHaveLength(2);
186
+ expect(mainOnly[0]?.id).toBe("a1");
187
+ expect(mainOnly[1]?.id).toBe("c1");
188
+ });
189
+ });
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Sprint Q: Persistent Context & Causal Intelligence tests.
3
+ *
4
+ * Tests the complete persistent intelligence loop:
5
+ * Q.1: Causal bridge, Q.2: Auto-snapshots, Q.3: Convention learning,
6
+ * Q.4: Session resume, Q.5: Timeline forks, Q.6: Prompt durability,
7
+ * Q.9: Session health + exploration cost
8
+ */
9
+ import { describe, expect, it } from "vitest";
10
+ describe("Q.1: Causal Bridge Query Engine", () => {
11
+ it("assembles causal chain from entity interactions", async () => {
12
+ const { assembleCausalChain } = await import("../tracking/causal-bridge.js");
13
+ const entries = [
14
+ {
15
+ id: "e1",
16
+ ts: "2026-05-01T10:00:00Z",
17
+ tool: "sync_local_diff",
18
+ args_summary: {
19
+ files: ["src/auth.ts"],
20
+ prompt: "Add login validation",
21
+ },
22
+ result_summary: {},
23
+ session_id: "s1",
24
+ head_sha: "abc",
25
+ },
26
+ {
27
+ id: "e2",
28
+ ts: "2026-05-01T10:05:00Z",
29
+ tool: "sync_local_diff",
30
+ args_summary: {
31
+ files: ["src/auth.ts"],
32
+ prompt: "Fix validation edge case",
33
+ },
34
+ result_summary: { commit_sha: "def" },
35
+ session_id: "s1",
36
+ head_sha: "def",
37
+ },
38
+ ];
39
+ const chain = assembleCausalChain("src/auth.ts", entries);
40
+ expect(chain.entityKey).toBe("src/auth.ts");
41
+ expect(chain.interactions.length).toBeGreaterThanOrEqual(1);
42
+ });
43
+ it("computes durability from survival data", async () => {
44
+ const { computeDurability } = await import("../tracking/causal-bridge.js");
45
+ const interactions = [
46
+ { survived: true, survivalMs: 86400000 },
47
+ { survived: true, survivalMs: 86400000 },
48
+ { survived: false, survivalMs: 3600000 },
49
+ ];
50
+ const durability = computeDurability(interactions);
51
+ expect(durability).toBeGreaterThan(0.5);
52
+ expect(durability).toBeLessThanOrEqual(1.0);
53
+ });
54
+ });
55
+ describe("Q.2: Auto-Snapshot Triggers", () => {
56
+ it("detects test pass commands", async () => {
57
+ const { isTestCommand } = await import("../tracking/auto-snapshot-triggers.js");
58
+ expect(isTestCommand("pnpm test:run")).toBe(true);
59
+ expect(isTestCommand("vitest run")).toBe(true);
60
+ expect(isTestCommand("jest --coverage")).toBe(true);
61
+ expect(isTestCommand("pytest -v")).toBe(true);
62
+ expect(isTestCommand("git status")).toBe(false);
63
+ expect(isTestCommand("ls -la")).toBe(false);
64
+ });
65
+ it("triggers snapshot on test pass", async () => {
66
+ const { shouldAutoSnapshot } = await import("../tracking/auto-snapshot-triggers.js");
67
+ const trigger = shouldAutoSnapshot("bash", { command: "pnpm test:run" }, { exitCode: 0 });
68
+ expect(trigger).not.toBeNull();
69
+ expect(trigger?.type).toBe("test_pass");
70
+ });
71
+ it("does not trigger on test failure", async () => {
72
+ const { shouldAutoSnapshot } = await import("../tracking/auto-snapshot-triggers.js");
73
+ const trigger = shouldAutoSnapshot("bash", { command: "pnpm test:run" }, { exitCode: 1 });
74
+ expect(trigger).toBeNull();
75
+ });
76
+ });
77
+ describe("Q.5: Timeline Branching on Rewind", () => {
78
+ it("creates timeline fork with abandoned + new branches", async () => {
79
+ const { createTimelineFork } = await import("../tracking/timeline-fork.js");
80
+ const fork = createTimelineFork("snapshot-123", ["src/auth.ts::login", "src/auth.ts::validate"], ["Add optional param to login", "Fix validate edge case"], "Broke 3 downstream callers");
81
+ expect(fork.forkPoint).toBe("snapshot-123");
82
+ expect(fork.abandonedBranch.entityChanges).toHaveLength(2);
83
+ expect(fork.abandonedBranch.promptsTried).toHaveLength(2);
84
+ expect(fork.abandonedBranch.failureReason).toContain("Broke");
85
+ expect(fork.newBranch.timelineId).toBeGreaterThan(fork.abandonedBranch.timelineId);
86
+ });
87
+ });
88
+ describe("Q.6: Prompt Durability Ranking", () => {
89
+ it("extracts action types from prompts", async () => {
90
+ const { extractActionType } = await import("../tracking/prompt-durability.js");
91
+ expect(extractActionType("Add a new helper function")).toBe("add");
92
+ expect(extractActionType("Fix the authentication bug")).toBe("fix");
93
+ expect(extractActionType("Refactor the payment module")).toBe("refactor");
94
+ expect(extractActionType("Modify the user service")).toBe("modify");
95
+ expect(extractActionType("Delete unused imports")).toBe("delete");
96
+ expect(extractActionType("Do something complex")).toBe("other");
97
+ });
98
+ it("computes durability profiles", async () => {
99
+ const { computePromptDurabilityProfiles } = await import("../tracking/prompt-durability.js");
100
+ const entries = [
101
+ {
102
+ prompt: "Add helper function",
103
+ files: ["src/utils.ts"],
104
+ survived: true,
105
+ riskLevel: "low",
106
+ },
107
+ {
108
+ prompt: "Add another helper",
109
+ files: ["src/utils.ts"],
110
+ survived: true,
111
+ riskLevel: "low",
112
+ },
113
+ {
114
+ prompt: "Modify critical service",
115
+ files: ["src/core.ts"],
116
+ survived: false,
117
+ riskLevel: "critical",
118
+ },
119
+ ];
120
+ const profiles = computePromptDurabilityProfiles(entries);
121
+ expect(profiles.length).toBeGreaterThanOrEqual(1);
122
+ const addProfile = profiles.find((p) => p.actionType === "add");
123
+ if (addProfile) {
124
+ expect(addProfile.durability).toBe(1.0);
125
+ }
126
+ });
127
+ });
128
+ describe("Q.9: Session Health Monitor", () => {
129
+ it("starts healthy", async () => {
130
+ const { createSessionHealthMonitor } = await import("../intelligence/session-health-monitor.js");
131
+ const monitor = createSessionHealthMonitor();
132
+ const health = monitor.getHealth();
133
+ expect(health.health).toBe(1.0);
134
+ expect(health.recommendation).toBe("continue");
135
+ expect(health.signals).toHaveLength(0);
136
+ });
137
+ it("detects repeated queries as degradation", async () => {
138
+ const { createSessionHealthMonitor } = await import("../intelligence/session-health-monitor.js");
139
+ const monitor = createSessionHealthMonitor();
140
+ for (let i = 0; i < 5; i++) {
141
+ monitor.recordToolCall("get_function", "entity-x");
142
+ }
143
+ const health = monitor.getHealth();
144
+ expect(health.health).toBeLessThan(1.0);
145
+ const repeated = health.signals.find((s) => s.type === "repeated_query");
146
+ expect(repeated).toBeDefined();
147
+ });
148
+ it("detects convention violation spikes", async () => {
149
+ const { createSessionHealthMonitor } = await import("../intelligence/session-health-monitor.js");
150
+ const monitor = createSessionHealthMonitor();
151
+ for (let i = 0; i < 6; i++) {
152
+ monitor.recordConventionViolation();
153
+ }
154
+ const health = monitor.getHealth();
155
+ const spike = health.signals.find((s) => s.type === "convention_violation_spike");
156
+ expect(spike).toBeDefined();
157
+ });
158
+ });
159
+ describe("Q.9: Exploration Cost Estimator", () => {
160
+ it("estimates counterfactual for blast_radius query", async () => {
161
+ const { estimateExplorationCost } = await import("../intelligence/exploration-cost.js");
162
+ const estimate = estimateExplorationCost("blast_radius", 500, 10);
163
+ expect(estimate.tokensWithout).toBeGreaterThan(estimate.tokensUsed);
164
+ expect(estimate.counterfactualMethod).toBeTruthy();
165
+ expect(estimate.explanation).toBeTruthy();
166
+ });
167
+ it("accumulates session savings", async () => {
168
+ const { createExplorationAccumulator, estimateExplorationCost } = await import("../intelligence/exploration-cost.js");
169
+ const acc = createExplorationAccumulator();
170
+ acc.record(estimateExplorationCost("blast_radius", 500, 10));
171
+ acc.record(estimateExplorationCost("find_callers", 200, 5));
172
+ const total = acc.getTotal();
173
+ expect(total.saved).toBeGreaterThan(0);
174
+ expect(total.ratio).toBeGreaterThan(0);
175
+ });
176
+ });
177
+ describe("Q: Intent Token Tracker", () => {
178
+ it("tracks tokens per intent group", async () => {
179
+ const { createIntentTokenTracker } = await import("../tracking/intent-token-tracker.js");
180
+ const tracker = createIntentTokenTracker();
181
+ tracker.recordToolCall("intent-1", 500, 200, "entity-a");
182
+ tracker.recordToolCall("intent-1", 300, 100, "entity-b");
183
+ const group = tracker.getGroup("intent-1");
184
+ expect(group).not.toBeNull();
185
+ expect(group?.toolCalls).toBe(2);
186
+ expect(group?.tokensConsumed).toBe(800);
187
+ expect(group?.tokensSaved).toBe(300);
188
+ expect(group?.entitiesModified).toContain("entity-a");
189
+ });
190
+ it("marks outcomes", async () => {
191
+ const { createIntentTokenTracker } = await import("../tracking/intent-token-tracker.js");
192
+ const tracker = createIntentTokenTracker();
193
+ tracker.recordToolCall("intent-2", 100, 50);
194
+ tracker.markOutcome("intent-2", "completed");
195
+ const group = tracker.getGroup("intent-2");
196
+ expect(group?.outcome).toBe("completed");
197
+ });
198
+ });