@unerr-ai/unerr 0.2.1 → 0.2.3
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.
- package/README.md +36 -45
- package/dist/cli.js +37443 -36022
- package/package.json +2 -1
- package/dist/behaviors/agent-llm-bridge.js +0 -166
- package/dist/behaviors/architecture-guard.js +0 -256
- package/dist/behaviors/auto-doc.js +0 -247
- package/dist/behaviors/cascade-guard.js +0 -289
- package/dist/behaviors/change-narrative.js +0 -270
- package/dist/behaviors/convention-drift.js +0 -290
- package/dist/behaviors/framework.js +0 -235
- package/dist/behaviors/guard-formatter.js +0 -44
- package/dist/behaviors/incomplete-work.js +0 -270
- package/dist/behaviors/loop-breaker.js +0 -300
- package/dist/behaviors/session-continuity.js +0 -208
- package/dist/commands/branches.js +0 -97
- package/dist/commands/check-commit.js +0 -225
- package/dist/commands/compress-output.js +0 -64
- package/dist/commands/config-verify.js +0 -243
- package/dist/commands/daemon.js +0 -905
- package/dist/commands/dashboard.js +0 -52
- package/dist/commands/debug.js +0 -200
- package/dist/commands/enrich.js +0 -184
- package/dist/commands/exec.js +0 -233
- package/dist/commands/gain.js +0 -156
- package/dist/commands/hook.js +0 -88
- package/dist/commands/index.js +0 -88
- package/dist/commands/init.js +0 -74
- package/dist/commands/install.js +0 -505
- package/dist/commands/learn.js +0 -116
- package/dist/commands/manifest.js +0 -193
- package/dist/commands/rewind.js +0 -103
- package/dist/commands/serve.js +0 -19
- package/dist/commands/setup-wizard.js +0 -414
- package/dist/commands/skills.js +0 -64
- package/dist/commands/stats.js +0 -20
- package/dist/commands/status.js +0 -654
- package/dist/commands/timeline.js +0 -139
- package/dist/commands/uninstall.js +0 -230
- package/dist/components/App.js +0 -109
- package/dist/components/Banner.js +0 -12
- package/dist/components/ConfirmPrompt.js +0 -25
- package/dist/components/DriftSummary.js +0 -23
- package/dist/components/GradeBadge.js +0 -15
- package/dist/components/HealthCard.js +0 -18
- package/dist/components/InkSpinner.js +0 -22
- package/dist/components/InputBox.js +0 -17
- package/dist/components/KeyValue.js +0 -13
- package/dist/components/MessageList.js +0 -14
- package/dist/components/ProgressBar.js +0 -26
- package/dist/components/Section.js +0 -16
- package/dist/components/SessionSummaryCard.js +0 -73
- package/dist/components/StartupDisplay.js +0 -24
- package/dist/components/StatusDashboard.js +0 -57
- package/dist/components/StatusLine.js +0 -8
- package/dist/components/StepLine.js +0 -22
- package/dist/components/Theme.js +0 -20
- package/dist/components/ToolProgress.js +0 -8
- package/dist/components/ViolationList.js +0 -21
- package/dist/components/render.js +0 -13
- package/dist/config/agent-registry.js +0 -237
- package/dist/config/claude-settings-hooks.js +0 -304
- package/dist/config/hook-installer.js +0 -65
- package/dist/config/instruction-writer.js +0 -388
- package/dist/config/mcp-config-writer.js +0 -266
- package/dist/config/settings.js +0 -174
- package/dist/config/tool-detector.js +0 -42
- package/dist/config/value-surfacing.js +0 -119
- package/dist/core/context-assembly.js +0 -108
- package/dist/core/conversation.js +0 -33
- package/dist/core/local-chat-provider.js +0 -475
- package/dist/core/provider-factory.js +0 -55
- package/dist/core/providers.js +0 -90
- package/dist/core/query-engine.js +0 -174
- package/dist/daemon/api.js +0 -312
- package/dist/daemon/autostart.js +0 -119
- package/dist/daemon/bootstrap.js +0 -39
- package/dist/daemon/client.js +0 -164
- package/dist/daemon/detect-ci.js +0 -81
- package/dist/daemon/platform-linux.js +0 -146
- package/dist/daemon/platform-macos.js +0 -134
- package/dist/daemon/platform-windows.js +0 -116
- package/dist/daemon/process-manager.js +0 -299
- package/dist/daemon/protocol.js +0 -23
- package/dist/daemon/registry.js +0 -270
- package/dist/daemon/settings-schema.js +0 -72
- package/dist/daemon/system-health.js +0 -134
- package/dist/daemon/version-checker.js +0 -262
- package/dist/daemon/warm-start.js +0 -223
- package/dist/entrypoints/cli.js +0 -1043
- package/dist/entrypoints/daemon.js +0 -380
- package/dist/entrypoints/repl.js +0 -147
- package/dist/hooks/adapters/claude-code.js +0 -90
- package/dist/hooks/adapters/cline.js +0 -100
- package/dist/hooks/adapters/cursor.js +0 -98
- package/dist/hooks/hook-dedup.js +0 -79
- package/dist/hooks/hook-runner.js +0 -113
- package/dist/hooks/navigation-hooks.js +0 -175
- package/dist/hooks/prompt-hooks.js +0 -63
- package/dist/hooks/shell-hooks.js +0 -47
- package/dist/ignore.js +0 -111
- package/dist/intelligence/approach-suggester.js +0 -61
- package/dist/intelligence/ast-extractor.js +0 -2615
- package/dist/intelligence/ast-worker.js +0 -34
- package/dist/intelligence/background-indexer.js +0 -121
- package/dist/intelligence/blast-radius.js +0 -200
- package/dist/intelligence/community-detection.js +0 -691
- package/dist/intelligence/community-detector.js +0 -184
- package/dist/intelligence/computation-scheduler.js +0 -75
- package/dist/intelligence/confidence-propagation.js +0 -47
- package/dist/intelligence/convention-detector.js +0 -242
- package/dist/intelligence/convention-learner.js +0 -205
- package/dist/intelligence/convention-matcher.js +0 -205
- package/dist/intelligence/cozo-schema.js +0 -376
- package/dist/intelligence/decision-point-detector.js +0 -90
- package/dist/intelligence/deep-dive-tools.js +0 -586
- package/dist/intelligence/durability-scorer.js +0 -84
- package/dist/intelligence/exploration-cost.js +0 -204
- package/dist/intelligence/exploration-pattern-tracker.js +0 -61
- package/dist/intelligence/fact-generator.js +0 -322
- package/dist/intelligence/facts-schema.js +0 -90
- package/dist/intelligence/file-intelligence.js +0 -59
- package/dist/intelligence/graph-holder.js +0 -220
- package/dist/intelligence/graph-temporal-joiner.js +0 -238
- package/dist/intelligence/health-grade.js +0 -423
- package/dist/intelligence/health-grader.js +0 -200
- package/dist/intelligence/health-map-data.js +0 -259
- package/dist/intelligence/import-symbols.js +0 -136
- package/dist/intelligence/incremental-indexer.js +0 -658
- package/dist/intelligence/indexer/centrality.js +0 -62
- package/dist/intelligence/indexer/cfg-context.js +0 -95
- package/dist/intelligence/indexer/confidence.js +0 -34
- package/dist/intelligence/indexer/cross-file-resolver.js +0 -104
- package/dist/intelligence/indexer/edge-repair.js +0 -89
- package/dist/intelligence/indexer/entity-key.js +0 -17
- package/dist/intelligence/indexer/export-map.js +0 -132
- package/dist/intelligence/indexer/git-cochange.js +0 -128
- package/dist/intelligence/indexer/graph-patch.js +0 -147
- package/dist/intelligence/indexer/incremental.js +0 -78
- package/dist/intelligence/indexer/ingest.js +0 -160
- package/dist/intelligence/indexer/language-detect.js +0 -226
- package/dist/intelligence/indexer/metadata.js +0 -63
- package/dist/intelligence/indexer/mutation-tracker.js +0 -79
- package/dist/intelligence/indexer/orchestrator.js +0 -155
- package/dist/intelligence/indexer/plugin-interface.js +0 -31
- package/dist/intelligence/indexer/plugins/csharp.js +0 -440
- package/dist/intelligence/indexer/plugins/go.js +0 -335
- package/dist/intelligence/indexer/plugins/java.js +0 -370
- package/dist/intelligence/indexer/plugins/python.js +0 -358
- package/dist/intelligence/indexer/plugins/regex-fallback.js +0 -82
- package/dist/intelligence/indexer/plugins/ruby.js +0 -290
- package/dist/intelligence/indexer/plugins/rust.js +0 -484
- package/dist/intelligence/indexer/plugins/tier2-generic.js +0 -310
- package/dist/intelligence/indexer/plugins/typescript.js +0 -456
- package/dist/intelligence/indexer/resource-monitor.js +0 -93
- package/dist/intelligence/indexer/scip/decoder.js +0 -253
- package/dist/intelligence/indexer/scip/detector.js +0 -232
- package/dist/intelligence/indexer/scip/downloader.js +0 -427
- package/dist/intelligence/indexer/scip/fallback.js +0 -34
- package/dist/intelligence/indexer/scip/merger.js +0 -109
- package/dist/intelligence/indexer/scip/orchestrator.js +0 -433
- package/dist/intelligence/indexer/scip/runner.js +0 -98
- package/dist/intelligence/indexer/snapshot.js +0 -66
- package/dist/intelligence/indexer/test-detector.js +0 -196
- package/dist/intelligence/indexer/watch-integration.js +0 -61
- package/dist/intelligence/indexer/worker.js +0 -85
- package/dist/intelligence/local-convention-detector.js +0 -437
- package/dist/intelligence/local-embeddings.js +0 -190
- package/dist/intelligence/local-graph.js +0 -1946
- package/dist/intelligence/local-indexer.js +0 -1575
- package/dist/intelligence/local-llm.js +0 -163
- package/dist/intelligence/local-rule-generator.js +0 -154
- package/dist/intelligence/local-snapshot.js +0 -213
- package/dist/intelligence/negative-knowledge.js +0 -103
- package/dist/intelligence/persistent-db.js +0 -85
- package/dist/intelligence/query-router.js +0 -2556
- package/dist/intelligence/risk-classifier.js +0 -116
- package/dist/intelligence/rule-evaluator.js +0 -380
- package/dist/intelligence/rule-generator.js +0 -49
- package/dist/intelligence/search-index.js +0 -173
- package/dist/intelligence/semantic/docstring-extractor.js +0 -67
- package/dist/intelligence/semantic/embedding-store.js +0 -52
- package/dist/intelligence/semantic/enrichment-orchestrator.js +0 -48
- package/dist/intelligence/semantic/git-message-miner.js +0 -114
- package/dist/intelligence/semantic/identifier-tokenizer.js +0 -51
- package/dist/intelligence/semantic/node2vec-embeddings.js +0 -71
- package/dist/intelligence/semantic/node2vec-walks.js +0 -103
- package/dist/intelligence/semantic/path-domain-inference.js +0 -112
- package/dist/intelligence/semantic/similarity-engine.js +0 -60
- package/dist/intelligence/semantic/tfidf-vectors.js +0 -88
- package/dist/intelligence/session-brief-builder.js +0 -159
- package/dist/intelligence/session-context.js +0 -221
- package/dist/intelligence/session-health-monitor.js +0 -211
- package/dist/intelligence/session-narrative.js +0 -197
- package/dist/intelligence/session-pattern-analyzer.js +0 -218
- package/dist/intelligence/signal-scorer.js +0 -390
- package/dist/intelligence/signal-show-store.js +0 -182
- package/dist/intelligence/smart-truncate.js +0 -158
- package/dist/intelligence/subgraph-cache.js +0 -88
- package/dist/intelligence/temporal-facts.js +0 -494
- package/dist/intelligence/token-estimator.js +0 -100
- package/dist/intelligence/tool-injector.js +0 -87
- package/dist/intelligence/tree-sitter-loader.js +0 -71
- package/dist/intelligence/worker-pool.js +0 -116
- package/dist/proxy/arg-validator.js +0 -79
- package/dist/proxy/auto-bootstrap.js +0 -167
- package/dist/proxy/bridge.js +0 -147
- package/dist/proxy/budget-enforcer.js +0 -70
- package/dist/proxy/compression-quality-monitor.js +0 -160
- package/dist/proxy/compression-stats.js +0 -51
- package/dist/proxy/context-rot-detector.js +0 -137
- package/dist/proxy/drift-detector.js +0 -139
- package/dist/proxy/efficiency-tracker.js +0 -79
- package/dist/proxy/fact-ranking.js +0 -154
- package/dist/proxy/format-encoder.js +0 -266
- package/dist/proxy/http-transport.js +0 -90
- package/dist/proxy/lifecycle-actor.js +0 -55
- package/dist/proxy/lifecycle-machine.js +0 -187
- package/dist/proxy/log-tailer.js +0 -265
- package/dist/proxy/model-pricing.js +0 -98
- package/dist/proxy/network-firewall.js +0 -141
- package/dist/proxy/nudge-state.js +0 -93
- package/dist/proxy/output-compressor.js +0 -185
- package/dist/proxy/pid-lock.js +0 -291
- package/dist/proxy/proxy-context.js +0 -11
- package/dist/proxy/proxy.js +0 -2633
- package/dist/proxy/response-enrichment.js +0 -32
- package/dist/proxy/response-envelope.js +0 -313
- package/dist/proxy/session-dedup.js +0 -82
- package/dist/proxy/session-legend.js +0 -30
- package/dist/proxy/session-persistence.js +0 -210
- package/dist/proxy/session-resume.js +0 -94
- package/dist/proxy/session-stats.js +0 -513
- package/dist/proxy/shell-classifier.js +0 -1346
- package/dist/proxy/shell-compression-log.js +0 -93
- package/dist/proxy/shell-compressor.js +0 -390
- package/dist/proxy/shell-graph-boost.js +0 -202
- package/dist/proxy/shell-monitor-map.js +0 -18
- package/dist/proxy/shell-stats.js +0 -54
- package/dist/proxy/shell-strategies/cloud.js +0 -215
- package/dist/proxy/shell-strategies/diff.js +0 -159
- package/dist/proxy/shell-strategies/error-diagnostic.js +0 -796
- package/dist/proxy/shell-strategies/filter-dsl.js +0 -358
- package/dist/proxy/shell-strategies/git-status.js +0 -177
- package/dist/proxy/shell-strategies/key-value.js +0 -193
- package/dist/proxy/shell-strategies/log-text.js +0 -154
- package/dist/proxy/shell-strategies/omni.js +0 -188
- package/dist/proxy/shell-strategies/progress.js +0 -55
- package/dist/proxy/shell-strategies/redact.js +0 -76
- package/dist/proxy/shell-strategies/structured.js +0 -241
- package/dist/proxy/shell-strategies/tabular.js +0 -243
- package/dist/proxy/shell-strategies/test-results-types.js +0 -13
- package/dist/proxy/shell-strategies/test-results.js +0 -784
- package/dist/proxy/shell-strategies/tree-paths.js +0 -144
- package/dist/proxy/shell-strategies/yaml.js +0 -182
- package/dist/proxy/shell-tee.js +0 -111
- package/dist/proxy/signal-dedup.js +0 -171
- package/dist/proxy/startup-renderer.js +0 -158
- package/dist/proxy/task-token-display.js +0 -38
- package/dist/proxy/token-counter.js +0 -61
- package/dist/proxy/tool-clusters.js +0 -273
- package/dist/proxy/tool-definitions.js +0 -525
- package/dist/proxy/transport-mux.js +0 -229
- package/dist/proxy/wire-cap.js +0 -268
- package/dist/rules/developer.mozilla.org.json +0 -9
- package/dist/rules/github.com.json +0 -21
- package/dist/schemas/api/skills.js +0 -19
- package/dist/schemas/common/errors.js +0 -7
- package/dist/schemas/common/headers.js +0 -5
- package/dist/schemas/entities/edge.js +0 -25
- package/dist/schemas/entities/entity.js +0 -22
- package/dist/schemas/entities/rule.js +0 -18
- package/dist/schemas/index.js +0 -14
- package/dist/server/event-bus.js +0 -59
- package/dist/server/http.js +0 -156
- package/dist/server/middleware.js +0 -70
- package/dist/server/routes/drift.js +0 -97
- package/dist/server/routes/intelligence.js +0 -1217
- package/dist/server/routes/reasoning-quality.js +0 -444
- package/dist/server/routes/session.js +0 -86
- package/dist/server/routes/stream.js +0 -120
- package/dist/server/routes/system.js +0 -73
- package/dist/server/routes/temporal.js +0 -170
- package/dist/server/routes/timeline.js +0 -232
- package/dist/server/routes/token-flow.js +0 -403
- package/dist/skills/effectiveness-tracker.js +0 -93
- package/dist/skills/local-pack.js +0 -380
- package/dist/skills/resolver.js +0 -495
- package/dist/state-detector.js +0 -83
- package/dist/timeline/intent-detector.js +0 -263
- package/dist/timeline/loop-miner.js +0 -140
- package/dist/timeline/open-threads.js +0 -49
- package/dist/timeline/signal-reinforcer.js +0 -62
- package/dist/timeline/timeline-bootstrap.js +0 -151
- package/dist/timeline/timeline-store.js +0 -618
- package/dist/tools/coding/bash.js +0 -49
- package/dist/tools/coding/file-edit.js +0 -72
- package/dist/tools/coding/file-outline.js +0 -227
- package/dist/tools/coding/file-read-protocol.js +0 -425
- package/dist/tools/coding/file-read.js +0 -35
- package/dist/tools/coding/file-write.js +0 -43
- package/dist/tools/coding/glob-tool.js +0 -109
- package/dist/tools/coding/grep.js +0 -162
- package/dist/tools/coding/index.js +0 -27
- package/dist/tools/intelligence/index.js +0 -269
- package/dist/tools/intelligence/record-fact.js +0 -48
- package/dist/tools/intelligence/timeline-markers.js +0 -130
- package/dist/tools/registry.js +0 -47
- package/dist/tools/types.js +0 -8
- package/dist/tracking/auto-snapshot-triggers.js +0 -246
- package/dist/tracking/branch-context.js +0 -115
- package/dist/tracking/branch-snapshot.js +0 -217
- package/dist/tracking/causal-bridge.js +0 -317
- package/dist/tracking/circuit-breaker.js +0 -147
- package/dist/tracking/commit-watcher.js +0 -114
- package/dist/tracking/context-ledger.js +0 -119
- package/dist/tracking/correction-detector.js +0 -324
- package/dist/tracking/drift-tracker.js +0 -874
- package/dist/tracking/durability-tracker.js +0 -94
- package/dist/tracking/entity-rewind.js +0 -200
- package/dist/tracking/file-hash-state.js +0 -114
- package/dist/tracking/git-attribution.js +0 -132
- package/dist/tracking/git-trailers.js +0 -171
- package/dist/tracking/intelligence-counter.js +0 -46
- package/dist/tracking/intent-correlator.js +0 -202
- package/dist/tracking/intent-encoder.js +0 -52
- package/dist/tracking/intent-token-tracker.js +0 -159
- package/dist/tracking/ledger-archiver.js +0 -94
- package/dist/tracking/ledger-chains.js +0 -245
- package/dist/tracking/metrics-store.js +0 -361
- package/dist/tracking/native-watcher.js +0 -131
- package/dist/tracking/offline-rewind.js +0 -295
- package/dist/tracking/pending-violations.js +0 -74
- package/dist/tracking/persistence-effectiveness.js +0 -167
- package/dist/tracking/prompt-durability.js +0 -202
- package/dist/tracking/quality-signals.js +0 -213
- package/dist/tracking/redactor.js +0 -73
- package/dist/tracking/rewind-engine.js +0 -161
- package/dist/tracking/session-history.js +0 -128
- package/dist/tracking/session-receipt.js +0 -88
- package/dist/tracking/session-summary-writer.js +0 -157
- package/dist/tracking/shadow-ledger.js +0 -321
- package/dist/tracking/stash-manager.js +0 -258
- package/dist/tracking/timeline-fork.js +0 -213
- package/dist/tracking/timeline.js +0 -69
- package/dist/tracking/token-flow.js +0 -276
- package/dist/tracking/turn-segmenter.js +0 -122
- package/dist/tracking/weekly-accumulator.js +0 -179
- package/dist/tracking/working-snapshots.js +0 -188
- package/dist/tracking/workspace-manifest.js +0 -176
- package/dist/transport/http.js +0 -102
- package/dist/utils/counterfactual.js +0 -65
- package/dist/utils/deep-link.js +0 -34
- package/dist/utils/detect.js +0 -193
- package/dist/utils/exec.js +0 -73
- package/dist/utils/file-logger.js +0 -87
- package/dist/utils/format-error.js +0 -29
- package/dist/utils/git.js +0 -181
- package/dist/utils/log.js +0 -57
- package/dist/utils/logger.js +0 -35
- package/dist/utils/mcp-content-json.js +0 -8
- package/dist/utils/session-logger.js +0 -154
- package/dist/utils/startup-log.js +0 -512
- 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
|
-
};
|
package/dist/hooks/hook-dedup.js
DELETED
|
@@ -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
|
-
}
|