@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,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compression-specific event log — per-command rows written to
|
|
3
|
-
* `.unerr/metrics.db` (`compression_events` and `file_read_events`).
|
|
4
|
-
*
|
|
5
|
-
* Previously this was JSONL (`logs/compression.jsonl`, `logs/file-reads.jsonl`);
|
|
6
|
-
* migrated to SQLite for indexed lookups, monotonic poll-by-id semantics
|
|
7
|
-
* (used by the log-tailer), and cheap aggregations in the dashboard.
|
|
8
|
-
*
|
|
9
|
-
* The wire types (CompressionLogEntry / FileReadLogEntry) are unchanged so
|
|
10
|
-
* callers don't need to know about the storage backend.
|
|
11
|
-
*/
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
import { openMetricsStore } from "../tracking/metrics-store.js";
|
|
14
|
-
function parseTs(ts) {
|
|
15
|
-
const n = Date.parse(ts);
|
|
16
|
-
return Number.isNaN(n) ? Date.now() : n;
|
|
17
|
-
}
|
|
18
|
-
export function appendCompressionLog(cwd, entry) {
|
|
19
|
-
try {
|
|
20
|
-
const store = openMetricsStore(join(cwd, ".unerr"));
|
|
21
|
-
store.insertCompression({
|
|
22
|
-
ts: parseTs(entry.ts),
|
|
23
|
-
ts_iso: entry.ts,
|
|
24
|
-
command: entry.command,
|
|
25
|
-
category: entry.category,
|
|
26
|
-
confidence: entry.confidence,
|
|
27
|
-
raw_bytes: entry.rawBytes,
|
|
28
|
-
compressed_bytes: entry.compressedBytes,
|
|
29
|
-
saved_pct: entry.savedPct,
|
|
30
|
-
omni_fallback: entry.omniFallback ? 1 : 0,
|
|
31
|
-
tee_file: entry.teeFile ?? null,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
/* best effort */
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
export function appendFileReadLog(cwd, entry) {
|
|
39
|
-
try {
|
|
40
|
-
const store = openMetricsStore(join(cwd, ".unerr"));
|
|
41
|
-
store.insertFileRead({
|
|
42
|
-
ts: parseTs(entry.ts),
|
|
43
|
-
ts_iso: entry.ts,
|
|
44
|
-
file: entry.file,
|
|
45
|
-
mode: entry.mode,
|
|
46
|
-
total_lines: entry.totalLines,
|
|
47
|
-
returned_lines: entry.returnedLines,
|
|
48
|
-
saved_pct: entry.savedPct,
|
|
49
|
-
entity: entry.entity ?? null,
|
|
50
|
-
token_estimate: entry.tokenEstimate ?? null,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
/* best effort */
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
export function readRecentFileReadLogs(cwd, limit = 10) {
|
|
58
|
-
try {
|
|
59
|
-
const store = openMetricsStore(join(cwd, ".unerr"));
|
|
60
|
-
return store.recentFileReads(limit).map((r) => ({
|
|
61
|
-
ts: r.ts_iso,
|
|
62
|
-
file: r.file,
|
|
63
|
-
mode: r.mode,
|
|
64
|
-
totalLines: r.total_lines,
|
|
65
|
-
returnedLines: r.returned_lines,
|
|
66
|
-
savedPct: r.saved_pct,
|
|
67
|
-
entity: r.entity ?? undefined,
|
|
68
|
-
tokenEstimate: r.token_estimate ?? undefined,
|
|
69
|
-
}));
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
export function readRecentCompressionLogs(cwd, limit = 10) {
|
|
76
|
-
try {
|
|
77
|
-
const store = openMetricsStore(join(cwd, ".unerr"));
|
|
78
|
-
return store.recentCompression(limit).map((r) => ({
|
|
79
|
-
ts: r.ts_iso,
|
|
80
|
-
command: r.command,
|
|
81
|
-
category: r.category,
|
|
82
|
-
confidence: r.confidence,
|
|
83
|
-
rawBytes: r.raw_bytes,
|
|
84
|
-
compressedBytes: r.compressed_bytes,
|
|
85
|
-
savedPct: r.saved_pct,
|
|
86
|
-
omniFallback: r.omni_fallback === 1,
|
|
87
|
-
teeFile: r.tee_file ?? undefined,
|
|
88
|
-
}));
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
return [];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer 6 Sprint FE-D / FE-F — ANSI strip + classifier dispatch + strategy compression + optional graph boost.
|
|
3
|
-
*/
|
|
4
|
-
import { readFileSync } from "node:fs";
|
|
5
|
-
import { TokenFlowWriter } from "../tracking/token-flow.js";
|
|
6
|
-
import { classifyShellOutput, } from "./shell-classifier.js";
|
|
7
|
-
import { appendCompressionLog } from "./shell-compression-log.js";
|
|
8
|
-
import { buildShellDiffRiskMap, buildShellFileRiskMap, categoryWantsFileRiskBoost, tryLoadGraphForShellBoost, } from "./shell-graph-boost.js";
|
|
9
|
-
import { shellCategoryToContentType } from "./shell-monitor-map.js";
|
|
10
|
-
import { estimateRoughTokens, recordShellCompressionEvent, } from "./shell-stats.js";
|
|
11
|
-
import { tryCompressCloud } from "./shell-strategies/cloud.js";
|
|
12
|
-
import { compressDiff } from "./shell-strategies/diff.js";
|
|
13
|
-
import { compressErrorDiagnostic } from "./shell-strategies/error-diagnostic.js";
|
|
14
|
-
import { applyUserFilter } from "./shell-strategies/filter-dsl.js";
|
|
15
|
-
import { compressGitStatus } from "./shell-strategies/git-status.js";
|
|
16
|
-
import { compressKeyValue } from "./shell-strategies/key-value.js";
|
|
17
|
-
import { compressLogText } from "./shell-strategies/log-text.js";
|
|
18
|
-
import { compressOmni } from "./shell-strategies/omni.js";
|
|
19
|
-
import { compressProgress } from "./shell-strategies/progress.js";
|
|
20
|
-
import { redactOutput } from "./shell-strategies/redact.js";
|
|
21
|
-
import { compressStructured } from "./shell-strategies/structured.js";
|
|
22
|
-
import { compressTabular } from "./shell-strategies/tabular.js";
|
|
23
|
-
import { compressTestResults } from "./shell-strategies/test-results.js";
|
|
24
|
-
import { compressTreePaths } from "./shell-strategies/tree-paths.js";
|
|
25
|
-
import { compressYaml } from "./shell-strategies/yaml.js";
|
|
26
|
-
import { teeShellOutput } from "./shell-tee.js";
|
|
27
|
-
const CONFIDENCE_GATE = 0.7;
|
|
28
|
-
/**
|
|
29
|
-
* R8 — commands whose primary signal lives on stderr. When the caller passes
|
|
30
|
-
* options.stderr alongside stdout, we merge stderr into the stream that goes
|
|
31
|
-
* through ANSI strip → redact → classify → compress. De-duplicated within the
|
|
32
|
-
* strategies (log_text / error_diagnostic already pattern-dedup).
|
|
33
|
-
*/
|
|
34
|
-
const STDERR_SIGNAL_COMMANDS = /\b(tsc|tsx|cargo|rustc|go (?:build|vet|test)|eslint|biome|ruff|mypy|pyright|npm (?:install|i|ci)|pnpm (?:install|i)|yarn (?:install)?|mvn|gradle|gradlew|make|cmake|webpack|vite build|esbuild|swc|rollup|next build|nuxi build|scalac|clang|gcc|shellcheck|hadolint|terraform (?:plan|apply|validate))\b/;
|
|
35
|
-
function shouldMergeStderr(command) {
|
|
36
|
-
return STDERR_SIGNAL_COMMANDS.test(command);
|
|
37
|
-
}
|
|
38
|
-
function mergeStderrIntoStdout(stdout, stderr) {
|
|
39
|
-
// Drop lines that already appear in stdout to avoid double-counting noise
|
|
40
|
-
if (!stderr.trim())
|
|
41
|
-
return stdout;
|
|
42
|
-
const stdoutLines = new Set(stdout.split("\n"));
|
|
43
|
-
const uniqueErr = stderr
|
|
44
|
-
.split("\n")
|
|
45
|
-
.filter((l) => l.trim() && !stdoutLines.has(l))
|
|
46
|
-
.join("\n");
|
|
47
|
-
if (!uniqueErr)
|
|
48
|
-
return stdout;
|
|
49
|
-
return stdout ? `${stdout}\n${uniqueErr}` : uniqueErr;
|
|
50
|
-
}
|
|
51
|
-
/** Strip common ANSI escape sequences (no extra dependency). */
|
|
52
|
-
export function stripAnsiCodes(text) {
|
|
53
|
-
const esc = String.fromCharCode(27);
|
|
54
|
-
const bel = String.fromCharCode(7);
|
|
55
|
-
const csi = new RegExp(`${esc}\\[[0-9;?]*[\\w~]`, "g");
|
|
56
|
-
const osc = new RegExp(`${esc}\\][^${bel}]*${bel}`, "g");
|
|
57
|
-
return text.replace(csi, "").replace(osc, "");
|
|
58
|
-
}
|
|
59
|
-
/** Apply a specific strategy compressor by category. */
|
|
60
|
-
function applyStrategy(category, stripped, command, options) {
|
|
61
|
-
switch (category) {
|
|
62
|
-
case "tabular":
|
|
63
|
-
return compressTabular(stripped, command);
|
|
64
|
-
case "log_text":
|
|
65
|
-
return compressLogText(stripped, command);
|
|
66
|
-
case "test_results":
|
|
67
|
-
return compressTestResults(stripped, command, options?.exitCode);
|
|
68
|
-
case "progress_streaming":
|
|
69
|
-
return compressProgress(stripped, command);
|
|
70
|
-
case "structured":
|
|
71
|
-
return compressStructured(stripped, command);
|
|
72
|
-
case "diff":
|
|
73
|
-
return compressDiff(stripped, undefined, command);
|
|
74
|
-
case "tree_paths":
|
|
75
|
-
return compressTreePaths(stripped, undefined, command);
|
|
76
|
-
case "key_value":
|
|
77
|
-
return compressKeyValue(stripped, command);
|
|
78
|
-
case "error_diagnostic":
|
|
79
|
-
return compressErrorDiagnostic(stripped, command);
|
|
80
|
-
case "yaml":
|
|
81
|
-
return compressYaml(stripped, command);
|
|
82
|
-
default:
|
|
83
|
-
return stripped;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
export async function compressShellOutput(command, stdout, options) {
|
|
87
|
-
const cwd = options?.cwd ?? process.cwd();
|
|
88
|
-
const persist = options?.persistStats !== false;
|
|
89
|
-
// R8 — fold stderr into the stream for commands whose signal lives there
|
|
90
|
-
const streamIn = options?.stderr && shouldMergeStderr(command)
|
|
91
|
-
? mergeStderrIntoStdout(stdout, options.stderr)
|
|
92
|
-
: stdout;
|
|
93
|
-
if (streamIn.includes("\u0000")) {
|
|
94
|
-
const classification = {
|
|
95
|
-
category: "structured",
|
|
96
|
-
confidence: 1,
|
|
97
|
-
hint_source: "content_heuristic",
|
|
98
|
-
};
|
|
99
|
-
if (options?.qualityMonitor) {
|
|
100
|
-
options.qualityMonitor.recordCompression(`shell-binary-${Date.now()}`, shellCategoryToContentType("structured"), 1);
|
|
101
|
-
}
|
|
102
|
-
return {
|
|
103
|
-
text: streamIn,
|
|
104
|
-
classification,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
const ansiStripped = stripAnsiCodes(streamIn);
|
|
108
|
-
const stripped = options?.skipRedact
|
|
109
|
-
? ansiStripped
|
|
110
|
-
: redactOutput(ansiStripped, options?.redactRules);
|
|
111
|
-
// N8 — tee-passthrough for small files. When the agent runs cat/head/tail/less
|
|
112
|
-
// on a file under 4 KB AND that file lives in a transient location
|
|
113
|
-
// (/tmp/*, .unerr/tee/*, or current cwd /tmp scratch), pass through verbatim.
|
|
114
|
-
// The existing .unerr/tee/*.txt bypass in exec.ts covers that one path; this
|
|
115
|
-
// covers the broader "agent reading its own scratch output" case.
|
|
116
|
-
const SMALL_PASSTHROUGH_BYTES = 4096;
|
|
117
|
-
if (stripped.length < SMALL_PASSTHROUGH_BYTES &&
|
|
118
|
-
/^(?:\s*)(?:cat|head|tail|less|more)\s+/.test(command) &&
|
|
119
|
-
/(?:^|\s)(?:\/tmp\/|\.unerr\/tee\/)\S+/.test(command)) {
|
|
120
|
-
const classification = {
|
|
121
|
-
category: "structured",
|
|
122
|
-
confidence: 1,
|
|
123
|
-
hint_source: "command_name",
|
|
124
|
-
};
|
|
125
|
-
return { text: stripped, classification };
|
|
126
|
-
}
|
|
127
|
-
// F2 — dedicated `git status` parser runs before classification.
|
|
128
|
-
// Generic key_value/log_text classifiers produced 0% compression on
|
|
129
|
-
// git status; this parser groups + de-boilerplates for ~70-85% wins.
|
|
130
|
-
if (/^\s*git\s+status\b/.test(command)) {
|
|
131
|
-
let gitStatusOut = compressGitStatus(stripped);
|
|
132
|
-
if (gitStatusOut) {
|
|
133
|
-
// R9 — also apply file-risk boost when the daemon graph is available,
|
|
134
|
-
// since git status is exactly the surface a coding agent uses to
|
|
135
|
-
// pick what to edit next.
|
|
136
|
-
let graphForBoost = options?.graph ?? null;
|
|
137
|
-
if (!graphForBoost && process.env.UNERR_SHELL_GRAPH_BOOST === "1") {
|
|
138
|
-
graphForBoost = await tryLoadGraphForShellBoost(cwd);
|
|
139
|
-
}
|
|
140
|
-
if (graphForBoost) {
|
|
141
|
-
const fileRisk = await buildShellFileRiskMap(graphForBoost, stripped);
|
|
142
|
-
if (fileRisk.size > 0) {
|
|
143
|
-
const header = `_shell_boost:file_risk\n${[...fileRisk.values()]
|
|
144
|
-
.slice(0, 10)
|
|
145
|
-
.map((h) => `${h.file} — risk:${h.risk_level} fan_in=${h.fan_in} top=${h.topEntity}`)
|
|
146
|
-
.join("\n")}\n`;
|
|
147
|
-
gitStatusOut = `${header}${gitStatusOut}`;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
const classification = {
|
|
151
|
-
category: "key_value",
|
|
152
|
-
confidence: 1,
|
|
153
|
-
hint_source: "command_name",
|
|
154
|
-
};
|
|
155
|
-
appendCompressionLog(cwd, {
|
|
156
|
-
ts: new Date().toISOString(),
|
|
157
|
-
command,
|
|
158
|
-
category: "git_status",
|
|
159
|
-
confidence: 1,
|
|
160
|
-
rawBytes: stripped.length,
|
|
161
|
-
compressedBytes: gitStatusOut.length,
|
|
162
|
-
savedPct: Math.max(0, Math.round(((stripped.length - gitStatusOut.length) /
|
|
163
|
-
Math.max(1, stripped.length)) *
|
|
164
|
-
100)),
|
|
165
|
-
omniFallback: false,
|
|
166
|
-
});
|
|
167
|
-
if (persist)
|
|
168
|
-
recordShellCompressionEvent(cwd, classification.category, stdout, gitStatusOut);
|
|
169
|
-
recordShellTokenFlow(cwd, command, classification.category, stripped, gitStatusOut, "git_status");
|
|
170
|
-
return { text: gitStatusOut, classification };
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// R4 — cloud-specific deep parser runs before classification
|
|
174
|
-
const cloudOut = tryCompressCloud(stripped, command);
|
|
175
|
-
if (cloudOut) {
|
|
176
|
-
const classification = {
|
|
177
|
-
category: "structured",
|
|
178
|
-
confidence: 1,
|
|
179
|
-
hint_source: "command_name",
|
|
180
|
-
};
|
|
181
|
-
appendCompressionLog(cwd, {
|
|
182
|
-
ts: new Date().toISOString(),
|
|
183
|
-
command,
|
|
184
|
-
category: "cloud",
|
|
185
|
-
confidence: 1,
|
|
186
|
-
rawBytes: stripped.length,
|
|
187
|
-
compressedBytes: cloudOut.length,
|
|
188
|
-
savedPct: Math.max(0, Math.round(((stripped.length - cloudOut.length) / Math.max(1, stripped.length)) *
|
|
189
|
-
100)),
|
|
190
|
-
omniFallback: false,
|
|
191
|
-
});
|
|
192
|
-
if (persist)
|
|
193
|
-
recordShellCompressionEvent(cwd, classification.category, stdout, cloudOut);
|
|
194
|
-
recordShellTokenFlow(cwd, command, classification.category, stripped, cloudOut, "cloud");
|
|
195
|
-
return { text: cloudOut, classification };
|
|
196
|
-
}
|
|
197
|
-
// R3 — user filter takes precedence over built-in classifier
|
|
198
|
-
const userFiltered = applyUserFilter(command, stripped, cwd);
|
|
199
|
-
if (userFiltered) {
|
|
200
|
-
const text = userFiltered.text;
|
|
201
|
-
const classification = {
|
|
202
|
-
category: "log_text",
|
|
203
|
-
confidence: 1,
|
|
204
|
-
hint_source: "command_name",
|
|
205
|
-
};
|
|
206
|
-
appendCompressionLog(cwd, {
|
|
207
|
-
ts: new Date().toISOString(),
|
|
208
|
-
command,
|
|
209
|
-
category: `user_filter[${userFiltered.name}]`,
|
|
210
|
-
confidence: 1,
|
|
211
|
-
rawBytes: stripped.length,
|
|
212
|
-
compressedBytes: text.length,
|
|
213
|
-
savedPct: Math.max(0, Math.round(((stripped.length - text.length) / Math.max(1, stripped.length)) * 100)),
|
|
214
|
-
omniFallback: false,
|
|
215
|
-
});
|
|
216
|
-
if (persist)
|
|
217
|
-
recordShellCompressionEvent(cwd, classification.category, stdout, text);
|
|
218
|
-
recordShellTokenFlow(cwd, command, classification.category, stripped, text, "user_filter");
|
|
219
|
-
return { text, classification };
|
|
220
|
-
}
|
|
221
|
-
const classification = classifyShellOutput(command, stripped);
|
|
222
|
-
let graph = options?.graph ?? null;
|
|
223
|
-
const wantsFileBoost = categoryWantsFileRiskBoost(classification.category, command);
|
|
224
|
-
if (!graph &&
|
|
225
|
-
(classification.category === "diff" || wantsFileBoost) &&
|
|
226
|
-
process.env.UNERR_SHELL_GRAPH_BOOST === "1") {
|
|
227
|
-
graph = await tryLoadGraphForShellBoost(cwd);
|
|
228
|
-
}
|
|
229
|
-
const riskMap = graph && classification.category === "diff"
|
|
230
|
-
? await buildShellDiffRiskMap(graph, stripped)
|
|
231
|
-
: undefined;
|
|
232
|
-
// R9 — file-level risk overlay for git status / git log --stat / lint output
|
|
233
|
-
const fileRiskMap = graph && wantsFileBoost
|
|
234
|
-
? await buildShellFileRiskMap(graph, stripped)
|
|
235
|
-
: undefined;
|
|
236
|
-
const fileRiskHeader = fileRiskMap && fileRiskMap.size > 0
|
|
237
|
-
? `_shell_boost:file_risk\n${[...fileRiskMap.values()]
|
|
238
|
-
.slice(0, 10)
|
|
239
|
-
.map((h) => `${h.file} — risk:${h.risk_level} fan_in=${h.fan_in} top=${h.topEntity}`)
|
|
240
|
-
.join("\n")}\n`
|
|
241
|
-
: "";
|
|
242
|
-
if (classification.confidence < CONFIDENCE_GATE) {
|
|
243
|
-
// Smart omni: try both the best-guess strategy AND omni, pick whichever compresses more
|
|
244
|
-
const omniResult = compressOmni(stripped);
|
|
245
|
-
let strategyResult = null;
|
|
246
|
-
try {
|
|
247
|
-
strategyResult = applyStrategy(classification.category, stripped, command, options);
|
|
248
|
-
}
|
|
249
|
-
catch {
|
|
250
|
-
// Strategy failed — fall back to omni
|
|
251
|
-
}
|
|
252
|
-
// Use strategy if it achieved >20% compression AND beat omni (or is within 10%)
|
|
253
|
-
let text;
|
|
254
|
-
if (strategyResult && strategyResult.length < stripped.length * 0.8) {
|
|
255
|
-
const strategyRatio = strategyResult.length / stripped.length;
|
|
256
|
-
const omniRatio = omniResult.length / stripped.length;
|
|
257
|
-
text = strategyRatio <= omniRatio * 1.1 ? strategyResult : omniResult;
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
text = omniResult;
|
|
261
|
-
}
|
|
262
|
-
const usedOmni = text !== strategyResult;
|
|
263
|
-
const savedPct = stripped.length > 0
|
|
264
|
-
? Math.round(((stripped.length - text.length) / stripped.length) * 100)
|
|
265
|
-
: 0;
|
|
266
|
-
appendCompressionLog(cwd, {
|
|
267
|
-
ts: new Date().toISOString(),
|
|
268
|
-
command,
|
|
269
|
-
category: classification.category,
|
|
270
|
-
confidence: classification.confidence,
|
|
271
|
-
rawBytes: stripped.length,
|
|
272
|
-
compressedBytes: text.length,
|
|
273
|
-
savedPct: Math.max(0, savedPct),
|
|
274
|
-
omniFallback: usedOmni,
|
|
275
|
-
});
|
|
276
|
-
if (persist)
|
|
277
|
-
recordShellCompressionEvent(cwd, classification.category, stdout, text);
|
|
278
|
-
if (options?.qualityMonitor) {
|
|
279
|
-
const before = estimateRoughTokens(stripped);
|
|
280
|
-
const after = estimateRoughTokens(text);
|
|
281
|
-
options.qualityMonitor.recordCompression(`shell-omni-${Date.now()}`, shellCategoryToContentType(classification.category), after / Math.max(1, before));
|
|
282
|
-
}
|
|
283
|
-
recordShellTokenFlow(cwd, command, classification.category, stripped, text, usedOmni ? "omni" : classification.category);
|
|
284
|
-
return { text, classification };
|
|
285
|
-
}
|
|
286
|
-
let text = stripped;
|
|
287
|
-
switch (classification.category) {
|
|
288
|
-
case "tabular":
|
|
289
|
-
text = compressTabular(stripped, command);
|
|
290
|
-
break;
|
|
291
|
-
case "log_text":
|
|
292
|
-
text = compressLogText(stripped, command);
|
|
293
|
-
break;
|
|
294
|
-
case "test_results":
|
|
295
|
-
text = compressTestResults(stripped, command, options?.exitCode);
|
|
296
|
-
break;
|
|
297
|
-
case "progress_streaming":
|
|
298
|
-
text = compressProgress(stripped, command);
|
|
299
|
-
break;
|
|
300
|
-
case "structured":
|
|
301
|
-
text = compressStructured(stripped, command);
|
|
302
|
-
break;
|
|
303
|
-
case "diff":
|
|
304
|
-
text = compressDiff(stripped, riskMap, command);
|
|
305
|
-
break;
|
|
306
|
-
case "tree_paths":
|
|
307
|
-
text = compressTreePaths(stripped, undefined, command);
|
|
308
|
-
break;
|
|
309
|
-
case "key_value":
|
|
310
|
-
text = compressKeyValue(stripped, command);
|
|
311
|
-
break;
|
|
312
|
-
case "error_diagnostic":
|
|
313
|
-
text = compressErrorDiagnostic(stripped, command);
|
|
314
|
-
break;
|
|
315
|
-
case "yaml":
|
|
316
|
-
text = compressYaml(stripped, command);
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
// R9 — prepend file-risk overlay when the boost ran
|
|
320
|
-
if (fileRiskHeader)
|
|
321
|
-
text = `${fileRiskHeader}${text}`;
|
|
322
|
-
// Tee: save full output to disk when compression is significant
|
|
323
|
-
const tee = teeShellOutput(cwd, command, stripped, text);
|
|
324
|
-
if (tee) {
|
|
325
|
-
text += `\n[full output: ${tee.filePath} (${(tee.sizeBytes / 1024).toFixed(1)}KB)]`;
|
|
326
|
-
}
|
|
327
|
-
const savedPctHi = stripped.length > 0
|
|
328
|
-
? Math.round(((stripped.length - text.length) / stripped.length) * 100)
|
|
329
|
-
: 0;
|
|
330
|
-
appendCompressionLog(cwd, {
|
|
331
|
-
ts: new Date().toISOString(),
|
|
332
|
-
command,
|
|
333
|
-
category: classification.category,
|
|
334
|
-
confidence: classification.confidence,
|
|
335
|
-
rawBytes: stripped.length,
|
|
336
|
-
compressedBytes: text.length,
|
|
337
|
-
savedPct: Math.max(0, savedPctHi),
|
|
338
|
-
omniFallback: false,
|
|
339
|
-
teeFile: tee?.filePath,
|
|
340
|
-
});
|
|
341
|
-
if (persist)
|
|
342
|
-
recordShellCompressionEvent(cwd, classification.category, stdout, text);
|
|
343
|
-
if (options?.qualityMonitor) {
|
|
344
|
-
const before = estimateRoughTokens(stripped);
|
|
345
|
-
const after = estimateRoughTokens(text);
|
|
346
|
-
options.qualityMonitor.recordCompression(`shell-${classification.category}-${Date.now()}`, shellCategoryToContentType(classification.category), after / Math.max(1, before));
|
|
347
|
-
}
|
|
348
|
-
recordShellTokenFlow(cwd, command, classification.category, stripped, text, classification.category);
|
|
349
|
-
return { text, classification };
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Layer 10: Record shell compression savings to token-flow.jsonl.
|
|
353
|
-
* Uses UNERR_SESSION_ID from env (set by parent proxy/MCP process).
|
|
354
|
-
* Exec processes have turn=0 since they lack turn context.
|
|
355
|
-
*/
|
|
356
|
-
function recordShellTokenFlow(cwd, command, category, raw, compressed, strategy) {
|
|
357
|
-
const rawTokens = Math.ceil(raw.length / 4);
|
|
358
|
-
const compressedTokens = Math.ceil(compressed.length / 4);
|
|
359
|
-
const shellSaved = rawTokens - compressedTokens;
|
|
360
|
-
if (shellSaved <= 0)
|
|
361
|
-
return;
|
|
362
|
-
try {
|
|
363
|
-
let sessionId = process.env.UNERR_SESSION_ID ?? "";
|
|
364
|
-
const unerrDir = `${cwd}/.unerr`;
|
|
365
|
-
// RC3 fix: Read session ID from file if env var not set (exec processes
|
|
366
|
-
// are launched by the agent shell, not child of --mcp)
|
|
367
|
-
if (!sessionId) {
|
|
368
|
-
try {
|
|
369
|
-
sessionId = readFileSync(`${unerrDir}/state/session.id`, "utf-8").trim();
|
|
370
|
-
}
|
|
371
|
-
catch {
|
|
372
|
-
sessionId = "unknown";
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
const writer = new TokenFlowWriter(unerrDir, sessionId);
|
|
376
|
-
writer.record({
|
|
377
|
-
session_id: sessionId,
|
|
378
|
-
turn: 0,
|
|
379
|
-
mechanism: "shell_compression",
|
|
380
|
-
tool: null,
|
|
381
|
-
tokens_without: rawTokens,
|
|
382
|
-
tokens_with: compressedTokens,
|
|
383
|
-
tokens_saved: shellSaved,
|
|
384
|
-
detail: { command: command.slice(0, 80), category, strategy },
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
catch {
|
|
388
|
-
/* best effort — never block compression */
|
|
389
|
-
}
|
|
390
|
-
}
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer 6 Sprint FE-F — optional Layer 2 lookups for shell stdout (diff risk hints).
|
|
3
|
-
*/
|
|
4
|
-
/** Identifier-like tokens worth resolving against the graph (bounded). */
|
|
5
|
-
export function extractRiskLookupCandidates(text) {
|
|
6
|
-
const out = new Set();
|
|
7
|
-
for (const m of text.matchAll(/\b([A-Za-z_][\w$]{2,63})\b/g)) {
|
|
8
|
-
const name = m[1];
|
|
9
|
-
if (name)
|
|
10
|
-
out.add(name);
|
|
11
|
-
}
|
|
12
|
-
for (const m of text.matchAll(/function\s+([A-Za-z_][\w$]*)/g)) {
|
|
13
|
-
const name = m[1];
|
|
14
|
-
if (name)
|
|
15
|
-
out.add(name);
|
|
16
|
-
}
|
|
17
|
-
return [...out].slice(0, 48);
|
|
18
|
-
}
|
|
19
|
-
/** Resolve symbols that are high-risk or high fan-in for diff annotation. */
|
|
20
|
-
export async function buildShellDiffRiskMap(graph, stdout) {
|
|
21
|
-
const candidates = extractRiskLookupCandidates(stdout);
|
|
22
|
-
const map = new Map();
|
|
23
|
-
for (const name of candidates) {
|
|
24
|
-
const e = await graph.findEntityByName(name);
|
|
25
|
-
if (!e)
|
|
26
|
-
continue;
|
|
27
|
-
if (e.risk_level === "high" ||
|
|
28
|
-
e.risk_level === "critical" ||
|
|
29
|
-
e.fan_in > 5) {
|
|
30
|
-
map.set(name, {
|
|
31
|
-
name: e.name,
|
|
32
|
-
risk_level: e.risk_level,
|
|
33
|
-
fan_in: e.fan_in,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return map;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* R9 — Extend graph boost beyond diff.
|
|
41
|
-
*
|
|
42
|
-
* Extracts likely file paths from `git status`, `git log --stat`, lint output,
|
|
43
|
-
* etc., then resolves each path's max-risk entity from the graph. Returns
|
|
44
|
-
* `path → "risk:high (fan_in=24)"` annotations so the strategy can inline
|
|
45
|
-
* them next to the file row without reading the file.
|
|
46
|
-
*/
|
|
47
|
-
const PATH_RE = /\b([A-Za-z0-9_./-]+\.[A-Za-z0-9]{1,6})\b/g;
|
|
48
|
-
export function extractFilePathCandidates(text) {
|
|
49
|
-
const out = new Set();
|
|
50
|
-
for (const m of text.matchAll(PATH_RE)) {
|
|
51
|
-
const p = m[1];
|
|
52
|
-
if (!p)
|
|
53
|
-
continue;
|
|
54
|
-
// Must contain a / OR look like a code file at the root
|
|
55
|
-
if (p.includes("/") ||
|
|
56
|
-
/\.(ts|tsx|js|jsx|py|go|rs|rb|cs|java|kt|swift|scala|cpp|c|h|hpp)$/i.test(p)) {
|
|
57
|
-
out.add(p);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return [...out].slice(0, 64);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Build `file_path → risk hint` map by scanning paths in the output and
|
|
64
|
-
* resolving the highest-fan-in entity owned by each file via the graph.
|
|
65
|
-
*/
|
|
66
|
-
export async function buildShellFileRiskMap(graph, stdout) {
|
|
67
|
-
const paths = extractFilePathCandidates(stdout);
|
|
68
|
-
const map = new Map();
|
|
69
|
-
for (const path of paths) {
|
|
70
|
-
try {
|
|
71
|
-
const entities = await graph.getEntitiesByFile(path);
|
|
72
|
-
if (!entities || entities.length === 0)
|
|
73
|
-
continue;
|
|
74
|
-
const first = entities[0];
|
|
75
|
-
if (!first)
|
|
76
|
-
continue;
|
|
77
|
-
// Pick the highest fan_in entity to summarize file-level risk
|
|
78
|
-
let top = first;
|
|
79
|
-
for (const e of entities) {
|
|
80
|
-
if ((e.fan_in ?? 0) > (top.fan_in ?? 0))
|
|
81
|
-
top = e;
|
|
82
|
-
}
|
|
83
|
-
const fanIn = top.fan_in ?? 0;
|
|
84
|
-
if (top.risk_level === "high" ||
|
|
85
|
-
top.risk_level === "critical" ||
|
|
86
|
-
fanIn > 5) {
|
|
87
|
-
map.set(path, {
|
|
88
|
-
file: path,
|
|
89
|
-
topEntity: top.name,
|
|
90
|
-
risk_level: top.risk_level,
|
|
91
|
-
fan_in: fanIn,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
// Per-file lookup failures are non-fatal — keep building the map
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return map;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Categories whose strategies can benefit from a file-level risk overlay.
|
|
103
|
-
* Used by the dispatcher to decide whether to invoke buildShellFileRiskMap.
|
|
104
|
-
*/
|
|
105
|
-
export function categoryWantsFileRiskBoost(category, command) {
|
|
106
|
-
if (!command)
|
|
107
|
-
return false;
|
|
108
|
-
if (category === "diff")
|
|
109
|
-
return false; // handled by buildShellDiffRiskMap
|
|
110
|
-
// git status / git log --stat / lint outputs benefit
|
|
111
|
-
if (/^\s*git\s+(status|log)\b/.test(command))
|
|
112
|
-
return true;
|
|
113
|
-
if (/^\s*(eslint|biome|tsc|ruff|mypy|golangci|cargo clippy|shellcheck)\b/.test(command))
|
|
114
|
-
return true;
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
const BOOST_TTL_MS = 30_000;
|
|
118
|
-
const boostCache = new Map();
|
|
119
|
-
async function logBoostFailure(reason) {
|
|
120
|
-
try {
|
|
121
|
-
const { startupLog } = await import("../utils/startup-log.js");
|
|
122
|
-
startupLog.fileOnly("warn", `shell graph boost: ${reason}`);
|
|
123
|
-
}
|
|
124
|
-
catch {
|
|
125
|
-
/* logging failure is non-fatal */
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
/** Load snapshot graph for hook/exec boost (same snapshot layout as proxy). */
|
|
129
|
-
export async function tryLoadGraphForShellBoost(cwd) {
|
|
130
|
-
const cached = boostCache.get(cwd);
|
|
131
|
-
if (cached && Date.now() - cached.loadedAt < BOOST_TTL_MS) {
|
|
132
|
-
return cached.kind === "ok" ? cached.graph : null;
|
|
133
|
-
}
|
|
134
|
-
const { existsSync, readFileSync } = await import("node:fs");
|
|
135
|
-
const { join } = await import("node:path");
|
|
136
|
-
const snapshotsDir = join(cwd, ".unerr", "snapshots");
|
|
137
|
-
let snapshotPath = join(snapshotsDir, "graph.msgpack.gz");
|
|
138
|
-
if (!existsSync(snapshotPath)) {
|
|
139
|
-
snapshotPath = join(snapshotsDir, "graph.msgpack");
|
|
140
|
-
}
|
|
141
|
-
if (!existsSync(snapshotPath)) {
|
|
142
|
-
const reason = `snapshot missing at ${snapshotsDir}`;
|
|
143
|
-
await logBoostFailure(reason);
|
|
144
|
-
boostCache.set(cwd, { kind: "miss", reason, loadedAt: Date.now() });
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
let CozoDbConstructor;
|
|
148
|
-
let CozoGraphStoreCtor;
|
|
149
|
-
try {
|
|
150
|
-
const cozoMod = (await import("cozo-node"));
|
|
151
|
-
CozoDbConstructor = cozoMod.default
|
|
152
|
-
? cozoMod.default.CozoDb
|
|
153
|
-
: cozoMod.CozoDb;
|
|
154
|
-
if (typeof CozoDbConstructor !== "function") {
|
|
155
|
-
throw new Error("cozo-node CozoDb export is not a constructor");
|
|
156
|
-
}
|
|
157
|
-
const localGraph = await import("../intelligence/local-graph.js");
|
|
158
|
-
CozoGraphStoreCtor = localGraph.CozoGraphStore;
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
const reason = `module load failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
162
|
-
await logBoostFailure(reason);
|
|
163
|
-
boostCache.set(cwd, { kind: "miss", reason, loadedAt: Date.now() });
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
let buffer;
|
|
167
|
-
try {
|
|
168
|
-
const { gunzipSync } = await import("node:zlib");
|
|
169
|
-
const raw = readFileSync(snapshotPath);
|
|
170
|
-
try {
|
|
171
|
-
buffer = gunzipSync(raw);
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
buffer = raw; // legacy uncompressed snapshot
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
catch (err) {
|
|
178
|
-
const reason = `snapshot read failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
179
|
-
await logBoostFailure(reason);
|
|
180
|
-
boostCache.set(cwd, { kind: "miss", reason, loadedAt: Date.now() });
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
try {
|
|
184
|
-
const db = new CozoDbConstructor();
|
|
185
|
-
const graph = await CozoGraphStoreCtor.create(db);
|
|
186
|
-
const { unpack } = await import("msgpackr");
|
|
187
|
-
const envelope = unpack(buffer);
|
|
188
|
-
await graph.loadSnapshot(envelope);
|
|
189
|
-
boostCache.set(cwd, { kind: "ok", graph, loadedAt: Date.now() });
|
|
190
|
-
return graph;
|
|
191
|
-
}
|
|
192
|
-
catch (err) {
|
|
193
|
-
const reason = `graph instantiation failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
194
|
-
await logBoostFailure(reason);
|
|
195
|
-
boostCache.set(cwd, { kind: "miss", reason, loadedAt: Date.now() });
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
/** Test-only — clear the boost cache so unit tests don't share state. */
|
|
200
|
-
export function _clearShellBoostCache() {
|
|
201
|
-
boostCache.clear();
|
|
202
|
-
}
|