@unerr-ai/unerr 0.2.0 → 0.2.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 (363) hide show
  1. package/README.md +6 -0
  2. package/dist/cli.js +37236 -35793
  3. package/package.json +6 -1
  4. package/dist/behaviors/agent-llm-bridge.js +0 -166
  5. package/dist/behaviors/architecture-guard.js +0 -256
  6. package/dist/behaviors/auto-doc.js +0 -247
  7. package/dist/behaviors/cascade-guard.js +0 -289
  8. package/dist/behaviors/change-narrative.js +0 -270
  9. package/dist/behaviors/convention-drift.js +0 -290
  10. package/dist/behaviors/framework.js +0 -235
  11. package/dist/behaviors/guard-formatter.js +0 -44
  12. package/dist/behaviors/incomplete-work.js +0 -270
  13. package/dist/behaviors/loop-breaker.js +0 -300
  14. package/dist/behaviors/session-continuity.js +0 -208
  15. package/dist/commands/branches.js +0 -97
  16. package/dist/commands/check-commit.js +0 -225
  17. package/dist/commands/compress-output.js +0 -64
  18. package/dist/commands/config-verify.js +0 -243
  19. package/dist/commands/daemon.js +0 -905
  20. package/dist/commands/dashboard.js +0 -52
  21. package/dist/commands/debug.js +0 -200
  22. package/dist/commands/enrich.js +0 -184
  23. package/dist/commands/exec.js +0 -233
  24. package/dist/commands/gain.js +0 -156
  25. package/dist/commands/hook.js +0 -88
  26. package/dist/commands/index.js +0 -88
  27. package/dist/commands/init.js +0 -74
  28. package/dist/commands/install.js +0 -505
  29. package/dist/commands/learn.js +0 -116
  30. package/dist/commands/manifest.js +0 -193
  31. package/dist/commands/rewind.js +0 -103
  32. package/dist/commands/serve.js +0 -19
  33. package/dist/commands/setup-wizard.js +0 -414
  34. package/dist/commands/skills.js +0 -64
  35. package/dist/commands/stats.js +0 -20
  36. package/dist/commands/status.js +0 -654
  37. package/dist/commands/timeline.js +0 -139
  38. package/dist/commands/uninstall.js +0 -230
  39. package/dist/components/App.js +0 -109
  40. package/dist/components/Banner.js +0 -12
  41. package/dist/components/ConfirmPrompt.js +0 -25
  42. package/dist/components/DriftSummary.js +0 -23
  43. package/dist/components/GradeBadge.js +0 -15
  44. package/dist/components/HealthCard.js +0 -18
  45. package/dist/components/InkSpinner.js +0 -22
  46. package/dist/components/InputBox.js +0 -17
  47. package/dist/components/KeyValue.js +0 -13
  48. package/dist/components/MessageList.js +0 -14
  49. package/dist/components/ProgressBar.js +0 -26
  50. package/dist/components/Section.js +0 -16
  51. package/dist/components/SessionSummaryCard.js +0 -73
  52. package/dist/components/StartupDisplay.js +0 -24
  53. package/dist/components/StatusDashboard.js +0 -57
  54. package/dist/components/StatusLine.js +0 -8
  55. package/dist/components/StepLine.js +0 -22
  56. package/dist/components/Theme.js +0 -20
  57. package/dist/components/ToolProgress.js +0 -8
  58. package/dist/components/ViolationList.js +0 -21
  59. package/dist/components/render.js +0 -13
  60. package/dist/config/agent-registry.js +0 -237
  61. package/dist/config/claude-settings-hooks.js +0 -304
  62. package/dist/config/hook-installer.js +0 -65
  63. package/dist/config/instruction-writer.js +0 -388
  64. package/dist/config/mcp-config-writer.js +0 -266
  65. package/dist/config/settings.js +0 -174
  66. package/dist/config/tool-detector.js +0 -42
  67. package/dist/config/value-surfacing.js +0 -119
  68. package/dist/core/context-assembly.js +0 -108
  69. package/dist/core/conversation.js +0 -33
  70. package/dist/core/local-chat-provider.js +0 -475
  71. package/dist/core/provider-factory.js +0 -55
  72. package/dist/core/providers.js +0 -90
  73. package/dist/core/query-engine.js +0 -174
  74. package/dist/daemon/api.js +0 -312
  75. package/dist/daemon/autostart.js +0 -119
  76. package/dist/daemon/bootstrap.js +0 -39
  77. package/dist/daemon/client.js +0 -164
  78. package/dist/daemon/detect-ci.js +0 -81
  79. package/dist/daemon/platform-linux.js +0 -146
  80. package/dist/daemon/platform-macos.js +0 -134
  81. package/dist/daemon/platform-windows.js +0 -116
  82. package/dist/daemon/process-manager.js +0 -299
  83. package/dist/daemon/protocol.js +0 -23
  84. package/dist/daemon/registry.js +0 -270
  85. package/dist/daemon/settings-schema.js +0 -72
  86. package/dist/daemon/system-health.js +0 -134
  87. package/dist/daemon/version-checker.js +0 -262
  88. package/dist/daemon/warm-start.js +0 -223
  89. package/dist/entrypoints/cli.js +0 -1043
  90. package/dist/entrypoints/daemon.js +0 -380
  91. package/dist/entrypoints/repl.js +0 -147
  92. package/dist/hooks/adapters/claude-code.js +0 -90
  93. package/dist/hooks/adapters/cline.js +0 -100
  94. package/dist/hooks/adapters/cursor.js +0 -98
  95. package/dist/hooks/hook-dedup.js +0 -79
  96. package/dist/hooks/hook-runner.js +0 -113
  97. package/dist/hooks/navigation-hooks.js +0 -175
  98. package/dist/hooks/prompt-hooks.js +0 -63
  99. package/dist/hooks/shell-hooks.js +0 -47
  100. package/dist/ignore.js +0 -111
  101. package/dist/intelligence/approach-suggester.js +0 -61
  102. package/dist/intelligence/ast-extractor.js +0 -2615
  103. package/dist/intelligence/ast-worker.js +0 -34
  104. package/dist/intelligence/background-indexer.js +0 -121
  105. package/dist/intelligence/blast-radius.js +0 -200
  106. package/dist/intelligence/community-detection.js +0 -691
  107. package/dist/intelligence/community-detector.js +0 -184
  108. package/dist/intelligence/computation-scheduler.js +0 -75
  109. package/dist/intelligence/confidence-propagation.js +0 -47
  110. package/dist/intelligence/convention-detector.js +0 -242
  111. package/dist/intelligence/convention-learner.js +0 -205
  112. package/dist/intelligence/convention-matcher.js +0 -205
  113. package/dist/intelligence/cozo-schema.js +0 -376
  114. package/dist/intelligence/decision-point-detector.js +0 -90
  115. package/dist/intelligence/deep-dive-tools.js +0 -586
  116. package/dist/intelligence/durability-scorer.js +0 -84
  117. package/dist/intelligence/exploration-cost.js +0 -204
  118. package/dist/intelligence/exploration-pattern-tracker.js +0 -61
  119. package/dist/intelligence/fact-generator.js +0 -322
  120. package/dist/intelligence/facts-schema.js +0 -90
  121. package/dist/intelligence/file-intelligence.js +0 -59
  122. package/dist/intelligence/graph-holder.js +0 -220
  123. package/dist/intelligence/graph-temporal-joiner.js +0 -238
  124. package/dist/intelligence/health-grade.js +0 -423
  125. package/dist/intelligence/health-grader.js +0 -200
  126. package/dist/intelligence/health-map-data.js +0 -259
  127. package/dist/intelligence/import-symbols.js +0 -136
  128. package/dist/intelligence/incremental-indexer.js +0 -658
  129. package/dist/intelligence/indexer/centrality.js +0 -62
  130. package/dist/intelligence/indexer/cfg-context.js +0 -95
  131. package/dist/intelligence/indexer/confidence.js +0 -34
  132. package/dist/intelligence/indexer/cross-file-resolver.js +0 -104
  133. package/dist/intelligence/indexer/edge-repair.js +0 -89
  134. package/dist/intelligence/indexer/entity-key.js +0 -17
  135. package/dist/intelligence/indexer/export-map.js +0 -132
  136. package/dist/intelligence/indexer/git-cochange.js +0 -128
  137. package/dist/intelligence/indexer/graph-patch.js +0 -147
  138. package/dist/intelligence/indexer/incremental.js +0 -78
  139. package/dist/intelligence/indexer/ingest.js +0 -160
  140. package/dist/intelligence/indexer/language-detect.js +0 -226
  141. package/dist/intelligence/indexer/metadata.js +0 -63
  142. package/dist/intelligence/indexer/mutation-tracker.js +0 -79
  143. package/dist/intelligence/indexer/orchestrator.js +0 -155
  144. package/dist/intelligence/indexer/plugin-interface.js +0 -31
  145. package/dist/intelligence/indexer/plugins/csharp.js +0 -440
  146. package/dist/intelligence/indexer/plugins/go.js +0 -335
  147. package/dist/intelligence/indexer/plugins/java.js +0 -370
  148. package/dist/intelligence/indexer/plugins/python.js +0 -358
  149. package/dist/intelligence/indexer/plugins/regex-fallback.js +0 -82
  150. package/dist/intelligence/indexer/plugins/ruby.js +0 -290
  151. package/dist/intelligence/indexer/plugins/rust.js +0 -484
  152. package/dist/intelligence/indexer/plugins/tier2-generic.js +0 -310
  153. package/dist/intelligence/indexer/plugins/typescript.js +0 -456
  154. package/dist/intelligence/indexer/resource-monitor.js +0 -93
  155. package/dist/intelligence/indexer/scip/decoder.js +0 -253
  156. package/dist/intelligence/indexer/scip/detector.js +0 -232
  157. package/dist/intelligence/indexer/scip/downloader.js +0 -427
  158. package/dist/intelligence/indexer/scip/fallback.js +0 -34
  159. package/dist/intelligence/indexer/scip/merger.js +0 -109
  160. package/dist/intelligence/indexer/scip/orchestrator.js +0 -433
  161. package/dist/intelligence/indexer/scip/runner.js +0 -98
  162. package/dist/intelligence/indexer/snapshot.js +0 -66
  163. package/dist/intelligence/indexer/test-detector.js +0 -196
  164. package/dist/intelligence/indexer/watch-integration.js +0 -61
  165. package/dist/intelligence/indexer/worker.js +0 -85
  166. package/dist/intelligence/local-convention-detector.js +0 -437
  167. package/dist/intelligence/local-embeddings.js +0 -190
  168. package/dist/intelligence/local-graph.js +0 -1946
  169. package/dist/intelligence/local-indexer.js +0 -1575
  170. package/dist/intelligence/local-llm.js +0 -163
  171. package/dist/intelligence/local-rule-generator.js +0 -154
  172. package/dist/intelligence/local-snapshot.js +0 -213
  173. package/dist/intelligence/negative-knowledge.js +0 -103
  174. package/dist/intelligence/persistent-db.js +0 -85
  175. package/dist/intelligence/query-router.js +0 -2556
  176. package/dist/intelligence/risk-classifier.js +0 -116
  177. package/dist/intelligence/rule-evaluator.js +0 -380
  178. package/dist/intelligence/rule-generator.js +0 -49
  179. package/dist/intelligence/search-index.js +0 -173
  180. package/dist/intelligence/semantic/docstring-extractor.js +0 -67
  181. package/dist/intelligence/semantic/embedding-store.js +0 -52
  182. package/dist/intelligence/semantic/enrichment-orchestrator.js +0 -48
  183. package/dist/intelligence/semantic/git-message-miner.js +0 -114
  184. package/dist/intelligence/semantic/identifier-tokenizer.js +0 -51
  185. package/dist/intelligence/semantic/node2vec-embeddings.js +0 -71
  186. package/dist/intelligence/semantic/node2vec-walks.js +0 -103
  187. package/dist/intelligence/semantic/path-domain-inference.js +0 -112
  188. package/dist/intelligence/semantic/similarity-engine.js +0 -60
  189. package/dist/intelligence/semantic/tfidf-vectors.js +0 -88
  190. package/dist/intelligence/session-brief-builder.js +0 -159
  191. package/dist/intelligence/session-context.js +0 -221
  192. package/dist/intelligence/session-health-monitor.js +0 -211
  193. package/dist/intelligence/session-narrative.js +0 -197
  194. package/dist/intelligence/session-pattern-analyzer.js +0 -218
  195. package/dist/intelligence/signal-scorer.js +0 -390
  196. package/dist/intelligence/signal-show-store.js +0 -182
  197. package/dist/intelligence/smart-truncate.js +0 -158
  198. package/dist/intelligence/subgraph-cache.js +0 -88
  199. package/dist/intelligence/temporal-facts.js +0 -494
  200. package/dist/intelligence/token-estimator.js +0 -100
  201. package/dist/intelligence/tool-injector.js +0 -87
  202. package/dist/intelligence/tree-sitter-loader.js +0 -71
  203. package/dist/intelligence/worker-pool.js +0 -116
  204. package/dist/proxy/arg-validator.js +0 -79
  205. package/dist/proxy/auto-bootstrap.js +0 -167
  206. package/dist/proxy/bridge.js +0 -147
  207. package/dist/proxy/budget-enforcer.js +0 -70
  208. package/dist/proxy/compression-quality-monitor.js +0 -160
  209. package/dist/proxy/compression-stats.js +0 -51
  210. package/dist/proxy/context-rot-detector.js +0 -137
  211. package/dist/proxy/drift-detector.js +0 -139
  212. package/dist/proxy/efficiency-tracker.js +0 -79
  213. package/dist/proxy/fact-ranking.js +0 -154
  214. package/dist/proxy/format-encoder.js +0 -266
  215. package/dist/proxy/http-transport.js +0 -90
  216. package/dist/proxy/lifecycle-actor.js +0 -55
  217. package/dist/proxy/lifecycle-machine.js +0 -187
  218. package/dist/proxy/log-tailer.js +0 -265
  219. package/dist/proxy/model-pricing.js +0 -98
  220. package/dist/proxy/network-firewall.js +0 -141
  221. package/dist/proxy/nudge-state.js +0 -93
  222. package/dist/proxy/output-compressor.js +0 -185
  223. package/dist/proxy/pid-lock.js +0 -291
  224. package/dist/proxy/proxy-context.js +0 -11
  225. package/dist/proxy/proxy.js +0 -2633
  226. package/dist/proxy/response-enrichment.js +0 -32
  227. package/dist/proxy/response-envelope.js +0 -313
  228. package/dist/proxy/session-dedup.js +0 -82
  229. package/dist/proxy/session-legend.js +0 -30
  230. package/dist/proxy/session-persistence.js +0 -210
  231. package/dist/proxy/session-resume.js +0 -94
  232. package/dist/proxy/session-stats.js +0 -513
  233. package/dist/proxy/shell-classifier.js +0 -1346
  234. package/dist/proxy/shell-compression-log.js +0 -93
  235. package/dist/proxy/shell-compressor.js +0 -390
  236. package/dist/proxy/shell-graph-boost.js +0 -202
  237. package/dist/proxy/shell-monitor-map.js +0 -18
  238. package/dist/proxy/shell-stats.js +0 -54
  239. package/dist/proxy/shell-strategies/cloud.js +0 -215
  240. package/dist/proxy/shell-strategies/diff.js +0 -159
  241. package/dist/proxy/shell-strategies/error-diagnostic.js +0 -796
  242. package/dist/proxy/shell-strategies/filter-dsl.js +0 -358
  243. package/dist/proxy/shell-strategies/git-status.js +0 -177
  244. package/dist/proxy/shell-strategies/key-value.js +0 -193
  245. package/dist/proxy/shell-strategies/log-text.js +0 -154
  246. package/dist/proxy/shell-strategies/omni.js +0 -188
  247. package/dist/proxy/shell-strategies/progress.js +0 -55
  248. package/dist/proxy/shell-strategies/redact.js +0 -76
  249. package/dist/proxy/shell-strategies/structured.js +0 -241
  250. package/dist/proxy/shell-strategies/tabular.js +0 -243
  251. package/dist/proxy/shell-strategies/test-results-types.js +0 -13
  252. package/dist/proxy/shell-strategies/test-results.js +0 -784
  253. package/dist/proxy/shell-strategies/tree-paths.js +0 -144
  254. package/dist/proxy/shell-strategies/yaml.js +0 -182
  255. package/dist/proxy/shell-tee.js +0 -111
  256. package/dist/proxy/signal-dedup.js +0 -171
  257. package/dist/proxy/startup-renderer.js +0 -158
  258. package/dist/proxy/task-token-display.js +0 -38
  259. package/dist/proxy/token-counter.js +0 -61
  260. package/dist/proxy/tool-clusters.js +0 -273
  261. package/dist/proxy/tool-definitions.js +0 -525
  262. package/dist/proxy/transport-mux.js +0 -229
  263. package/dist/proxy/wire-cap.js +0 -268
  264. package/dist/rules/developer.mozilla.org.json +0 -9
  265. package/dist/rules/github.com.json +0 -21
  266. package/dist/schemas/api/skills.js +0 -19
  267. package/dist/schemas/common/errors.js +0 -7
  268. package/dist/schemas/common/headers.js +0 -5
  269. package/dist/schemas/entities/edge.js +0 -25
  270. package/dist/schemas/entities/entity.js +0 -22
  271. package/dist/schemas/entities/rule.js +0 -18
  272. package/dist/schemas/index.js +0 -14
  273. package/dist/server/event-bus.js +0 -59
  274. package/dist/server/http.js +0 -156
  275. package/dist/server/middleware.js +0 -70
  276. package/dist/server/routes/drift.js +0 -97
  277. package/dist/server/routes/intelligence.js +0 -1217
  278. package/dist/server/routes/reasoning-quality.js +0 -444
  279. package/dist/server/routes/session.js +0 -86
  280. package/dist/server/routes/stream.js +0 -120
  281. package/dist/server/routes/system.js +0 -73
  282. package/dist/server/routes/temporal.js +0 -170
  283. package/dist/server/routes/timeline.js +0 -232
  284. package/dist/server/routes/token-flow.js +0 -403
  285. package/dist/skills/effectiveness-tracker.js +0 -93
  286. package/dist/skills/local-pack.js +0 -380
  287. package/dist/skills/resolver.js +0 -495
  288. package/dist/state-detector.js +0 -83
  289. package/dist/timeline/intent-detector.js +0 -263
  290. package/dist/timeline/loop-miner.js +0 -140
  291. package/dist/timeline/open-threads.js +0 -49
  292. package/dist/timeline/signal-reinforcer.js +0 -62
  293. package/dist/timeline/timeline-bootstrap.js +0 -151
  294. package/dist/timeline/timeline-store.js +0 -618
  295. package/dist/tools/coding/bash.js +0 -49
  296. package/dist/tools/coding/file-edit.js +0 -72
  297. package/dist/tools/coding/file-outline.js +0 -227
  298. package/dist/tools/coding/file-read-protocol.js +0 -425
  299. package/dist/tools/coding/file-read.js +0 -35
  300. package/dist/tools/coding/file-write.js +0 -43
  301. package/dist/tools/coding/glob-tool.js +0 -109
  302. package/dist/tools/coding/grep.js +0 -162
  303. package/dist/tools/coding/index.js +0 -27
  304. package/dist/tools/intelligence/index.js +0 -269
  305. package/dist/tools/intelligence/record-fact.js +0 -48
  306. package/dist/tools/intelligence/timeline-markers.js +0 -130
  307. package/dist/tools/registry.js +0 -47
  308. package/dist/tools/types.js +0 -8
  309. package/dist/tracking/auto-snapshot-triggers.js +0 -246
  310. package/dist/tracking/branch-context.js +0 -115
  311. package/dist/tracking/branch-snapshot.js +0 -217
  312. package/dist/tracking/causal-bridge.js +0 -317
  313. package/dist/tracking/circuit-breaker.js +0 -147
  314. package/dist/tracking/commit-watcher.js +0 -114
  315. package/dist/tracking/context-ledger.js +0 -119
  316. package/dist/tracking/correction-detector.js +0 -324
  317. package/dist/tracking/drift-tracker.js +0 -874
  318. package/dist/tracking/durability-tracker.js +0 -94
  319. package/dist/tracking/entity-rewind.js +0 -200
  320. package/dist/tracking/file-hash-state.js +0 -114
  321. package/dist/tracking/git-attribution.js +0 -132
  322. package/dist/tracking/git-trailers.js +0 -171
  323. package/dist/tracking/intelligence-counter.js +0 -46
  324. package/dist/tracking/intent-correlator.js +0 -202
  325. package/dist/tracking/intent-encoder.js +0 -52
  326. package/dist/tracking/intent-token-tracker.js +0 -159
  327. package/dist/tracking/ledger-archiver.js +0 -94
  328. package/dist/tracking/ledger-chains.js +0 -245
  329. package/dist/tracking/metrics-store.js +0 -361
  330. package/dist/tracking/native-watcher.js +0 -131
  331. package/dist/tracking/offline-rewind.js +0 -295
  332. package/dist/tracking/pending-violations.js +0 -74
  333. package/dist/tracking/persistence-effectiveness.js +0 -167
  334. package/dist/tracking/prompt-durability.js +0 -202
  335. package/dist/tracking/quality-signals.js +0 -213
  336. package/dist/tracking/redactor.js +0 -73
  337. package/dist/tracking/rewind-engine.js +0 -161
  338. package/dist/tracking/session-history.js +0 -128
  339. package/dist/tracking/session-receipt.js +0 -88
  340. package/dist/tracking/session-summary-writer.js +0 -157
  341. package/dist/tracking/shadow-ledger.js +0 -321
  342. package/dist/tracking/stash-manager.js +0 -258
  343. package/dist/tracking/timeline-fork.js +0 -213
  344. package/dist/tracking/timeline.js +0 -69
  345. package/dist/tracking/token-flow.js +0 -276
  346. package/dist/tracking/turn-segmenter.js +0 -122
  347. package/dist/tracking/weekly-accumulator.js +0 -179
  348. package/dist/tracking/working-snapshots.js +0 -188
  349. package/dist/tracking/workspace-manifest.js +0 -176
  350. package/dist/transport/http.js +0 -102
  351. package/dist/utils/counterfactual.js +0 -65
  352. package/dist/utils/deep-link.js +0 -34
  353. package/dist/utils/detect.js +0 -193
  354. package/dist/utils/exec.js +0 -73
  355. package/dist/utils/file-logger.js +0 -87
  356. package/dist/utils/format-error.js +0 -29
  357. package/dist/utils/git.js +0 -181
  358. package/dist/utils/log.js +0 -57
  359. package/dist/utils/logger.js +0 -35
  360. package/dist/utils/mcp-content-json.js +0 -8
  361. package/dist/utils/session-logger.js +0 -154
  362. package/dist/utils/startup-log.js +0 -512
  363. package/dist/utils/ui.js +0 -56
