@unerr-ai/unerr 0.1.0 → 0.1.2

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 +996 -710
  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 +312 -0
  553. package/dist/ui/assets/index-B-0HTtUR.js +0 -10
@@ -0,0 +1,199 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { detectLanguage, entityKey, extractEntities, } from "../intelligence/ast-extractor.js";
6
+ describe("Local Parse — TypeScript AST Extraction (P5.6-ADV-01)", () => {
7
+ let tmpDir;
8
+ beforeEach(() => {
9
+ tmpDir = path.join(os.tmpdir(), `unerr-local-parse-${Date.now()}`);
10
+ fs.mkdirSync(tmpDir, { recursive: true });
11
+ });
12
+ afterEach(() => {
13
+ fs.rmSync(tmpDir, { recursive: true, force: true });
14
+ });
15
+ it("extracts entities from TypeScript files matching expected output format", () => {
16
+ const content = [
17
+ "export function main(): void {",
18
+ " console.log('hello')",
19
+ "}",
20
+ "",
21
+ "export interface Config {",
22
+ " port: number",
23
+ " host: string",
24
+ "}",
25
+ "",
26
+ "export class Server {",
27
+ " start() {",
28
+ " return true",
29
+ " }",
30
+ "}",
31
+ ].join("\n");
32
+ const entities = extractEntities(content, "src/index.ts");
33
+ expect(entities.length).toBeGreaterThanOrEqual(3);
34
+ const funcEntity = entities.find((e) => e.name === "main");
35
+ expect(funcEntity).toBeDefined();
36
+ expect(funcEntity?.kind).toBe("function");
37
+ expect(funcEntity?.line_start).toBe(1);
38
+ const ifaceEntity = entities.find((e) => e.name === "Config");
39
+ expect(ifaceEntity).toBeDefined();
40
+ expect(ifaceEntity?.kind).toBe("interface");
41
+ const classEntity = entities.find((e) => e.name === "Server");
42
+ expect(classEntity).toBeDefined();
43
+ expect(classEntity?.kind).toBe("class");
44
+ });
45
+ it("entity keys match entityHash algorithm", () => {
46
+ // entityKey(repoId, filePath, kind, name, signature?)
47
+ // SHA-256 of null-byte-separated string, first 16 hex chars
48
+ const key1 = entityKey("repo-1", "src/auth.ts", "function", "login", "(user: string)");
49
+ const key2 = entityKey("repo-1", "src/auth.ts", "function", "login", "(user: string)");
50
+ // Deterministic
51
+ expect(key1).toBe(key2);
52
+ // 16 hex chars
53
+ expect(key1).toMatch(/^[0-9a-f]{16}$/);
54
+ // Different inputs → different keys
55
+ const key3 = entityKey("repo-1", "src/auth.ts", "function", "logout", "()");
56
+ expect(key3).not.toBe(key1);
57
+ });
58
+ it("extracts entities from Python files", () => {
59
+ const content = [
60
+ "class AuthService:",
61
+ " def __init__(self):",
62
+ " self.token = None",
63
+ "",
64
+ " async def login(self, user: str) -> bool:",
65
+ " return True",
66
+ "",
67
+ "def main():",
68
+ " svc = AuthService()",
69
+ ].join("\n");
70
+ const entities = extractEntities(content, "src/auth.py");
71
+ const cls = entities.find((e) => e.name === "AuthService");
72
+ expect(cls).toBeDefined();
73
+ expect(cls?.kind).toBe("class");
74
+ const initFn = entities.find((e) => e.name === "__init__");
75
+ expect(initFn).toBeDefined();
76
+ expect(initFn?.kind).toBe("function");
77
+ const mainFn = entities.find((e) => e.name === "main");
78
+ expect(mainFn).toBeDefined();
79
+ });
80
+ it("extracts entities from Go files", () => {
81
+ const content = [
82
+ "package main",
83
+ "",
84
+ "type Server struct {",
85
+ " port int",
86
+ "}",
87
+ "",
88
+ "func (s *Server) Start() error {",
89
+ " return nil",
90
+ "}",
91
+ "",
92
+ "func main() {",
93
+ " s := &Server{port: 8080}",
94
+ " s.Start()",
95
+ "}",
96
+ ].join("\n");
97
+ const entities = extractEntities(content, "main.go");
98
+ const structEntity = entities.find((e) => e.name === "Server");
99
+ expect(structEntity).toBeDefined();
100
+ expect(structEntity?.kind).toBe("class");
101
+ const methodEntity = entities.find((e) => e.name === "Server.Start");
102
+ expect(methodEntity).toBeDefined();
103
+ expect(methodEntity?.kind).toBe("method");
104
+ expect(methodEntity?.parent_class).toBe("Server");
105
+ const mainFn = entities.find((e) => e.name === "main");
106
+ expect(mainFn).toBeDefined();
107
+ });
108
+ it("detects language from file extension", () => {
109
+ expect(detectLanguage("src/index.ts")).toBe("typescript");
110
+ expect(detectLanguage("src/app.tsx")).toBe("typescript");
111
+ expect(detectLanguage("src/main.py")).toBe("python");
112
+ expect(detectLanguage("main.go")).toBe("go");
113
+ expect(detectLanguage("Main.java")).toBe("java");
114
+ expect(detectLanguage("lib.rs")).toBe("rust");
115
+ expect(detectLanguage("main.c")).toBe("c");
116
+ expect(detectLanguage("main.cpp")).toBe("cpp");
117
+ expect(detectLanguage("README.md")).toBeNull();
118
+ expect(detectLanguage("Makefile")).toBeNull();
119
+ });
120
+ it("returns empty array for unsupported file types", () => {
121
+ const entities = extractEntities("# Heading\nSome markdown", "README.md");
122
+ expect(entities).toEqual([]);
123
+ });
124
+ it("each entity includes content_hash for dedup", () => {
125
+ const content = "export function hello(): string {\n return 'world'\n}\n";
126
+ const entities = extractEntities(content, "src/hello.ts");
127
+ expect(entities.length).toBe(1);
128
+ expect(entities[0]?.content_hash).toMatch(/^[0-9a-f]{16}$/);
129
+ // Same content → same hash
130
+ const entities2 = extractEntities(content, "src/hello.ts");
131
+ expect(entities2[0]?.content_hash).toBe(entities[0]?.content_hash);
132
+ // Different content → different hash
133
+ const modified = "export function hello(): string {\n return 'changed'\n}\n";
134
+ const entities3 = extractEntities(modified, "src/hello.ts");
135
+ expect(entities3[0]?.content_hash).not.toBe(entities[0]?.content_hash);
136
+ });
137
+ it("handles large files without hanging", () => {
138
+ // Generate 10K lines
139
+ const lines = [];
140
+ for (let i = 0; i < 10000; i++) {
141
+ if (i % 100 === 0) {
142
+ lines.push(`export function func_${i}(): void {`);
143
+ lines.push(` console.log(${i})`);
144
+ lines.push("}");
145
+ }
146
+ else {
147
+ lines.push(`// line ${i}`);
148
+ }
149
+ }
150
+ const content = lines.join("\n");
151
+ const start = Date.now();
152
+ const entities = extractEntities(content, "src/big.ts");
153
+ const elapsed = Date.now() - start;
154
+ expect(entities.length).toBe(100); // 10000/100 functions
155
+ expect(elapsed).toBeLessThan(5000); // Should complete in <5s
156
+ });
157
+ it("builds graph-upload payload from extracted entities", () => {
158
+ // Simulate what push.ts --local-parse does
159
+ const repoId = "repo-123";
160
+ const filePath = "src/auth.ts";
161
+ const content = [
162
+ "export class AuthService {",
163
+ " login(user: string): boolean {",
164
+ " return true",
165
+ " }",
166
+ "}",
167
+ ].join("\n");
168
+ const extracted = extractEntities(content, filePath);
169
+ // Build EntityDoc-compatible objects
170
+ const entities = extracted.map((e) => ({
171
+ id: entityKey(repoId, filePath, e.kind, e.name, e.signature),
172
+ kind: e.kind,
173
+ name: e.name,
174
+ file_path: filePath,
175
+ start_line: e.line_start,
176
+ end_line: e.line_end,
177
+ signature: e.signature || undefined,
178
+ }));
179
+ // Validate shapes match graph-upload requirements
180
+ for (const entity of entities) {
181
+ expect(entity.id).toMatch(/^[0-9a-f]{16}$/);
182
+ expect(entity.kind).toBeTruthy();
183
+ expect(entity.name).toBeTruthy();
184
+ expect(entity.file_path).toBe(filePath);
185
+ }
186
+ // Build edges
187
+ const fileId = entityKey(repoId, filePath, "file", filePath);
188
+ const edges = entities.map((e) => ({
189
+ _from: `files/${fileId}`,
190
+ _to: `${e.kind === "class" ? "classes" : "functions"}/${e.id}`,
191
+ kind: "contains",
192
+ }));
193
+ expect(edges.length).toBe(entities.length);
194
+ for (const edge of edges) {
195
+ expect(edge._from).toContain("files/");
196
+ expect(edge.kind).toBe("contains");
197
+ }
198
+ });
199
+ });
@@ -0,0 +1,208 @@
1
+ import { appendFileSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; // appendFileSync + writeFileSync still used for unerr.jsonl JSONL tests
2
+ import os from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { closeMetricsStore, openMetricsStore, } from "../tracking/metrics-store.js";
6
+ import { startupLog } from "../utils/startup-log.js";
7
+ // Mock startupLog to capture output
8
+ vi.mock("../utils/startup-log.js", () => {
9
+ const calls = [];
10
+ return {
11
+ startupLog: {
12
+ step: (...args) => calls.push({ method: "step", args }),
13
+ done: (...args) => calls.push({ method: "done", args }),
14
+ warn: (...args) => calls.push({ method: "warn", args }),
15
+ error: (...args) => calls.push({ method: "error", args }),
16
+ tokenFlow: (...args) => calls.push({ method: "tokenFlow", args }),
17
+ fmt: {
18
+ muted: (s) => s,
19
+ dim: (s) => s,
20
+ cyan: (s) => s,
21
+ bold: (s) => s,
22
+ },
23
+ _calls: calls,
24
+ _reset: () => {
25
+ calls.length = 0;
26
+ },
27
+ },
28
+ };
29
+ });
30
+ function getCalls() {
31
+ return startupLog._calls;
32
+ }
33
+ function resetCalls() {
34
+ startupLog._reset();
35
+ }
36
+ describe("log-tailer", () => {
37
+ let tmpDir;
38
+ let unerrDir;
39
+ let logsDir;
40
+ beforeEach(() => {
41
+ tmpDir = join(os.tmpdir(), `unerr-tailer-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
42
+ unerrDir = join(tmpDir, ".unerr");
43
+ logsDir = join(unerrDir, "logs");
44
+ mkdirSync(logsDir, { recursive: true });
45
+ resetCalls();
46
+ });
47
+ afterEach(() => {
48
+ closeMetricsStore(unerrDir);
49
+ rmSync(tmpDir, { recursive: true, force: true });
50
+ });
51
+ it("startLogTailer returns a handle with close()", async () => {
52
+ const { startLogTailer } = await import("../proxy/log-tailer.js");
53
+ const handle = startLogTailer(tmpDir);
54
+ expect(handle).toBeDefined();
55
+ expect(typeof handle.close).toBe("function");
56
+ handle.close();
57
+ });
58
+ it("polls compression rows from metrics.db", async () => {
59
+ const { startLogTailer } = await import("../proxy/log-tailer.js");
60
+ const handle = startLogTailer(tmpDir, { pollIntervalMs: 50 });
61
+ // Insert after the tailer has captured its initial lastIds.
62
+ openMetricsStore(unerrDir).insertCompression({
63
+ ts: Date.now(),
64
+ ts_iso: new Date().toISOString(),
65
+ command: "ps aux",
66
+ category: "tabular",
67
+ confidence: 0.85,
68
+ raw_bytes: 1000,
69
+ compressed_bytes: 300,
70
+ saved_pct: 70,
71
+ omni_fallback: 0,
72
+ tee_file: null,
73
+ });
74
+ await new Promise((resolve) => setTimeout(resolve, 250));
75
+ const calls = getCalls();
76
+ const compressionCall = calls.find((c) => c.method === "step" && String(c.args[0]).includes("ps aux"));
77
+ expect(compressionCall).toBeDefined();
78
+ handle.close();
79
+ });
80
+ it("polls token-flow rows and filters own-PID", async () => {
81
+ const { startLogTailer } = await import("../proxy/log-tailer.js");
82
+ const handle = startLogTailer(tmpDir, { pollIntervalMs: 50 });
83
+ const store = openMetricsStore(unerrDir);
84
+ const ts = Date.now();
85
+ // Own PID — should be filtered out by printTokenFlowEntry
86
+ store.insertTokenFlow({
87
+ ts,
88
+ ts_iso: new Date(ts).toISOString(),
89
+ session_id: "s1",
90
+ pid: process.pid,
91
+ turn: 1,
92
+ mechanism: "graph_query",
93
+ tool: "get_callers",
94
+ tokens_without: 150,
95
+ tokens_with: 50,
96
+ tokens_saved: 100,
97
+ detail: null,
98
+ });
99
+ // Other PID — should be relayed
100
+ store.insertTokenFlow({
101
+ ts: ts + 1,
102
+ ts_iso: new Date(ts + 1).toISOString(),
103
+ session_id: "s1",
104
+ pid: process.pid + 999,
105
+ turn: 3,
106
+ mechanism: "shell_compression",
107
+ tool: "bash",
108
+ tokens_without: 500,
109
+ tokens_with: 150,
110
+ tokens_saved: 350,
111
+ detail: null,
112
+ });
113
+ await new Promise((resolve) => setTimeout(resolve, 250));
114
+ const calls = getCalls();
115
+ const tfCalls = calls.filter((c) => c.method === "tokenFlow");
116
+ expect(tfCalls.length).toBe(1);
117
+ const tfCall = tfCalls[0].args[0];
118
+ expect(tfCall.mechanism).toBe("shell_compression");
119
+ expect(tfCall.tokensSaved).toBe(350);
120
+ handle.close();
121
+ });
122
+ it("filters own-PID entries from unerr.jsonl", async () => {
123
+ const generalPath = join(logsDir, "unerr.jsonl");
124
+ writeFileSync(generalPath, "");
125
+ const { startLogTailer } = await import("../proxy/log-tailer.js");
126
+ const handle = startLogTailer(tmpDir);
127
+ const ownEntry = JSON.stringify({
128
+ pid: process.pid,
129
+ level: "warn",
130
+ msg: "own-warning",
131
+ });
132
+ const otherEntry = JSON.stringify({
133
+ pid: process.pid + 1,
134
+ level: "warn",
135
+ msg: "other-warning",
136
+ });
137
+ appendFileSync(generalPath, `${ownEntry}\n${otherEntry}\n`);
138
+ await new Promise((resolve) => setTimeout(resolve, 3500));
139
+ const calls = getCalls();
140
+ const warnCalls = calls.filter((c) => c.method === "warn");
141
+ expect(warnCalls.find((c) => String(c.args[0]).includes("own-warning"))).toBeUndefined();
142
+ expect(warnCalls.find((c) => String(c.args[0]).includes("other-warning"))).toBeDefined();
143
+ handle.close();
144
+ });
145
+ it("polls file-read rows for events with savings", async () => {
146
+ const { startLogTailer } = await import("../proxy/log-tailer.js");
147
+ const handle = startLogTailer(tmpDir, { pollIntervalMs: 50 });
148
+ openMetricsStore(unerrDir).insertFileRead({
149
+ ts: Date.now(),
150
+ ts_iso: new Date().toISOString(),
151
+ file: "src/proxy/proxy.ts",
152
+ mode: "entity",
153
+ total_lines: 2000,
154
+ returned_lines: 45,
155
+ saved_pct: 98,
156
+ entity: null,
157
+ token_estimate: null,
158
+ });
159
+ await new Promise((resolve) => setTimeout(resolve, 250));
160
+ const calls = getCalls();
161
+ const fileReadCall = calls.find((c) => c.method === "step" &&
162
+ String(c.args[0]).includes("proxy.ts") &&
163
+ String(c.args[0]).includes("entity"));
164
+ expect(fileReadCall).toBeDefined();
165
+ handle.close();
166
+ });
167
+ it("skips file-read entries with 0% savings", async () => {
168
+ const { startLogTailer } = await import("../proxy/log-tailer.js");
169
+ const handle = startLogTailer(tmpDir, { pollIntervalMs: 50 });
170
+ openMetricsStore(unerrDir).insertFileRead({
171
+ ts: Date.now(),
172
+ ts_iso: new Date().toISOString(),
173
+ file: "readme.md",
174
+ mode: "full",
175
+ total_lines: 20,
176
+ returned_lines: 20,
177
+ saved_pct: 0,
178
+ entity: null,
179
+ token_estimate: null,
180
+ });
181
+ await new Promise((resolve) => setTimeout(resolve, 250));
182
+ const calls = getCalls();
183
+ const fileReadCall = calls.find((c) => c.method === "step" && String(c.args[0]).includes("readme.md"));
184
+ expect(fileReadCall).toBeUndefined();
185
+ handle.close();
186
+ });
187
+ it("skips compression entries with 0% savings", async () => {
188
+ const { startLogTailer } = await import("../proxy/log-tailer.js");
189
+ const handle = startLogTailer(tmpDir, { pollIntervalMs: 50 });
190
+ openMetricsStore(unerrDir).insertCompression({
191
+ ts: Date.now(),
192
+ ts_iso: new Date().toISOString(),
193
+ command: "echo hello",
194
+ category: "omni",
195
+ confidence: 1,
196
+ raw_bytes: 12,
197
+ compressed_bytes: 12,
198
+ saved_pct: 0,
199
+ omni_fallback: 0,
200
+ tee_file: null,
201
+ });
202
+ await new Promise((resolve) => setTimeout(resolve, 250));
203
+ const calls = getCalls();
204
+ const echoCall = calls.find((c) => c.method === "step" && String(c.args[0]).includes("echo hello"));
205
+ expect(echoCall).toBeUndefined();
206
+ handle.close();
207
+ });
208
+ });
@@ -0,0 +1,276 @@
1
+ /**
2
+ * BA-1.6: Loop Circuit Breaker tests.
3
+ *
4
+ * Verifies:
5
+ * - 3 stuck patterns detected (repetitive_failure, context_poisoning, over_planning)
6
+ * - Circuit breaker state transitions (closed → open → half_open → closed)
7
+ * - Dollar calculation correctness
8
+ * - TDD exemption (test files excluded from entity-retry count)
9
+ * - $0.50 gate enforcement
10
+ */
11
+ import { describe, expect, it } from "vitest";
12
+ import { LoopCircuitBreaker } from "../behaviors/loop-breaker.js";
13
+ function makeCtx(overrides = {}) {
14
+ return {
15
+ toolName: "edit_file",
16
+ args: { path: "src/payment.ts", content: "fix" },
17
+ sessionId: "test-session",
18
+ entityKey: "src/payment.ts::processPayment",
19
+ filePath: "src/payment.ts",
20
+ ...overrides,
21
+ };
22
+ }
23
+ function makeErrorResult() {
24
+ return {
25
+ error: true,
26
+ content: [{ type: "text", text: "TypeError: Cannot read property" }],
27
+ };
28
+ }
29
+ function makeSuccessResult() {
30
+ return {
31
+ content: [{ type: "text", text: "File updated successfully" }],
32
+ };
33
+ }
34
+ describe("Loop Circuit Breaker (BA-1.1)", () => {
35
+ describe("Pattern Detection", () => {
36
+ it("detects repetitive failure after 4 consecutive errors on same entity", async () => {
37
+ const breaker = new LoopCircuitBreaker({ maxAttemptsPerEntity: 4 });
38
+ for (let i = 0; i < 3; i++) {
39
+ const ctx = makeCtx({
40
+ result: { error: true, content: `TypeError on line ${45 + i}` },
41
+ args: { path: "src/payment.ts", content: "retry logic v1" },
42
+ });
43
+ const output = await breaker.onPostToolUse(ctx);
44
+ expect(output).toBeNull();
45
+ }
46
+ const finalCtx = makeCtx({
47
+ result: { error: true, content: "TypeError on line 48" },
48
+ args: { path: "src/payment.ts", content: "retry logic v1" },
49
+ });
50
+ const output = await breaker.onPostToolUse(finalCtx);
51
+ expect(output).not.toBeNull();
52
+ expect(output?.halt).toBe(true);
53
+ expect(output?._context?.pattern).toBe("repetitive_failure");
54
+ expect(output?._context?.reason).toContain("4 consecutive failed attempts");
55
+ });
56
+ it("detects context poisoning when all results are identical", async () => {
57
+ const breaker = new LoopCircuitBreaker({ maxAttemptsPerEntity: 4 });
58
+ const identicalResult = {
59
+ error: true,
60
+ content: [{ type: "text", text: "The exact same error every time" }],
61
+ };
62
+ for (let i = 0; i < 4; i++) {
63
+ const ctx = makeCtx({
64
+ result: identicalResult,
65
+ args: {
66
+ path: "src/payment.ts",
67
+ content: `completely_different_approach_${i * 100}`,
68
+ },
69
+ });
70
+ const output = await breaker.onPostToolUse(ctx);
71
+ if (i < 3) {
72
+ expect(output).toBeNull();
73
+ }
74
+ else {
75
+ expect(output).not.toBeNull();
76
+ expect(output?._context?.pattern).toBe("context_poisoning");
77
+ }
78
+ }
79
+ });
80
+ it("detects over-planning when attempts are spaced far apart", async () => {
81
+ const breaker = new LoopCircuitBreaker({ maxAttemptsPerEntity: 4 });
82
+ const now = Date.now();
83
+ for (let i = 0; i < 4; i++) {
84
+ const ctx = makeCtx({
85
+ result: makeErrorResult(),
86
+ args: {
87
+ path: "src/payment.ts",
88
+ action: `action_${i}`,
89
+ variation: `v${i * 50}`,
90
+ },
91
+ });
92
+ const originalDateNow = Date.now;
93
+ Date.now = () => now + i * 20_000;
94
+ try {
95
+ await breaker.onPostToolUse(ctx);
96
+ }
97
+ finally {
98
+ Date.now = originalDateNow;
99
+ }
100
+ }
101
+ const stats = breaker.getSessionStats();
102
+ expect(stats.loopsPrevented).toBeGreaterThanOrEqual(1);
103
+ });
104
+ });
105
+ describe("Circuit Breaker States", () => {
106
+ it("transitions from CLOSED to OPEN on loop detection", async () => {
107
+ const breaker = new LoopCircuitBreaker({ maxAttemptsPerEntity: 4 });
108
+ expect(breaker.getCircuitState("src/payment.ts::processPayment")).toBeNull();
109
+ for (let i = 0; i < 4; i++) {
110
+ await breaker.onPostToolUse(makeCtx({ result: makeErrorResult() }));
111
+ }
112
+ expect(breaker.getCircuitState("src/payment.ts::processPayment")).toBe("open");
113
+ });
114
+ it("blocks further attempts while circuit is OPEN", async () => {
115
+ const breaker = new LoopCircuitBreaker({
116
+ maxAttemptsPerEntity: 4,
117
+ cooldownMs: 60_000,
118
+ });
119
+ for (let i = 0; i < 4; i++) {
120
+ await breaker.onPostToolUse(makeCtx({ result: makeErrorResult() }));
121
+ }
122
+ const preResult = await breaker.onPreToolUse(makeCtx());
123
+ expect(preResult).not.toBeNull();
124
+ expect(preResult?.halt).toBe(true);
125
+ expect(preResult?._context?.cooldown_remaining_s).toBeGreaterThan(0);
126
+ });
127
+ it("transitions to HALF_OPEN after cooldown expires", async () => {
128
+ const breaker = new LoopCircuitBreaker({
129
+ maxAttemptsPerEntity: 4,
130
+ cooldownMs: 100,
131
+ });
132
+ for (let i = 0; i < 4; i++) {
133
+ await breaker.onPostToolUse(makeCtx({ result: makeErrorResult() }));
134
+ }
135
+ await new Promise((r) => setTimeout(r, 150));
136
+ const preResult = await breaker.onPreToolUse(makeCtx());
137
+ expect(preResult).toBeNull();
138
+ });
139
+ it("returns to CLOSED from HALF_OPEN on success", async () => {
140
+ const breaker = new LoopCircuitBreaker({
141
+ maxAttemptsPerEntity: 4,
142
+ cooldownMs: 100,
143
+ });
144
+ for (let i = 0; i < 4; i++) {
145
+ await breaker.onPostToolUse(makeCtx({ result: makeErrorResult() }));
146
+ }
147
+ await new Promise((r) => setTimeout(r, 150));
148
+ await breaker.onPreToolUse(makeCtx());
149
+ await breaker.onPostToolUse(makeCtx({ result: makeSuccessResult() }));
150
+ expect(breaker.getCircuitState("src/payment.ts::processPayment")).toBe("closed");
151
+ });
152
+ it("returns to OPEN from HALF_OPEN on failure", async () => {
153
+ const breaker = new LoopCircuitBreaker({
154
+ maxAttemptsPerEntity: 4,
155
+ cooldownMs: 100,
156
+ });
157
+ for (let i = 0; i < 4; i++) {
158
+ await breaker.onPostToolUse(makeCtx({ result: makeErrorResult() }));
159
+ }
160
+ await new Promise((r) => setTimeout(r, 150));
161
+ await breaker.onPreToolUse(makeCtx());
162
+ await breaker.onPostToolUse(makeCtx({ result: makeErrorResult() }));
163
+ expect(breaker.getCircuitState("src/payment.ts::processPayment")).toBe("open");
164
+ });
165
+ });
166
+ describe("TDD Exemption", () => {
167
+ it("does NOT flag test file modifications as loop attempts", async () => {
168
+ const breaker = new LoopCircuitBreaker({ maxAttemptsPerEntity: 4 });
169
+ for (let i = 0; i < 6; i++) {
170
+ const ctx = makeCtx({
171
+ filePath: "src/__tests__/payment.test.ts",
172
+ entityKey: "src/__tests__/payment.test.ts::testProcessPayment",
173
+ result: makeErrorResult(),
174
+ });
175
+ const output = await breaker.onPostToolUse(ctx);
176
+ expect(output).toBeNull();
177
+ }
178
+ });
179
+ it("exempts .spec.ts files", async () => {
180
+ const breaker = new LoopCircuitBreaker({ maxAttemptsPerEntity: 4 });
181
+ for (let i = 0; i < 6; i++) {
182
+ const ctx = makeCtx({
183
+ filePath: "src/payment.spec.ts",
184
+ entityKey: "src/payment.spec.ts::specProcessPayment",
185
+ result: makeErrorResult(),
186
+ });
187
+ expect(await breaker.onPostToolUse(ctx)).toBeNull();
188
+ }
189
+ });
190
+ });
191
+ describe("Dollar Calculation", () => {
192
+ it("includes dollar amount in guard moment output", async () => {
193
+ const breaker = new LoopCircuitBreaker({ maxAttemptsPerEntity: 4 });
194
+ let lastOutput = null;
195
+ for (let i = 0; i < 4; i++) {
196
+ lastOutput = await breaker.onPostToolUse(makeCtx({ result: makeErrorResult() }));
197
+ }
198
+ expect(lastOutput).not.toBeNull();
199
+ expect(lastOutput?._meta?.dollars_saved).toBeDefined();
200
+ expect(lastOutput?._meta?.tokens_saved).toBeGreaterThan(0);
201
+ });
202
+ it("accumulates savings across multiple loops", async () => {
203
+ const breaker = new LoopCircuitBreaker({
204
+ maxAttemptsPerEntity: 4,
205
+ cooldownMs: 50,
206
+ });
207
+ for (let i = 0; i < 4; i++) {
208
+ await breaker.onPostToolUse(makeCtx({ result: makeErrorResult() }));
209
+ }
210
+ const stats1 = breaker.getSessionStats();
211
+ expect(stats1.loopsPrevented).toBe(1);
212
+ expect(stats1.totalTokensSaved).toBeGreaterThan(0);
213
+ await new Promise((r) => setTimeout(r, 100));
214
+ await breaker.onPreToolUse(makeCtx({ entityKey: "src/checkout.ts::handleOrder" }));
215
+ for (let i = 0; i < 4; i++) {
216
+ await breaker.onPostToolUse(makeCtx({
217
+ entityKey: "src/checkout.ts::handleOrder",
218
+ filePath: "src/checkout.ts",
219
+ result: makeErrorResult(),
220
+ }));
221
+ }
222
+ const stats2 = breaker.getSessionStats();
223
+ expect(stats2.loopsPrevented).toBe(2);
224
+ expect(stats2.totalTokensSaved).toBeGreaterThan(stats1.totalTokensSaved);
225
+ });
226
+ });
227
+ describe("$0.50 Gate", () => {
228
+ it("only fires guard when estimated savings exceed $0.50", async () => {
229
+ const breaker = new LoopCircuitBreaker({ maxAttemptsPerEntity: 4 });
230
+ let guardFired = false;
231
+ for (let i = 0; i < 4; i++) {
232
+ const output = await breaker.onPostToolUse(makeCtx({ result: makeErrorResult() }));
233
+ if (output?.guardMoment)
234
+ guardFired = true;
235
+ }
236
+ expect(guardFired).toBe(true);
237
+ });
238
+ });
239
+ describe("Session Stats", () => {
240
+ it("tracks active and open circuits", async () => {
241
+ const breaker = new LoopCircuitBreaker({ maxAttemptsPerEntity: 4 });
242
+ for (let i = 0; i < 4; i++) {
243
+ await breaker.onPostToolUse(makeCtx({ result: makeErrorResult() }));
244
+ }
245
+ const stats = breaker.getSessionStats();
246
+ expect(stats.activeCircuits).toBe(1);
247
+ expect(stats.openCircuits).toBe(1);
248
+ expect(stats.loopsPrevented).toBe(1);
249
+ });
250
+ });
251
+ describe("Edge Cases", () => {
252
+ it("does nothing without entityKey", async () => {
253
+ const breaker = new LoopCircuitBreaker({ maxAttemptsPerEntity: 4 });
254
+ const ctx = makeCtx({ entityKey: undefined, result: makeErrorResult() });
255
+ expect(await breaker.onPostToolUse(ctx)).toBeNull();
256
+ });
257
+ it("handles success results correctly (no false positive)", async () => {
258
+ const breaker = new LoopCircuitBreaker({ maxAttemptsPerEntity: 4 });
259
+ for (let i = 0; i < 6; i++) {
260
+ const output = await breaker.onPostToolUse(makeCtx({ result: makeSuccessResult() }));
261
+ expect(output).toBeNull();
262
+ }
263
+ });
264
+ it("resets failure count on success between errors", async () => {
265
+ const breaker = new LoopCircuitBreaker({ maxAttemptsPerEntity: 4 });
266
+ for (let i = 0; i < 3; i++) {
267
+ await breaker.onPostToolUse(makeCtx({ result: makeErrorResult() }));
268
+ }
269
+ await breaker.onPostToolUse(makeCtx({ result: makeSuccessResult() }));
270
+ for (let i = 0; i < 3; i++) {
271
+ const output = await breaker.onPostToolUse(makeCtx({ result: makeErrorResult() }));
272
+ expect(output).toBeNull();
273
+ }
274
+ });
275
+ });
276
+ });