@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,100 +0,0 @@
1
- /**
2
- * Cline hook adapter.
3
- *
4
- * Protocol (Cline extension hooks):
5
- * stdin: { tool: "read_file", params: { path: "..." }, event: "pre_tool" | "post_tool" }
6
- * stdout (PreToolUse):
7
- * - passthrough: { allow: true }
8
- * - nudge: { allow: true, context: "..." }
9
- * - deny: { allow: false, reason: "..." } (not used by unerr — never block)
10
- * stdout (PostToolUse):
11
- * - passthrough: {}
12
- * - enrich: { context: "..." }
13
- * stdout (UserPromptSubmit):
14
- * - passthrough: {}
15
- * - enrich: { context: "..." }
16
- *
17
- * Detection: Cline sends `tool` (lowercase, snake_case tool name) + `params` at root.
18
- */
19
- /** Map Cline tool names to normalized tool names. */
20
- const CLINE_TOOL_MAP = {
21
- read_file: "Read",
22
- write_to_file: "Write",
23
- replace_in_file: "Edit",
24
- search_files: "Grep",
25
- list_files: "Glob",
26
- execute_command: "Bash",
27
- };
28
- export const clineAdapter = {
29
- name: "cline",
30
- detect(payload) {
31
- // Cline sends `tool` (string) + `params` (object) at root
32
- // Distinguish from Claude Code (has hook_event_name) and Cursor (has toolName)
33
- return (typeof payload.tool === "string" &&
34
- typeof payload.params === "object" &&
35
- payload.params !== null &&
36
- typeof payload.hook_event_name !== "string" &&
37
- typeof payload.toolName !== "string");
38
- },
39
- normalize(payload) {
40
- const params = (payload.params ?? {});
41
- const clineTool = payload.tool;
42
- const toolName = CLINE_TOOL_MAP[clineTool] ?? clineTool;
43
- // Normalize Cline's param names to match our expectations
44
- const toolInput = { ...params };
45
- // Cline uses `path` instead of `file_path`
46
- if (toolInput.path && !toolInput.file_path) {
47
- toolInput.file_path = toolInput.path;
48
- }
49
- // Cline uses `regex` instead of `pattern`
50
- if (toolInput.regex && !toolInput.pattern) {
51
- toolInput.pattern = toolInput.regex;
52
- }
53
- // Cline uses `command` for execute_command
54
- // (already matches our expected format)
55
- // Map Cline event names
56
- let event;
57
- const clineEvent = payload.event;
58
- if (clineEvent === "pre_tool")
59
- event = "PreToolUse";
60
- else if (clineEvent === "post_tool")
61
- event = "PostToolUse";
62
- return { raw: payload, toolInput, toolName, event };
63
- },
64
- formatPreToolUse(result) {
65
- if (result.type === "passthrough") {
66
- return JSON.stringify({ allow: true });
67
- }
68
- if (result.type === "nudge" && result.message) {
69
- return JSON.stringify({
70
- allow: true,
71
- context: result.message,
72
- });
73
- }
74
- if (result.type === "rewrite" && result.updatedInput) {
75
- // Cline doesn't support input rewriting directly — allow with context
76
- return JSON.stringify({
77
- allow: true,
78
- context: "Suggested rewrite: use unerr exec for this command.",
79
- });
80
- }
81
- return JSON.stringify({ allow: true });
82
- },
83
- formatPostToolUse(result) {
84
- if (result.type === "passthrough")
85
- return "{}";
86
- if ((result.type === "enrich" || result.type === "nudge") &&
87
- result.message) {
88
- return JSON.stringify({ context: result.message });
89
- }
90
- return "{}";
91
- },
92
- formatPromptSubmit(result) {
93
- if (result.type === "passthrough")
94
- return "{}";
95
- if (result.message) {
96
- return JSON.stringify({ context: result.message });
97
- }
98
- return "{}";
99
- },
100
- };
@@ -1,98 +0,0 @@
1
- /**
2
- * Cursor hook adapter.
3
- *
4
- * Protocol (from official Cursor hooks documentation):
5
- * stdin (preToolUse): { tool_name: "Read", tool_input: { file_path: "..." }, tool_use_id: "abc", cwd: "/project", model: "..." }
6
- * stdin (postToolUse): { tool_name: "Read", tool_input: {...}, tool_output: "...", tool_use_id: "abc", cwd: "/project", duration: 1234 }
7
- * stdin (beforeSubmitPrompt): { prompt: "...", attachments: [...] }
8
- *
9
- * stdout (preToolUse):
10
- * - passthrough: { permission: "allow" }
11
- * - nudge: { permission: "allow", agent_message: "..." }
12
- * - rewrite: { permission: "allow", updated_input: { ... } }
13
- * stdout (postToolUse):
14
- * - passthrough: {}
15
- * - enrich: { additional_context: "..." }
16
- * stdout (beforeSubmitPrompt):
17
- * - always: { continue: true }
18
- *
19
- * Detection: Cursor sends `tool_name` (snake_case) + `cwd` at root level,
20
- * without `hook_event_name` (which is Claude Code's marker).
21
- */
22
- export const cursorAdapter = {
23
- name: "cursor",
24
- detect(payload) {
25
- // Cursor sends tool_name (snake_case) + cwd, without hook_event_name
26
- return (typeof payload.tool_name === "string" &&
27
- typeof payload.hook_event_name !== "string" &&
28
- typeof payload.cwd === "string");
29
- },
30
- normalize(payload) {
31
- const toolInput = (payload.tool_input ?? {});
32
- const toolName = payload.tool_name;
33
- // Cursor doesn't send explicit event type in a single field — infer from context:
34
- // postToolUse payloads include tool_output; preToolUse does not
35
- // beforeSubmitPrompt payloads include prompt
36
- let event;
37
- if (typeof payload.prompt === "string") {
38
- event = "UserPromptSubmit";
39
- }
40
- else if (typeof payload.tool_output === "string" ||
41
- payload.tool_output !== undefined) {
42
- event = "PostToolUse";
43
- }
44
- else {
45
- event = "PreToolUse";
46
- }
47
- return { raw: payload, toolInput, toolName, event };
48
- },
49
- formatPreToolUse(result) {
50
- if (result.type === "passthrough") {
51
- return JSON.stringify({ permission: "allow" });
52
- }
53
- if (result.type === "nudge" && result.message) {
54
- return JSON.stringify({
55
- permission: "allow",
56
- agent_message: result.message,
57
- });
58
- }
59
- if (result.type === "rewrite" && result.updatedInput) {
60
- return JSON.stringify({
61
- permission: "allow",
62
- updated_input: result.updatedInput,
63
- });
64
- }
65
- return JSON.stringify({ permission: "allow" });
66
- },
67
- formatPostToolUse(result) {
68
- if (result.type === "passthrough") {
69
- return "{}";
70
- }
71
- if ((result.type === "enrich" || result.type === "nudge") &&
72
- result.message) {
73
- return JSON.stringify({
74
- additional_context: result.message,
75
- });
76
- }
77
- return "{}";
78
- },
79
- formatPromptSubmit(result) {
80
- // Cursor's beforeSubmitPrompt only supports:
81
- // { continue: true|false, user_message: "..." }
82
- // user_message is shown to the USER in the client, not injected into agent context.
83
- // There is no agent_message or additionalContext field for this hook.
84
- // So we always continue — the prompt-submit nudge only works in Claude Code.
85
- if (result.type === "passthrough") {
86
- return JSON.stringify({ continue: true });
87
- }
88
- // Even with a message, we can't inject it into agent context via this hook.
89
- // The best we can do is show it to the user via user_message.
90
- if (result.message) {
91
- return JSON.stringify({
92
- continue: true,
93
- user_message: result.message,
94
- });
95
- }
96
- return JSON.stringify({ continue: true });
97
- },
98
- };
@@ -1,79 +0,0 @@
1
- /**
2
- * File-based dedup for PostToolUse hooks.
3
- *
4
- * Hooks are invoked as subprocess spawns by the agent — each call is a fresh
5
- * Node process with no in-memory state to share. To dedup a recently-emitted
6
- * reminder for the same (tool, file) pair we persist a tiny timestamp map to
7
- * `.unerr/state/hook-recent.json` and consult it on each invocation.
8
- *
9
- * Failure modes (no .unerr dir, corrupt JSON, write error) all degrade safely:
10
- * we return true (emit) so the reminder still surfaces. We never throw.
11
- */
12
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
13
- import { dirname, join } from "node:path";
14
- const STATE_FILE = join(".unerr", "state", "hook-recent.json");
15
- const DEFAULT_TTL_MS = 30_000;
16
- const PRUNE_FACTOR = 10;
17
- function readMap(file) {
18
- try {
19
- if (!existsSync(file))
20
- return {};
21
- const raw = readFileSync(file, "utf8");
22
- const obj = JSON.parse(raw);
23
- if (obj && typeof obj === "object")
24
- return obj;
25
- }
26
- catch {
27
- // fall through
28
- }
29
- return {};
30
- }
31
- function writeMap(file, map) {
32
- try {
33
- const dir = dirname(file);
34
- if (!existsSync(dir))
35
- mkdirSync(dir, { recursive: true });
36
- writeFileSync(file, JSON.stringify(map));
37
- }
38
- catch {
39
- // best-effort; if we can't persist, next call won't dedup but won't crash
40
- }
41
- }
42
- function prune(map, now, ttlMs) {
43
- const cutoff = now - ttlMs * PRUNE_FACTOR;
44
- const out = {};
45
- for (const [k, v] of Object.entries(map)) {
46
- if (typeof v === "number" && v >= cutoff)
47
- out[k] = v;
48
- }
49
- return out;
50
- }
51
- /**
52
- * Returns true if the caller should emit the reminder for `key` now, false if
53
- * it was already emitted within `ttlMs`. On first emission the timestamp is
54
- * persisted; subsequent calls within TTL return false.
55
- *
56
- * @param key stable identifier — `${tool}:${absPath}` is typical
57
- * @param ttlMs dedup window; default 30s
58
- */
59
- export function shouldEmitOnce(key, ttlMs = DEFAULT_TTL_MS) {
60
- const now = Date.now();
61
- const map = readMap(STATE_FILE);
62
- const last = map[key];
63
- if (typeof last === "number" && now - last < ttlMs) {
64
- return false;
65
- }
66
- map[key] = now;
67
- writeMap(STATE_FILE, prune(map, now, ttlMs));
68
- return true;
69
- }
70
- /** Test helper — clears the on-disk dedup file. */
71
- export function resetHookDedup() {
72
- try {
73
- if (existsSync(STATE_FILE))
74
- writeFileSync(STATE_FILE, "{}");
75
- }
76
- catch {
77
- // ignore
78
- }
79
- }
@@ -1,113 +0,0 @@
1
- /**
2
- * Universal Hook Runner — agent-agnostic hook orchestrator.
3
- *
4
- * Detects the calling agent from stdin JSON shape, routes through the
5
- * appropriate adapter, and returns the response in the agent's expected format.
6
- *
7
- * Adapter protocol:
8
- * 1. Parse stdin JSON
9
- * 2. Try each adapter's `detect()` until one matches
10
- * 3. Extract normalized tool input via adapter
11
- * 4. Run the hook handler (returns agent-agnostic HookResult)
12
- * 5. Format the response via adapter
13
- *
14
- * Supported agents: Claude Code, Cursor, Cline.
15
- * Fallback: Claude Code (most common MCP hook consumer).
16
- */
17
- import { claudeCodeAdapter } from "./adapters/claude-code.js";
18
- import { clineAdapter } from "./adapters/cline.js";
19
- import { cursorAdapter } from "./adapters/cursor.js";
20
- // ── Adapter Registry ─────────────────────────────────────────────────
21
- /**
22
- * Ordered list of adapters. More specific detectors first.
23
- * Claude Code is last (default fallback).
24
- */
25
- const ADAPTERS = [
26
- cursorAdapter,
27
- clineAdapter,
28
- claudeCodeAdapter, // default fallback
29
- ];
30
- // ── Runner ───────────────────────────────────────────────────────────
31
- /**
32
- * Detect the appropriate adapter for a parsed stdin payload.
33
- * Returns Claude Code adapter as fallback (most common).
34
- */
35
- export function detectAdapter(payload) {
36
- for (const adapter of ADAPTERS) {
37
- if (adapter.detect(payload))
38
- return adapter;
39
- }
40
- return claudeCodeAdapter;
41
- }
42
- /** Parse stdin JSON, returning null on failure. */
43
- function parseStdin(stdinJson) {
44
- const trimmed = stdinJson.trim();
45
- if (!trimmed)
46
- return null;
47
- try {
48
- return JSON.parse(trimmed);
49
- }
50
- catch {
51
- return null;
52
- }
53
- }
54
- /**
55
- * Run a PreToolUse hook through the universal runner.
56
- *
57
- * @param stdinJson — raw stdin from the hook process
58
- * @param handler — agent-agnostic handler that produces a HookResult
59
- * @returns JSON string for stdout
60
- */
61
- export function runPreToolUseHook(stdinJson, handler) {
62
- const payload = parseStdin(stdinJson);
63
- if (!payload)
64
- return "{}";
65
- const adapter = detectAdapter(payload);
66
- const normalized = adapter.normalize(payload);
67
- normalized.agentName = adapter.name;
68
- const result = handler(normalized);
69
- return adapter.formatPreToolUse(result);
70
- }
71
- /**
72
- * Run a PostToolUse hook through the universal runner.
73
- */
74
- export function runPostToolUseHook(stdinJson, handler) {
75
- const payload = parseStdin(stdinJson);
76
- if (!payload)
77
- return "{}";
78
- const adapter = detectAdapter(payload);
79
- const normalized = adapter.normalize(payload);
80
- normalized.agentName = adapter.name;
81
- const result = handler(normalized);
82
- return adapter.formatPostToolUse(result);
83
- }
84
- /**
85
- * Run a UserPromptSubmit hook through the universal runner.
86
- */
87
- export function runPromptSubmitHook(stdinJson, handler) {
88
- const payload = parseStdin(stdinJson);
89
- if (!payload)
90
- return "{}";
91
- const adapter = detectAdapter(payload);
92
- const normalized = adapter.normalize(payload);
93
- normalized.agentName = adapter.name;
94
- const result = handler(normalized);
95
- return adapter.formatPromptSubmit(result);
96
- }
97
- // ── Convenience Constructors ─────────────────────────────────────────
98
- /** Create a passthrough result. */
99
- export function passthrough() {
100
- return { type: "passthrough" };
101
- }
102
- /** Create a nudge (advisory systemMessage) result. */
103
- export function nudge(message) {
104
- return { type: "nudge", message };
105
- }
106
- /** Create a rewrite (updatedInput) result. */
107
- export function rewrite(updatedInput) {
108
- return { type: "rewrite", updatedInput };
109
- }
110
- /** Create an enrich (additionalContext) result. */
111
- export function enrich(message) {
112
- return { type: "enrich", message };
113
- }
@@ -1,175 +0,0 @@
1
- /**
2
- * PreToolUse + PostToolUse hooks for Read/Grep/Glob/Write/Edit.
3
- *
4
- * Uses the universal hook runner for multi-agent protocol support.
5
- * Handlers return agent-agnostic HookResults; adapters format for each agent.
6
- *
7
- * Design: NEVER block — always allow, only advise or enrich.
8
- */
9
- import { shouldEmitOnce } from "./hook-dedup.js";
10
- import { enrich, nudge, passthrough, runPostToolUseHook, runPreToolUseHook, } from "./hook-runner.js";
11
- // ── Helper ───────────────────────────────────────────────────────────
12
- /** Extract file path from normalized tool input. */
13
- function extractFilePath(input) {
14
- const fp = (input.file_path ?? input.path ?? input.filePath);
15
- return typeof fp === "string" && fp.length > 0 ? fp : undefined;
16
- }
17
- /** Check if file is a code file (not config/docs/binary). */
18
- function isCodeFile(filePath) {
19
- return !/\.(md|json|ya?ml|toml|txt|lock|css|html|svg|png|jpg|pdf)$/i.test(filePath);
20
- }
21
- // ── PreToolUse Handlers (agent-agnostic) ─────────────────────────────
22
- const preReadHandler = (normalized) => {
23
- const input = normalized.toolInput;
24
- const filePath = extractFilePath(input);
25
- if (!filePath)
26
- return passthrough();
27
- const isClaudeCode = normalized.agentName === "claude-code";
28
- const hasOffset = input.offset !== undefined;
29
- const hasLimit = input.limit !== undefined;
30
- const isTargeted = hasOffset || hasLimit;
31
- // Claude Code: targeted Read (offset/limit) before Edit is the correct workflow — allow silently.
32
- // Non-Claude Code agents: built-in Read is never needed, always nudge toward file_read.
33
- if (isClaudeCode && isTargeted) {
34
- return passthrough();
35
- }
36
- if (isClaudeCode) {
37
- // Full-file Read in Claude Code — nudge to use offset/limit for the Edit workflow
38
- return nudge(`READ ROUTING: Built-in Read is ONLY for the Edit workflow (Read → Edit). Use offset/limit to read only the lines you plan to edit — do NOT read the entire file.\nFor all other reading, use unerr tools instead:\n- Reading to understand: \`file_read({ file_path: "${filePath}" })\`\n- File structure: \`file_outline("${filePath}")\`\n- Specific function: \`get_entity\` or \`file_read\` with \`entity\` param`);
39
- }
40
- // Non-Claude Code agents: always nudge toward file_read
41
- return nudge(`Use unerr tools instead of built-in Read:\n- \`file_read({ file_path: "${filePath}" })\` — auto-injects conventions, facts, drift status\n- \`file_outline("${filePath}")\` — file structure overview\n- \`get_entity\` or \`file_read\` with \`entity\` param — specific function/class`);
42
- };
43
- const preGrepHandler = (normalized) => {
44
- const input = normalized.toolInput;
45
- const pattern = (input.pattern ?? input.regex ?? input.query);
46
- if (typeof pattern !== "string" || pattern.length === 0)
47
- return passthrough();
48
- const looksLikeFunctionSearch = /^[a-zA-Z_]\w*$/.test(pattern);
49
- const looksLikeImportSearch = /import|require|from\s/.test(pattern);
50
- if (looksLikeFunctionSearch) {
51
- return nudge(`STOP: You MUST use unerr graph tools instead of Grep for "${pattern}".\n- \`search_code("${pattern}")\` — finds ALL matching functions/classes/types in <5ms (zero false positives)\n- \`get_references("${pattern}")\` — finds all callers including indirect references\nText grep matches comments, strings, and unrelated code. Graph tools are precise and faster.`);
52
- }
53
- if (looksLikeImportSearch) {
54
- return nudge("STOP: Use `get_imports` instead of Grep for import/dependency tracing. It returns structured import maps in <5ms — more reliable than grepping for import statements.");
55
- }
56
- return nudge("REQUIRED: This project has unerr graph tools indexed. Use `search_code` for code entity searches (faster, more accurate than Grep). Use `get_references` for finding callers.");
57
- };
58
- const preGlobHandler = (normalized) => {
59
- const input = normalized.toolInput;
60
- const pattern = (input.pattern ?? input.glob ?? input.path);
61
- if (typeof pattern !== "string" || pattern.length === 0)
62
- return passthrough();
63
- return nudge(`STOP: You MUST use unerr graph tools instead of Glob.\n- \`search_code("${pattern}")\` — finds ALL matching functions/classes/types across the entire codebase in <5ms\n- \`file_outline\` — get file structure without reading entire files\n- \`get_file\` — get all entities in a file with structured metadata\nGlob+Grep is a multi-step pattern that wastes tokens. search_code does it in one call.`);
64
- };
65
- const preWriteHandler = (normalized) => {
66
- const filePath = extractFilePath(normalized.toolInput);
67
- if (!filePath || !isCodeFile(filePath))
68
- return passthrough();
69
- return nudge(`Before writing "${filePath}", check for unintended side effects:\n- \`get_references\` on any functions you're modifying — ensure callers still work after your changes\n- \`file_connections("${filePath}")\` — see all files that depend on this one\n- \`get_test_coverage\` on modified entities — know which tests to run`);
70
- };
71
- const preEditHandler = (normalized) => {
72
- const input = normalized.toolInput;
73
- const filePath = extractFilePath(input);
74
- if (!filePath || !isCodeFile(filePath))
75
- return passthrough();
76
- const isClaudeCode = normalized.agentName === "claude-code";
77
- // Read prerequisite warning — Claude Code only (other agents don't require built-in Read before Edit)
78
- const readPrereq = isClaudeCode
79
- ? `CRITICAL: Edit REQUIRES built-in Read to have been called on "${filePath}" first. file_read (MCP) does NOT satisfy this — the Edit tool will fail with "File has not been read yet". If you haven't called built-in Read (with offset/limit on the target lines) on this file, do so now before attempting Edit.\n\n`
80
- : "";
81
- const oldStr = input.old_string;
82
- const hasSignatureChange = oldStr &&
83
- /^(export\s+)?(async\s+)?function\s+\w+|^(export\s+)?class\s+\w+/.test(oldStr.trim());
84
- if (hasSignatureChange) {
85
- return nudge(`${readPrereq}You're editing a function/class signature in "${filePath}". This may break callers.\n- \`get_references\` on the entity you're modifying — all callers must be updated to match\n- \`get_test_coverage\` on the entity — verify which tests cover it`);
86
- }
87
- return nudge(`${readPrereq}Before editing "${filePath}":\n- \`get_references\` on any entity you're changing — ensure callers won't break`);
88
- };
89
- // ── PostToolUse Handlers (agent-agnostic) ────────────────────────────
90
- const postReadHandler = (normalized) => {
91
- const input = normalized.toolInput;
92
- const filePath = extractFilePath(input);
93
- if (!filePath || !isCodeFile(filePath))
94
- return passthrough();
95
- if (!shouldEmitOnce(`Read:${filePath}`))
96
- return passthrough();
97
- const isClaudeCode = normalized.agentName === "claude-code";
98
- if (isClaudeCode) {
99
- return enrich("ur|hnt Edit needs built-in Read first; for understanding use `file_read` (auto-injects facts/drift).");
100
- }
101
- return enrich("ur|hnt Prefer `file_read` over built-in Read — it auto-injects conventions, facts, drift.");
102
- };
103
- const postGrepHandler = (normalized) => {
104
- const input = normalized.toolInput;
105
- const pattern = (input.pattern ?? input.regex ?? input.query);
106
- if (typeof pattern !== "string" || pattern.length === 0)
107
- return passthrough();
108
- const looksLikeFunctionSearch = /^[a-zA-Z_]\w*$/.test(pattern);
109
- if (looksLikeFunctionSearch) {
110
- return enrich(`You just grepped for "${pattern}". For structured results, try:\n` +
111
- `- \`get_references "${pattern}"\` — finds ALL callers including indirect references (no false positives from comments/strings)\n` +
112
- `- \`get_entity "${pattern}"\` — returns the entity with its full signature, body, and metadata\n` +
113
- `- \`search_code "${pattern}"\` — ranked results across the entire codebase in <5ms`);
114
- }
115
- return enrich("You just grepped for a pattern. For structured code navigation, unerr graph tools are faster and more accurate:\n" +
116
- "- `search_code` for entity-level search (functions, classes, types)\n" +
117
- "- `get_references` for reference tracing (no false positives)");
118
- };
119
- const postGlobHandler = () => {
120
- return enrich("You just found files via Glob. For efficient exploration of matched files:\n" +
121
- "- `file_outline` on each file — see all entities without reading full contents (<5ms)\n" +
122
- "- `search_code` — search for specific entities across all matched files in one call\n" +
123
- `- \`get_file\` — get a structured summary of any file's entities, imports, and exports`);
124
- };
125
- const postWriteHandler = (normalized) => {
126
- const filePath = extractFilePath(normalized.toolInput);
127
- if (!filePath || !isCodeFile(filePath))
128
- return passthrough();
129
- if (!shouldEmitOnce(`Write:${filePath}`))
130
- return passthrough();
131
- // Table row #27 TRIM — slash-command fragments replaced with "why".
132
- return enrich(`ur|hnt Wrote ${filePath} — get_references on exports to check blast radius`);
133
- };
134
- const postEditHandler = (normalized) => {
135
- const filePath = extractFilePath(normalized.toolInput);
136
- if (!filePath || !isCodeFile(filePath))
137
- return passthrough();
138
- if (!shouldEmitOnce(`Edit:${filePath}`))
139
- return passthrough();
140
- // Table row #28 TRIM — drop slash-command fragments, keep concrete next call.
141
- return enrich(`ur|hnt Edited ${filePath} — get_references to check callers of changed entities`);
142
- };
143
- // ── Public API ───────────────────────────────────────────────────────
144
- // These maintain the same function signatures for backward compatibility
145
- // with hook.ts CLI commands.
146
- export function runPreReadHook(stdinJson) {
147
- return runPreToolUseHook(stdinJson, preReadHandler);
148
- }
149
- export function runPreGrepHook(stdinJson) {
150
- return runPreToolUseHook(stdinJson, preGrepHandler);
151
- }
152
- export function runPreGlobHook(stdinJson) {
153
- return runPreToolUseHook(stdinJson, preGlobHandler);
154
- }
155
- export function runPreWriteHook(stdinJson) {
156
- return runPreToolUseHook(stdinJson, preWriteHandler);
157
- }
158
- export function runPreEditHook(stdinJson) {
159
- return runPreToolUseHook(stdinJson, preEditHandler);
160
- }
161
- export function runPostReadHook(stdinJson) {
162
- return runPostToolUseHook(stdinJson, postReadHandler);
163
- }
164
- export function runPostGrepHook(stdinJson) {
165
- return runPostToolUseHook(stdinJson, postGrepHandler);
166
- }
167
- export function runPostGlobHook(stdinJson) {
168
- return runPostToolUseHook(stdinJson, postGlobHandler);
169
- }
170
- export function runPostWriteHook(stdinJson) {
171
- return runPostToolUseHook(stdinJson, postWriteHandler);
172
- }
173
- export function runPostEditHook(stdinJson) {
174
- return runPostToolUseHook(stdinJson, postEditHandler);
175
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * UserPromptSubmit hook — appends a concise unerr tool reminder to user prompt context.
3
- *
4
- * Uses the universal hook runner for multi-agent protocol support.
5
- * Fires on every user message. Appends ~50 tokens of context reminding the agent
6
- * that unerr graph tools are available.
7
- */
8
- import { readNudgeState, updateNudgeState } from "../proxy/nudge-state.js";
9
- import { enrich, passthrough, runPromptSubmitHook, } from "./hook-runner.js";
10
- /**
11
- * Agent-agnostic prompt submit handler.
12
- * Injects a brief tool-preference reminder into user prompt context.
13
- *
14
- * Nudge v2 (N1): when UNERR_NUDGE_V2=1, only fire on the first non-trivial
15
- * prompt of a session, then go silent until the next session. v1 default
16
- * behavior (fire on every prompt ≥10 chars) is preserved.
17
- */
18
- const promptSubmitHandler = (normalized) => {
19
- // Extract the user's message to detect if it's code-related
20
- const raw = normalized.raw;
21
- const message = (raw.user_message ?? raw.prompt ?? "");
22
- // Skip for very short messages (likely confirmations like "yes", "ok", "continue")
23
- if (message.length < 10)
24
- return passthrough();
25
- // N1 — Tier 0 one-shot session onboarding (opt-in)
26
- if (process.env.UNERR_NUDGE_V2 === "1") {
27
- try {
28
- const cwd = process.cwd();
29
- const state = readNudgeState(cwd);
30
- if (state.tier0_emitted)
31
- return passthrough();
32
- updateNudgeState(cwd, (s) => {
33
- s.tier0_emitted = true;
34
- });
35
- }
36
- catch {
37
- // State unavailable — fall through to v1 behaviour
38
- }
39
- }
40
- // Detect code-related intent for stronger nudge
41
- const isCodeTask = /\b(fix|bug|add|implement|refactor|debug|update|change|modify|create|delete|remove|test|find|search|where|who calls|callers|dependencies|import)\b/i.test(message);
42
- // Table rows #24/#25 TRIM — why (faster/graph-backed/project-aware) leads,
43
- // then what (tool roster + when-tags for markers). Half the bytes, same signal.
44
- if (isCodeTask) {
45
- return enrich("[unerr] Prefer unerr MCP tools for code work (faster, graph-backed, project-aware): " +
46
- "`search_code` (NOT grep/glob) · `get_references` (NOT grep for fn names) · " +
47
- "`file_read` (NOT built-in Read for understanding; built-in Read is only for pre-Edit) · " +
48
- "`file_outline` · `get_entity`. " +
49
- "Mark progress: `mark_intent` (task start) · `mark_decision` · `mark_blocker` · " +
50
- "`mark_resolution` — these power the cross-session timeline.");
51
- }
52
- return enrich("[unerr] Prefer unerr MCP tools (graph-backed, <5ms): " +
53
- "`search_code` · `get_references` · `file_read` · `file_outline` · `get_entity`. " +
54
- "Drop `mark_intent` / `mark_decision` / `mark_blocker` / `mark_resolution` as you work — " +
55
- "they keep the timeline coherent across sessions.");
56
- };
57
- /**
58
- * UserPromptSubmit hook handler.
59
- * Returns JSON string for stdout.
60
- */
61
- export function runUserPromptSubmitHook(stdinJson) {
62
- return runPromptSubmitHook(stdinJson, promptSubmitHandler);
63
- }
@@ -1,47 +0,0 @@
1
- /**
2
- * Layer 6 Sprint FE-D — PreToolUse stdin/stdout bridge for Bash -> `unerr exec`.
3
- *
4
- * Uses the universal hook runner for multi-agent protocol support.
5
- * Claude Code, Cursor, and Cline all route through the same handler;
6
- * the adapter layer handles protocol differences.
7
- *
8
- * Commands are base64-encoded (`--b64`) to avoid shell re-parsing issues
9
- * with multi-line commands, nested quotes, and special characters.
10
- */
11
- import { normalizeShellCommand } from "../proxy/shell-classifier.js";
12
- import { passthrough, rewrite, runPreToolUseHook, } from "./hook-runner.js";
13
- /**
14
- * Agent-agnostic Bash rewrite handler.
15
- * Rewrites shell commands to route through `unerr exec --b64 <base64>`.
16
- * Base64-encoding preserves quoting, newlines, and special characters
17
- * that would otherwise break when embedded in a shell command line.
18
- */
19
- const preBashHandler = (normalized) => {
20
- const input = normalized.toolInput;
21
- const cmd = typeof input.command === "string"
22
- ? input.command
23
- : typeof input.shell_command === "string"
24
- ? input.shell_command
25
- : "";
26
- if (!normalizeShellCommand(cmd))
27
- return passthrough();
28
- const n = normalizeShellCommand(cmd);
29
- if (n.startsWith("unerr exec") || n.startsWith("npx unerr exec")) {
30
- return passthrough();
31
- }
32
- // Base64-encode commands that contain characters unsafe for shell embedding:
33
- // newlines, quotes, parentheses, backticks, $, {, }, etc.
34
- // Simple commands (ls, git status) pass through as plain argv for readability.
35
- if (/[\n"'`(){}$\\;<>|&!~*?]/.test(cmd)) {
36
- const b64 = Buffer.from(cmd, "utf-8").toString("base64");
37
- return rewrite({ command: `unerr exec --b64 ${b64}` });
38
- }
39
- return rewrite({ command: `unerr exec -- ${cmd}` });
40
- };
41
- /**
42
- * Read hook JSON from stdin, rewrite Bash command to run through `unerr exec`.
43
- * Returns JSON string for stdout, or passthrough response.
44
- */
45
- export function runPreBashHook(stdinJson) {
46
- return runPreToolUseHook(stdinJson, preBashHandler);
47
- }