@@ -1,32 +0,0 @@
1
- /**
2
- * Intelligence Response Enrichment — populates the internal `meta` / `context`
3
- * carrier with risk, confidence, and convention data. These fields never reach
4
- * the wire — `buildSignalPrefix()` drains them into `ur|<tag>` prefix lines at
5
- * the wire boundary (MCP clients strip `_meta`/`_context` envelopes).
6
- *
7
- * P.9: Every intelligence query response carries (on the internal envelope):
8
- * - meta.confidence: propagated confidence level
9
- * - meta.risk_level: entity risk classification → `ur|rsk` when high
10
- * - meta.resolution_ms: query execution time
11
- * - context.conventions: applicable conventions → `ur|hnt` / `ur|fct` lines
12
- */
13
- import { confidenceToScore } from "../intelligence/confidence-propagation.js";
14
- /**
15
- * Enrich a query response with intelligence metadata.
16
- */
17
- export function enrichResponse(content, meta, context) {
18
- const result = {
19
- content,
20
- _meta: {
21
- confidence: meta.confidence,
22
- confidence_score: confidenceToScore(meta.confidence),
23
- risk_level: meta.riskLevel,
24
- resolution_ms: Math.round(meta.resolutionMs * 100) / 100,
25
- edge_sources: meta.edgeSources,
26
- },
27
- };
28
- if (context && Object.keys(context).length > 0) {
29
- result._context = context;
30
- }
31
- return result;
32
- }
@@ -1,313 +0,0 @@
1
- /**
2
- * MCP Response Envelope — standardized wrapper for every tool response.
3
- *
4
- * Every MCP tool response is enriched with:
5
- * _meta: { "dev.unerr/tokens_saved", "dev.unerr/latency_ms", "dev.unerr/version" }
6
- * _context: { optional intelligence injections from blast-radius, conventions, etc. }
7
- *
8
- * The envelope pipeline is composable: injectors register themselves and
9
- * are invoked in order for each response. The session dedup layer sits
10
- * on top, filtering context that has already been delivered.
11
- *
12
- * Token estimation uses char/4 as a fast approximation.
13
- * Sprint G will replace this with a proper tiktoken-based engine.
14
- */
15
- import { estimateTokens } from "../intelligence/token-estimator.js";
16
- export { estimateTokens };
17
- const VERSION = "0.1.0";
18
- /**
19
- * Update notification state — set by the daemon's version checker.
20
- * When set, tool responses include `_meta["dev.unerr/update_available"]`.
21
- * Only injected when >2 minor versions behind (high signal, low noise).
22
- */
23
- let updateNotification = null;
24
- export function setUpdateNotification(latest, current) {
25
- updateNotification = { latest, current };
26
- }
27
- export function clearUpdateNotification() {
28
- updateNotification = null;
29
- }
30
- /**
31
- * Create a response envelope pipeline with registered context injectors.
32
- */
33
- export function createEnvelopePipeline(injectors = []) {
34
- /**
35
- * Wrap a raw tool response in the standard envelope.
36
- *
37
- * @param content - The original tool response content
38
- * @param latencyMs - Time taken to execute the tool (ms)
39
- * @param toolName - Name of the MCP tool that was called
40
- * @param toolArgs - Arguments passed to the tool
41
- * @param originalTokens - Token count of the raw input (for savings calculation)
42
- */
43
- async function wrapResponse(content, latencyMs, toolName = "", toolArgs = {}, originalTokens = 0) {
44
- const responseTokens = estimateTokens(content);
45
- const tokensSaved = Math.max(0, originalTokens - responseTokens);
46
- const _meta = {
47
- "dev.unerr/tokens_saved": tokensSaved,
48
- "dev.unerr/latency_ms": Math.round(latencyMs * 100) / 100,
49
- "dev.unerr/version": VERSION,
50
- };
51
- if (updateNotification) {
52
- _meta["dev.unerr/update_available"] =
53
- `${updateNotification.current} → ${updateNotification.latest}`;
54
- }
55
- const _context = {};
56
- for (const injector of injectors) {
57
- try {
58
- const injected = await injector.inject({
59
- toolName,
60
- toolArgs,
61
- content,
62
- latencyMs,
63
- });
64
- if (injected) {
65
- for (const [k, v] of Object.entries(injected)) {
66
- _context[k] = v;
67
- }
68
- }
69
- }
70
- catch {
71
- /* injector failures are silently swallowed — never break the response */
72
- }
73
- }
74
- const envelope = { content, _meta };
75
- if (Object.keys(_context).length > 0) {
76
- envelope._context = _context;
77
- }
78
- return envelope;
79
- }
80
- return { wrapResponse };
81
- }
82
- /**
83
- * Tier-2 strip: trim wire-only fields from `_meta` before serialization.
84
- *
85
- * Internal callers (proxy stats, eventBus, dashboard SSE) read meta BEFORE this
86
- * runs; the wire envelope drops fields the agent never acts on.
87
- *
88
- * Rules — silent by default, vocal only when actionable:
89
- * - source / mode / mode_reason: always "local" in OSS → strip (mode kept when degraded)
90
- * - latency_ms: stderr telemetry only → strip
91
- * - format / columns / columnar_legend / output_format_legend: in-band
92
- * `_fmt:columnar` body header is canonical; _meta duplicates → strip
93
- * - tokens_budget: caller already knows what they asked for → strip
94
- * - tokens_used / truncated: meaningful only when truncated:true → conditional
95
- */
96
- const WIRE_STRIP_ALWAYS = new Set([
97
- "source",
98
- "latency_ms",
99
- "format",
100
- "columns",
101
- "columnar_legend",
102
- "output_format_legend",
103
- "tokens_budget",
104
- ]);
105
- export function wireifyMeta(meta) {
106
- if (!meta)
107
- return meta;
108
- const out = {};
109
- const isLocalMode = meta.mode === "local";
110
- const isTruncated = meta.truncated === true;
111
- for (const [k, v] of Object.entries(meta)) {
112
- if (WIRE_STRIP_ALWAYS.has(k))
113
- continue;
114
- if ((k === "mode" || k === "mode_reason") && isLocalMode)
115
- continue;
116
- if (k === "tokens_used" && !isTruncated)
117
- continue;
118
- if (k === "truncated" && !isTruncated)
119
- continue;
120
- if (v === undefined)
121
- continue;
122
- out[k] = v;
123
- }
124
- return out;
125
- }
126
- /**
127
- * Tier-3 strip: build a leading "ur|<tag>" signal block that goes INTO the
128
- * tool response body (`content[].text`).
129
- *
130
- * Why: per the MCP 2025-06-18 spec and confirmed in Claude Code / OpenAI Agents
131
- * SDK, `_meta` and custom envelope fields like `_context` are filtered by the
132
- * client and never reach the LLM context window. To keep anti-drift signals
133
- * actually visible to the model, we prepend them into the body.
134
- *
135
- * Token-optimized prefix format (no brackets/colons — minimal token cost):
136
- * "ur|<tag> <message>"
137
- *
138
- * `ur|` is typically a single BPE token in modern tokenizers. The 3-char tag
139
- * is also one token. So the entire signal prefix costs ~2-3 tokens of overhead
140
- * vs ~5 with bracketed notation. Legend lives in agent instruction files
141
- * (CLAUDE.md, .cursor/rules/*, etc.) via instruction-writer.ts so the LLM
142
- * knows what each one means without paying for the full word every call.
143
- *
144
- * hlt halt loop/circuit break — stop retrying
145
- * dft drift file/entity changed — re-read before edit
146
- * rsk risk high blast radius — verify callers
147
- * wrn warn anti-pattern / negative fact
148
- * hnt hint guidance / co-change suggestion
149
- * fct fact surfaced project fact (procedural / convention / semantic)
150
- * ctx context already delivered — don't re-query
151
- * hth health session degraded — consider new session
152
- * hst hist prior failures on this entity
153
- * ur| generic nudge (no tag)
154
- *
155
- * Only emitted when actionable. Marketing/metrics/"survived" causal histories
156
- * are dropped.
157
- */
158
- export const SIGNAL_PREFIX_LEGEND = `ur|<tag> is an unerr signal appended to MCP responses (as a footer). Tags:
159
- hlt halt — loop/circuit break: stop retrying this entity, mark_blocker and switch approach
160
- dft drift — file/entity changed: re-read with file_read/get_entity before edit
161
- rsk risk — high blast radius: call get_references({direction:'callers'}) before edit
162
- wrn warn — anti-pattern / negative fact: do not reintroduce the listed pattern
163
- hnt hint — guidance: apply the named pattern or co-modify the listed files
164
- fct fact — surfaced project fact (procedural / convention / semantic). Fact subtype is in [brackets] in the message.
165
- hth health — session degraded: start a new session before the next non-trivial task
166
- hst hist — prior failure modes: read each mode before retrying the same approach
167
- pg page — pagination/wire-cap: response was capped. Format: "ur|pg <tool> +<remaining> — <cursorArg>:<nextValue>". Paste <nextValue> back into the same arg to fetch the next slice; the values are concrete numbers, not placeholders.
168
- ur| generic nudge (no tag)`;
169
- /** Map signal-scorer types → 3-char wire tag. The scorer emits exactly 4
170
- * types (see SignalType in signal-scorer.ts): warning, guidance, context,
171
- * history. The fact_type ([procedural], [convention], etc.) is already
172
- * embedded in the message content by the scorer. */
173
- export function signalTag(type) {
174
- switch (type) {
175
- case "warning":
176
- return "wrn";
177
- case "guidance":
178
- return "hnt";
179
- case "context":
180
- return "fct"; // surfaced fact (procedural / convention / semantic)
181
- case "history":
182
- return "hst";
183
- // Direct fact-type passthrough (not currently emitted but reserved):
184
- case "convention":
185
- return "cnv";
186
- case "procedural":
187
- return "pro";
188
- case "semantic":
189
- return "sem";
190
- case "episodic":
191
- return "epi";
192
- case "negative":
193
- return "wrn";
194
- default:
195
- return (type ?? "inf").slice(0, 3);
196
- }
197
- }
198
- /**
199
- * Hard caps applied AFTER per-tag dedup. The goal is "high-signal, low-noise":
200
- * - MAX_LINES keeps the model from being drowned in stacked hints.
201
- * - MAX_BYTES is a final safety net for pathological message payloads.
202
- */
203
- const MAX_SIGNAL_LINES = 2;
204
- const MAX_SIGNAL_BYTES = 240;
205
- import { getSignalDedup } from "./signal-dedup.js";
206
- export function buildSignalPrefix(meta, context, entityKey = null) {
207
- const dedup = getSignalDedup();
208
- const lines = [];
209
- function tryPush(tag, scopeKey, body) {
210
- if (lines.length >= MAX_SIGNAL_LINES)
211
- return;
212
- if (!dedup.shouldEmit(tag, scopeKey, body))
213
- return;
214
- lines.push(`ur|${tag} ${body}`);
215
- }
216
- if (meta?.circuit_breaker) {
217
- const cb = meta.circuit_breaker;
218
- // Build the message from whatever is concrete; if we have neither attempts
219
- // nor entity AND no explicit message, the line would be "? failed attempts
220
- // on entity" — useless. Suppress in that case rather than emit a sentinel.
221
- let msg = null;
222
- if (cb.message) {
223
- msg = cb.message;
224
- }
225
- else if (typeof cb.attempts === "number" && cb.entity) {
226
- msg = `${cb.attempts} failed attempts on ${cb.entity} — stop retrying; mark_blocker and switch approach`;
227
- }
228
- else if (cb.entity) {
229
- msg = `repeated failures on ${cb.entity} — stop retrying; mark_blocker and switch approach`;
230
- }
231
- if (msg)
232
- tryPush("hlt", cb.entity ?? entityKey, msg);
233
- }
234
- if (meta?.drift) {
235
- const d = meta.drift;
236
- if (d.entityStatus) {
237
- const by = d.lastModifiedBy ? ` by ${d.lastModifiedBy}` : "";
238
- const where = d.branch ? ` on ${d.branch}` : "";
239
- tryPush("dft", entityKey, `${d.entityStatus}${where}${by} — re-read before edit`);
240
- }
241
- }
242
- if (meta?.entity_risk) {
243
- const r = meta.entity_risk;
244
- if (r.risk_level === "high") {
245
- // For array/envelope results (e.g. get_references), `r.entity_key` is the
246
- // max-risk reference's key, NOT the queried entity. Scoping dedup on that
247
- // key means each new high-risk neighbor re-fires `ur|rsk` instead of
248
- // being suppressed by `on_change` against the queried entity.
249
- const refKey = r.entity_key
250
- ? `${entityKey ?? "?"}:ref:${r.entity_key}`
251
- : entityKey;
252
- // Table row #15 TRIM — replace vague "verify blast radius" imperative
253
- // with a concrete next-call the agent can paste.
254
- tryPush("rsk", refKey, `fan_in=${r.fan_in ?? 0} fan_out=${r.fan_out ?? 0} (high blast radius — get_references first)`);
255
- }
256
- }
257
- if (meta?.causal_history) {
258
- const ch = meta.causal_history;
259
- const realFailures = (ch.failure_modes ?? []).filter((m) => m !== "survived");
260
- if (realFailures.length > 0) {
261
- tryPush("hst", entityKey, `${ch.interactions ?? 0} interactions, prior: ${realFailures.join(",")}`);
262
- }
263
- }
264
- // Dropped: untagged value_guard (no tag → can't be deduped per-tag, low signal).
265
- // Dropped: meta.context_complete ur|ctx — session-dedup already gates the
266
- // underlying context keys; "ctx" line was pure noise.
267
- if (meta?.session_health) {
268
- const h = meta.session_health;
269
- if (typeof h.health === "number" && h.health < 0.6) {
270
- // Table row #20 TRIM — replace "consider new session" hedge with a
271
- // concrete action the agent / user can execute.
272
- tryPush("hth", null, `${(h.health * 100).toFixed(0)}% — ${h.recommendation ?? "start a new session before next task"}`);
273
- }
274
- }
275
- if (context?.signals && Array.isArray(context.signals)) {
276
- const signals = context.signals;
277
- for (const s of signals) {
278
- if (lines.length >= MAX_SIGNAL_LINES)
279
- break;
280
- if (!s.content)
281
- continue;
282
- const tag = signalTag(s.type);
283
- const action = s.action ? ` — ${s.action}` : "";
284
- tryPush(tag, s.entity ?? entityKey, `${s.content}${action}`);
285
- }
286
- }
287
- // Dropped: context.tool_adoption.hint — appeared on every call regardless of
288
- // whether tool was already adopted; the instruction-writer surfaces this at
289
- // session boot.
290
- if (lines.length === 0)
291
- return "";
292
- let block = `${lines.join("\n")}\n\n`;
293
- if (block.length > MAX_SIGNAL_BYTES) {
294
- // Trim from the tail; first signals are typically the most critical
295
- // (hlt > dft > rsk > hst > hth > scorer signals).
296
- const truncated = [];
297
- let used = 0;
298
- for (const line of lines) {
299
- if (used + line.length + 1 > MAX_SIGNAL_BYTES - 2)
300
- break;
301
- truncated.push(line);
302
- used += line.length + 1;
303
- }
304
- block = truncated.length > 0 ? `${truncated.join("\n")}\n\n` : "";
305
- }
306
- return block;
307
- }
308
- /**
309
- * Convenience: wrap a single response without a pipeline.
310
- */
311
- export async function wrapResponse(content, latencyMs, originalTokens = 0) {
312
- return await createEnvelopePipeline().wrapResponse(content, latencyMs, "", {}, originalTokens);
313
- }
@@ -1,82 +0,0 @@
1
- /**
2
- * Session-Aware Context Deduplication — tracks which _context keys have been
3
- * delivered in this session and filters out repeats.
4
- *
5
- * Design: in-memory only (no persistence across sessions — that's Sprint H).
6
- * Bounded to MAX_TRACKED_KEYS to prevent memory leaks in long sessions.
7
- *
8
- * Temporal intelligence note: the dedup window corresponds to the episodic
9
- * memory tier (Section 13.2). Within a session, repeated context has near-zero
10
- * value. Cross-session dedup requires the warm/cold tier architecture (Sprint H).
11
- */
12
- const MAX_TRACKED_KEYS = 10_000;
13
- /**
14
- * Create a session dedup tracker.
15
- * Filters context keys that have already been delivered for a given entity.
16
- */
17
- export function createSessionDedup() {
18
- const delivered = new Map();
19
- let totalKeys = 0;
20
- function compoundKey(entityKey, contextKey) {
21
- return `${entityKey}::${contextKey}`;
22
- }
23
- function hasDelivered(entityKey, contextKey) {
24
- const entitySet = delivered.get(entityKey);
25
- return entitySet?.has(contextKey) ?? false;
26
- }
27
- function markDelivered(entityKey, contextKeys) {
28
- let entitySet = delivered.get(entityKey);
29
- if (!entitySet) {
30
- entitySet = new Set();
31
- delivered.set(entityKey, entitySet);
32
- }
33
- for (const key of contextKeys) {
34
- if (!entitySet.has(key)) {
35
- entitySet.add(key);
36
- totalKeys++;
37
- }
38
- }
39
- if (totalKeys > MAX_TRACKED_KEYS) {
40
- evictOldest();
41
- }
42
- }
43
- function evictOldest() {
44
- const iterator = delivered.keys();
45
- let evicted = 0;
46
- const target = Math.floor(MAX_TRACKED_KEYS * 0.2);
47
- while (evicted < target) {
48
- const result = iterator.next();
49
- if (result.done)
50
- break;
51
- const entityKey = result.value;
52
- const entitySet = delivered.get(entityKey);
53
- if (entitySet) {
54
- evicted += entitySet.size;
55
- totalKeys -= entitySet.size;
56
- delivered.delete(entityKey);
57
- }
58
- }
59
- }
60
- function filter(entityKey, context) {
61
- const filtered = {};
62
- const newKeys = [];
63
- for (const [key, value] of Object.entries(context)) {
64
- if (!hasDelivered(entityKey, key)) {
65
- filtered[key] = value;
66
- newKeys.push(key);
67
- }
68
- }
69
- if (newKeys.length > 0) {
70
- markDelivered(entityKey, newKeys);
71
- }
72
- return filtered;
73
- }
74
- function getDeliveredCount() {
75
- return totalKeys;
76
- }
77
- function reset() {
78
- delivered.clear();
79
- totalKeys = 0;
80
- }
81
- return { filter, hasDelivered, markDelivered, getDeliveredCount, reset };
82
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * Layer 6 Sprint FE-E — session-scoped legend delivery (columnar protocol + output format).
3
- * Legends attach once per session until invalidated (e.g. retry detected).
4
- */
5
- /** One-time output format legend text (~30 tokens). */
6
- export const OUTPUT_FORMAT_LEGEND = "Output conventions: use unified diff (---/+++/@@ hunks) for edits. " +
7
- "When a response begins with `ur|ctx`, all context for this entity was already delivered — skip restatement. " +
8
- "Prefer file path references over content repetition.";
9
- export function createSessionLegendTracker() {
10
- let columnarSent = false;
11
- let outputFormatSent = false;
12
- return {
13
- consumeColumnarLegend() {
14
- if (columnarSent)
15
- return false;
16
- columnarSent = true;
17
- return true;
18
- },
19
- consumeOutputFormatLegend() {
20
- if (outputFormatSent)
21
- return false;
22
- outputFormatSent = true;
23
- return true;
24
- },
25
- invalidateAll() {
26
- columnarSent = false;
27
- outputFormatSent = false;
28
- },
29
- };
30
- }
@@ -1,210 +0,0 @@
1
- /**
2
- * Session Persistence — cross-session continuity for --mcp mode.
3
- *
4
- * Layer 9 PI-4: On reconnect, loads the previous session's summary and
5
- * generates a targeted resume context that includes:
6
- * - What was in progress (hot files, incomplete entities)
7
- * - Relevant facts from facts.db for those hot files
8
- * - Session metrics (duration, tools used, revert count)
9
- *
10
- * Staleness rule: sessions older than 24h are considered too stale.
11
- * The agent gets fresh fact recall but no session continuity context.
12
- *
13
- * Graceful degradation: if any step fails, returns null (no crash).
14
- */
15
- // ── Constants ────────────────────────────────────────────────────────
16
- const STALENESS_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24 hours
17
- const WARM_THRESHOLD_MS = 4 * 60 * 60 * 1000; // 4 hours
18
- // ── Public API ───────────────────────────────────────────────────────
19
- /**
20
- * Generate a session resume payload from the last session's summary.
21
- * Integrates with facts.db if a fact store is available.
22
- *
23
- * Returns null if:
24
- * - No prior session exists
25
- * - Last session is older than 24h (too stale)
26
- * - Any error occurs (graceful degradation)
27
- */
28
- export async function generateSessionResumePayload(unerrDir, factStore) {
29
- try {
30
- const { readLastSession } = await import("../tracking/session-summary-writer.js");
31
- const lastSession = readLastSession(unerrDir);
32
- if (!lastSession)
33
- return null;
34
- const endedAt = new Date(lastSession.ended_at).getTime();
35
- const elapsed = Date.now() - endedAt;
36
- if (elapsed > STALENESS_THRESHOLD_MS)
37
- return null;
38
- const staleness = elapsed < WARM_THRESHOLD_MS ? "fresh" : "warm";
39
- const hotFiles = computeHotFiles(lastSession);
40
- const incompleteHint = generateIncompleteHint(lastSession);
41
- const recalledFacts = await recallFactsForSession(lastSession, factStore);
42
- // Query facts that recently decayed below recall threshold
43
- let decayedFacts = [];
44
- if (factStore?.recallDecaying) {
45
- try {
46
- const decaying = await factStore.recallDecaying(0.05, 0.2);
47
- decayedFacts = decaying.slice(0, 5).map((f) => ({
48
- fact_id: f.fact_id,
49
- type: f.fact_type,
50
- content: f.content,
51
- confidence: Math.round(f.effective_confidence * 100) / 100,
52
- }));
53
- }
54
- catch {
55
- // Non-critical
56
- }
57
- }
58
- return {
59
- session_resumed: true,
60
- previous_session: {
61
- session_id: lastSession.session_id,
62
- duration_ms: lastSession.duration_ms,
63
- tool_calls: lastSession.tool_calls,
64
- chains: lastSession.chains,
65
- files_modified: lastSession.files_modified.slice(0, 10),
66
- entities_touched: lastSession.entities_touched.slice(0, 10),
67
- tools_used: lastSession.tools_used,
68
- feature_areas: lastSession.feature_areas,
69
- revert_count: lastSession.revert_count,
70
- facts_recorded: lastSession.facts_recorded,
71
- branch: lastSession.branch,
72
- ended_at: lastSession.ended_at,
73
- },
74
- continuity: {
75
- hot_files: hotFiles,
76
- incomplete_hint: incompleteHint,
77
- staleness,
78
- },
79
- recalled_facts: recalledFacts,
80
- decayed_since_last_session: decayedFacts,
81
- };
82
- }
83
- catch {
84
- return null;
85
- }
86
- }
87
- // ── Internal Helpers ─────────────────────────────────────────────────
88
- /**
89
- * Compute the "hot files" — files most likely to be worked on again.
90
- * Prioritizes files that appeared most frequently in tool calls.
91
- */
92
- function computeHotFiles(session) {
93
- return session.files_modified.slice(0, 5);
94
- }
95
- /**
96
- * Generate a human-readable hint about what was incomplete.
97
- */
98
- function generateIncompleteHint(session) {
99
- const parts = [];
100
- if (session.revert_count > 0) {
101
- parts.push(`${session.revert_count} revert(s) in last session — approach may need rethinking`);
102
- }
103
- if (session.files_modified.length > 5) {
104
- parts.push(`Large change set (${session.files_modified.length} files) — verify consistency`);
105
- }
106
- const highUseTools = Object.entries(session.tools_used)
107
- .filter(([_, count]) => count > 10)
108
- .map(([tool]) => tool);
109
- if (highUseTools.length > 0) {
110
- parts.push(`Heavy usage: ${highUseTools.join(", ")}`);
111
- }
112
- if (parts.length === 0) {
113
- return session.files_modified.length > 0
114
- ? `Continuing work on ${session.files_modified.slice(0, 3).join(", ")}`
115
- : "No specific continuity context";
116
- }
117
- return parts.join(". ");
118
- }
119
- /**
120
- * Recall facts relevant to the previous session's hot files.
121
- * Returns up to 5 facts with highest effective confidence.
122
- */
123
- async function recallFactsForSession(session, factStore) {
124
- if (!factStore)
125
- return [];
126
- try {
127
- const allFacts = [];
128
- for (const file of session.files_modified.slice(0, 5)) {
129
- const facts = await factStore.recallByScope(file);
130
- for (const f of facts) {
131
- if (!allFacts.some((existing) => existing.fact_id === f.fact_id)) {
132
- allFacts.push(f);
133
- }
134
- }
135
- }
136
- const projectFacts = await factStore.recallByScope("project");
137
- for (const f of projectFacts) {
138
- if (f.fact_type === "negative" &&
139
- !allFacts.some((existing) => existing.fact_id === f.fact_id)) {
140
- allFacts.push(f);
141
- }
142
- }
143
- return allFacts
144
- .sort((a, b) => b.effective_confidence - a.effective_confidence)
145
- .slice(0, 5)
146
- .map((f) => ({
147
- fact_id: f.fact_id,
148
- type: f.fact_type,
149
- content: f.content,
150
- confidence: Math.round(f.effective_confidence * 100) / 100,
151
- source: f.source,
152
- }));
153
- }
154
- catch {
155
- return [];
156
- }
157
- }
158
- // ── Visible Resume Block ────────────────────────────────────────────
159
- /**
160
- * Format session resume payload as a visible text block that agents will read.
161
- * This is prepended to the first tool response content so it's impossible to miss.
162
- * Keeps total output under 500 chars.
163
- */
164
- export function formatSessionResumeBlock(payload) {
165
- if (!payload)
166
- return "";
167
- const parts = [];
168
- // Elapsed time
169
- const endedMs = new Date(payload.previous_session.ended_at).getTime();
170
- const elapsedMs = Date.now() - endedMs;
171
- const elapsed = formatElapsed(elapsedMs);
172
- // Hot files
173
- const hotFiles = payload.continuity.hot_files.slice(0, 3);
174
- const filesStr = hotFiles.length > 0 ? hotFiles.join(", ") : "various files";
175
- parts.push(`[unerr:session-resume] Previous session (${elapsed} ago): worked on ${filesStr}.`);
176
- // High-confidence recalled facts
177
- const importantFacts = payload.recalled_facts
178
- .filter((f) => f.confidence >= 0.5)
179
- .slice(0, 3);
180
- for (const f of importantFacts) {
181
- const pct = Math.round(f.confidence * 100);
182
- parts.push(`▸ ${f.content} (${pct}%).`);
183
- }
184
- // Decayed facts warning
185
- if (payload.decayed_since_last_session.length > 0) {
186
- const n = payload.decayed_since_last_session.length;
187
- const subjects = payload.decayed_since_last_session
188
- .slice(0, 2)
189
- .map((f) => `"${f.content.slice(0, 40)}"`);
190
- parts.push(`⚠ ${n} fact(s) expired since last session: ${subjects.join(", ")}. Use record_fact to re-record if still relevant.`);
191
- }
192
- // Incomplete hint if meaningful
193
- if (payload.continuity.incomplete_hint &&
194
- payload.continuity.incomplete_hint !== "No specific continuity context") {
195
- parts.push(`▸ ${payload.continuity.incomplete_hint}`);
196
- }
197
- const result = parts.join("\n");
198
- // Truncate to 500 chars max
199
- return result.length > 500 ? `${result.slice(0, 497)}...` : result;
200
- }
201
- function formatElapsed(ms) {
202
- const minutes = Math.floor(ms / 60000);
203
- if (minutes < 60)
204
- return `${minutes}m`;
205
- const hours = Math.floor(minutes / 60);
206
- if (hours < 24)
207
- return `${hours}h`;
208
- const days = Math.floor(hours / 24);
209
- return `${days}d`;
210
- }