@unerr-ai/unerr 0.1.6 → 0.1.7
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 +70 -194
- package/dist/cli.js +39149 -36991
- package/package.json +9 -2
- package/dist/__tests__/architecture-guard.test.js +0 -122
- package/dist/__tests__/arg-validator.test.js +0 -205
- package/dist/__tests__/ast-extractor.test.js +0 -203
- package/dist/__tests__/auto-bootstrap.test.js +0 -280
- package/dist/__tests__/background-indexer.test.js +0 -228
- package/dist/__tests__/blast-radius-engine.test.js +0 -200
- package/dist/__tests__/bridge-isolation.test.js +0 -37
- package/dist/__tests__/budget-enforcer.test.js +0 -53
- package/dist/__tests__/cfg-test-detection-perf.test.js +0 -82
- package/dist/__tests__/change-narrative.test.js +0 -190
- package/dist/__tests__/check-commit.test.js +0 -258
- package/dist/__tests__/checksum.test.js +0 -34
- package/dist/__tests__/commit-watcher.test.js +0 -154
- package/dist/__tests__/community-detection.test.js +0 -179
- package/dist/__tests__/community-tools.test.js +0 -299
- package/dist/__tests__/components.test.js +0 -449
- package/dist/__tests__/compression-log.test.js +0 -174
- package/dist/__tests__/compression-quality-monitor.test.js +0 -40
- package/dist/__tests__/config-healer.test.js +0 -165
- package/dist/__tests__/context-ledger.test.js +0 -58
- package/dist/__tests__/convention-detector.test.js +0 -99
- package/dist/__tests__/convention-learner.test.js +0 -86
- package/dist/__tests__/correction-detector.test.js +0 -330
- package/dist/__tests__/daemon-autostart-install.test.js +0 -283
- package/dist/__tests__/daemon-bridge.test.js +0 -222
- package/dist/__tests__/daemon-dashboard.test.js +0 -202
- package/dist/__tests__/daemon-registry.test.js +0 -240
- package/dist/__tests__/daemon-supervisor.test.js +0 -318
- package/dist/__tests__/daemon-version-check.test.js +0 -275
- package/dist/__tests__/decision-point-detector.test.js +0 -98
- package/dist/__tests__/deep-link.test.js +0 -143
- package/dist/__tests__/disallowed-tools.test.js +0 -115
- package/dist/__tests__/drift-tracker.test.js +0 -582
- package/dist/__tests__/durability-scorer.test.js +0 -152
- package/dist/__tests__/efficiency-tracker.test.js +0 -65
- package/dist/__tests__/enrich.test.js +0 -144
- package/dist/__tests__/entity-rewind.test.js +0 -248
- package/dist/__tests__/ephemeral.test.js +0 -111
- package/dist/__tests__/exploration-cost.test.js +0 -93
- package/dist/__tests__/fact-generator.test.js +0 -197
- package/dist/__tests__/file-l0-graph.test.js +0 -244
- package/dist/__tests__/file-logger.test.js +0 -82
- package/dist/__tests__/file-outline.test.js +0 -141
- package/dist/__tests__/file-read-protocol.test.js +0 -188
- package/dist/__tests__/format-encoder.test.js +0 -233
- package/dist/__tests__/git-attribution.test.js +0 -259
- package/dist/__tests__/graph-temporal-joiner.test.js +0 -219
- package/dist/__tests__/health-grade-enhanced.test.js +0 -138
- package/dist/__tests__/health-map-data.test.js +0 -173
- package/dist/__tests__/helpers/mcp-harness.js +0 -45
- package/dist/__tests__/helpers/mcp-harness.test.js +0 -68
- package/dist/__tests__/hook-dedup.test.js +0 -112
- package/dist/__tests__/hook-runner.test.js +0 -253
- package/dist/__tests__/indexer-cfg.test.js +0 -185
- package/dist/__tests__/indexer-cross-file.test.js +0 -172
- package/dist/__tests__/indexer-extraction.test.js +0 -245
- package/dist/__tests__/indexer-incremental.test.js +0 -232
- package/dist/__tests__/indexer-language-expansion.test.js +0 -165
- package/dist/__tests__/init-push.test.js +0 -131
- package/dist/__tests__/instruction-writer.test.js +0 -179
- package/dist/__tests__/intelligence-integration.test.js +0 -217
- package/dist/__tests__/intent-correlator.test.js +0 -175
- package/dist/__tests__/intent-detector.test.js +0 -235
- package/dist/__tests__/intent-encoder.test.js +0 -167
- package/dist/__tests__/java-build-tool-detection.test.js +0 -174
- package/dist/__tests__/layer3-sprint-q.test.js +0 -160
- package/dist/__tests__/layer3-sprint-r.test.js +0 -91
- package/dist/__tests__/layer3-sprint-s.test.js +0 -183
- package/dist/__tests__/layer3-sprint-t.test.js +0 -201
- package/dist/__tests__/layer3-sprint-u.test.js +0 -174
- package/dist/__tests__/layer4-sprint-ba2.test.js +0 -354
- package/dist/__tests__/layer4-sprint-ba4.test.js +0 -84
- package/dist/__tests__/layer4-sprint-vs.test.js +0 -105
- package/dist/__tests__/ledger-chains.test.js +0 -162
- package/dist/__tests__/lifecycle-machine.test.js +0 -226
- package/dist/__tests__/local-chat-provider.test.js +0 -170
- package/dist/__tests__/local-convention-detector.test.js +0 -308
- package/dist/__tests__/local-embeddings.test.js +0 -422
- package/dist/__tests__/local-graph.test.js +0 -540
- package/dist/__tests__/local-indexer.test.js +0 -228
- package/dist/__tests__/local-intelligence-l3.test.js +0 -332
- package/dist/__tests__/local-llm.test.js +0 -253
- package/dist/__tests__/local-mode-offline.test.js +0 -187
- package/dist/__tests__/local-mode-stats.test.js +0 -273
- package/dist/__tests__/local-mode-tui.test.js +0 -343
- package/dist/__tests__/local-parse.test.js +0 -199
- package/dist/__tests__/log-tailer.test.js +0 -208
- package/dist/__tests__/loop-breaker.test.js +0 -276
- package/dist/__tests__/loop-miner.test.js +0 -226
- package/dist/__tests__/mcp-config.test.js +0 -126
- package/dist/__tests__/mcp-content-json.test.js +0 -10
- package/dist/__tests__/mcp-envelope.test.js +0 -124
- package/dist/__tests__/metrics-store.test.js +0 -223
- package/dist/__tests__/native-watcher.test.js +0 -191
- package/dist/__tests__/navigation-hooks-agent-aware.test.js +0 -145
- package/dist/__tests__/negative-knowledge.test.js +0 -116
- package/dist/__tests__/network-boundary.test.js +0 -190
- package/dist/__tests__/network-firewall.test.js +0 -112
- package/dist/__tests__/nudge-invariants.test.js +0 -160
- package/dist/__tests__/nudge-v2.test.js +0 -225
- package/dist/__tests__/offline-rewind.test.js +0 -251
- package/dist/__tests__/open-threads.test.js +0 -89
- package/dist/__tests__/output-compressor.test.js +0 -93
- package/dist/__tests__/pending-violations.test.js +0 -112
- package/dist/__tests__/persistence-effectiveness.test.js +0 -143
- package/dist/__tests__/provider-factory.test.js +0 -42
- package/dist/__tests__/providers.test.js +0 -24
- package/dist/__tests__/proxy.test.js +0 -314
- package/dist/__tests__/query-router.test.js +0 -1018
- package/dist/__tests__/reasoning-quality-route.test.js +0 -138
- package/dist/__tests__/redactor.test.js +0 -120
- package/dist/__tests__/resource-monitor.test.js +0 -57
- package/dist/__tests__/response-envelope.test.js +0 -100
- package/dist/__tests__/risk-classifier.test.js +0 -101
- package/dist/__tests__/risk-signal-scope.test.js +0 -75
- package/dist/__tests__/rule-evaluator.test.js +0 -280
- package/dist/__tests__/scip-decoder.test.js +0 -49
- package/dist/__tests__/scip-downloader.test.js +0 -201
- package/dist/__tests__/scip-merger.test.js +0 -103
- package/dist/__tests__/search-index.test.js +0 -422
- package/dist/__tests__/semantic-enrichment.test.js +0 -360
- package/dist/__tests__/session-brief-builder.test.js +0 -187
- package/dist/__tests__/session-context.test.js +0 -221
- package/dist/__tests__/session-continuity.test.js +0 -144
- package/dist/__tests__/session-dedup.test.js +0 -74
- package/dist/__tests__/session-event-wiring.test.js +0 -206
- package/dist/__tests__/session-events.test.js +0 -149
- package/dist/__tests__/session-legend.test.js +0 -20
- package/dist/__tests__/session-persistence.test.js +0 -131
- package/dist/__tests__/session-resume-block.test.js +0 -107
- package/dist/__tests__/session-resume.test.js +0 -97
- package/dist/__tests__/session-summary-writer.test.js +0 -134
- package/dist/__tests__/shadow-ledger.test.js +0 -203
- package/dist/__tests__/shell-classifier.test.js +0 -151
- package/dist/__tests__/shell-compression-floor.test.js +0 -189
- package/dist/__tests__/shell-compression-v2.test.js +0 -339
- package/dist/__tests__/shell-compressor.test.js +0 -35
- package/dist/__tests__/shell-hooks.test.js +0 -128
- package/dist/__tests__/shell-strategies.test.js +0 -644
- package/dist/__tests__/shell-tee.test.js +0 -133
- package/dist/__tests__/signal-dedup.test.js +0 -158
- package/dist/__tests__/signal-reinforcer.test.js +0 -77
- package/dist/__tests__/signal-scorer.test.js +0 -251
- package/dist/__tests__/signal-show-store.test.js +0 -108
- package/dist/__tests__/smart-truncate.test.js +0 -215
- package/dist/__tests__/snapshot-v2.test.js +0 -113
- package/dist/__tests__/sprint-l1-local-mode.test.js +0 -130
- package/dist/__tests__/sprint-l10-boot.test.js +0 -220
- package/dist/__tests__/sprint-l9-offline-commands.test.js +0 -189
- package/dist/__tests__/sprint-q-persistent-context.test.js +0 -198
- package/dist/__tests__/sprint-s1-wiring.test.js +0 -215
- package/dist/__tests__/sprint-s2-wiring.test.js +0 -256
- package/dist/__tests__/sprint-s3-wiring.test.js +0 -195
- package/dist/__tests__/sprint-s4-wiring.test.js +0 -213
- package/dist/__tests__/sprint-s6-hooks.test.js +0 -222
- package/dist/__tests__/sprint-s7-persistent.test.js +0 -263
- package/dist/__tests__/sprint-s8-value.test.js +0 -167
- package/dist/__tests__/sprint-s9-behavioral.test.js +0 -179
- package/dist/__tests__/sprint3-intelligence.test.js +0 -297
- package/dist/__tests__/sprint5-mcp-server.test.js +0 -136
- package/dist/__tests__/startup-display.test.js +0 -302
- package/dist/__tests__/startup-log-file.test.js +0 -97
- package/dist/__tests__/stash-manager.test.js +0 -229
- package/dist/__tests__/state-detector.test.js +0 -92
- package/dist/__tests__/status-dashboard.test.js +0 -142
- package/dist/__tests__/temporal-facts.test.js +0 -292
- package/dist/__tests__/temporal-routes.test.js +0 -142
- package/dist/__tests__/test-detector.test.js +0 -174
- package/dist/__tests__/theme.test.js +0 -72
- package/dist/__tests__/timeline-agents.test.js +0 -122
- package/dist/__tests__/timeline-bootstrap.test.js +0 -176
- package/dist/__tests__/timeline-filters.test.js +0 -193
- package/dist/__tests__/timeline-markers.test.js +0 -151
- package/dist/__tests__/timeline-routes.test.js +0 -156
- package/dist/__tests__/timeline-store.test.js +0 -171
- package/dist/__tests__/token-counter.test.js +0 -86
- package/dist/__tests__/token-estimator.test.js +0 -96
- package/dist/__tests__/token-flow-api.test.js +0 -239
- package/dist/__tests__/token-flow-instrumentation.test.js +0 -437
- package/dist/__tests__/token-flow-persistence.test.js +0 -356
- package/dist/__tests__/token-flow-routes.test.js +0 -199
- package/dist/__tests__/token-flow.test.js +0 -695
- package/dist/__tests__/tool-clusters.test.js +0 -177
- package/dist/__tests__/transport-mux.test.js +0 -283
- package/dist/__tests__/turn-segmenter.test.js +0 -166
- package/dist/__tests__/uninstall.test.js +0 -141
- package/dist/__tests__/warm-start-policy.test.js +0 -271
- package/dist/__tests__/wire-cap-nudge.test.js +0 -77
- package/dist/__tests__/worker-pool.test.js +0 -101
- package/dist/ui/assets/index-7gl3mIuY.css +0 -1
- package/dist/ui/assets/index-CX4FCWGT.js +0 -10
- package/dist/ui/assets/rolldown-runtime-S-ySWqyJ.js +0 -1
- package/dist/ui/assets/vis-network-NIJHUFI3.js +0 -908
- package/dist/ui/fonts/jetbrains-mono-latin-400-normal.woff +0 -0
- package/dist/ui/icon-wordmark.png +0 -0
- package/dist/ui/icon-wordmark.svg +0 -30
- package/dist/ui/icon.png +0 -0
- package/dist/ui/icon.svg +0 -25
- package/dist/ui/index.html +0 -15
- package/dist/ui/prototype-sandbox/index.html +0 -257
- package/dist/ui/screenshots/activity.png +0 -0
- package/dist/ui/screenshots/code-base-intelligence.png +0 -0
- package/dist/ui/screenshots/dashboard.png +0 -0
- package/dist/ui/screenshots/project-memory.png +0 -0
- package/dist/ui/screenshots/reasoning-quality.png +0 -0
- package/dist/ui/screenshots/reasoning-session.png +0 -0
- package/dist/ui/screenshots/token-session.png +0 -0
- package/dist/ui/screenshots/token-trace-main.png +0 -0
- package/dist/ui/screenshots/token-turn.png +0 -0
- package/dist/ui/unerr-wordmark.png +0 -0
- package/dist/ui/unerr-wordmark.svg +0 -9
- package/dist/ui/unerr.png +0 -0
- package/dist/ui/unerr.svg +0 -25
- package/dist/ui/web-app-manifest-192x192.png +0 -0
- package/dist/ui/web-app-manifest-512x512.png +0 -0
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Nudge v2 — drift detector + session state.
|
|
3
|
-
*
|
|
4
|
-
* Critical contract: the new system must NOT degrade unerr MCP tool adoption.
|
|
5
|
-
* - Drift detector must fire on grep/find/cat on code paths.
|
|
6
|
-
* - Drift detector must NOT fire on legitimate Bash commands (npm, git tag,
|
|
7
|
-
* mkdir, clean linters, ls without -R, grep of log files, etc.) so the
|
|
8
|
-
* agent isn't nudged for things that don't have an unerr equivalent.
|
|
9
|
-
* - State machine must enforce one-Tier-1-per-kind-per-session.
|
|
10
|
-
* - markUnerrToolUsed must reset the drift accumulator so Tier 2 only fires
|
|
11
|
-
* on PERSISTENT drift, never on a single mistake.
|
|
12
|
-
*/
|
|
13
|
-
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
-
import { tmpdir } from "node:os";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
17
|
-
import { formatDriftNudge, isDriftCommand } from "../proxy/drift-detector.js";
|
|
18
|
-
import { _resetNudgeState, markUnerrToolUsed, readNudgeState, updateNudgeState, } from "../proxy/nudge-state.js";
|
|
19
|
-
describe("isDriftCommand — TRUE POSITIVES (must nudge)", () => {
|
|
20
|
-
it("flags grep on a code path", () => {
|
|
21
|
-
const h = isDriftCommand("grep -r 'compressShellOutput' src/proxy/");
|
|
22
|
-
expect(h?.kind).toBe("code_search");
|
|
23
|
-
expect(h?.arg).toBe("compressShellOutput");
|
|
24
|
-
expect(h?.suggest).toContain("search_code");
|
|
25
|
-
});
|
|
26
|
-
it("flags rg on a code path", () => {
|
|
27
|
-
const h = isDriftCommand("rg foo src/");
|
|
28
|
-
expect(h?.kind).toBe("code_search");
|
|
29
|
-
});
|
|
30
|
-
it("flags find -name *.ts", () => {
|
|
31
|
-
const h = isDriftCommand("find . -name '*.ts'");
|
|
32
|
-
expect(h?.kind).toBe("code_search");
|
|
33
|
-
expect(h?.suggest).toContain("search_code");
|
|
34
|
-
});
|
|
35
|
-
it("flags cat on a .ts file", () => {
|
|
36
|
-
const h = isDriftCommand("cat src/proxy/shell-compressor.ts");
|
|
37
|
-
expect(h?.kind).toBe("code_read");
|
|
38
|
-
expect(h?.suggest).toContain("file_read");
|
|
39
|
-
expect(h?.suggest).toContain("src/proxy/shell-compressor.ts");
|
|
40
|
-
});
|
|
41
|
-
it("flags head -100 on a .py file", () => {
|
|
42
|
-
const h = isDriftCommand("head -100 lib/foo.py");
|
|
43
|
-
expect(h?.kind).toBe("code_read");
|
|
44
|
-
});
|
|
45
|
-
it("flags ls -R src/", () => {
|
|
46
|
-
const h = isDriftCommand("ls -R src/");
|
|
47
|
-
expect(h?.kind).toBe("dir_explore");
|
|
48
|
-
expect(h?.suggest).toContain("file_outline");
|
|
49
|
-
});
|
|
50
|
-
it("strips leading env vars before classification", () => {
|
|
51
|
-
const h = isDriftCommand("FOO=1 BAR=2 grep -r 'x' src/");
|
|
52
|
-
expect(h?.kind).toBe("code_search");
|
|
53
|
-
});
|
|
54
|
-
it("strips leading sudo / time prefixes", () => {
|
|
55
|
-
const h = isDriftCommand("time grep -r 'x' src/");
|
|
56
|
-
expect(h?.kind).toBe("code_search");
|
|
57
|
-
});
|
|
58
|
-
// Regression: absolute paths like /Users/foo/repo/src/proxy/ must fire too.
|
|
59
|
-
// CODE_PATH_HINT_RE previously required src/lib/etc to be preceded by
|
|
60
|
-
// whitespace/quote/start — slashes were excluded, so abs paths silently
|
|
61
|
-
// bypassed the v2 drift nudge.
|
|
62
|
-
it("flags grep against an absolute path containing src/", () => {
|
|
63
|
-
const h = isDriftCommand("grep -rn 'compressShellOutput' /Users/foo/repo/src/proxy/");
|
|
64
|
-
expect(h?.kind).toBe("code_search");
|
|
65
|
-
expect(h?.suggest).toContain("search_code");
|
|
66
|
-
});
|
|
67
|
-
it("flags grep against an absolute path containing lib/", () => {
|
|
68
|
-
const h = isDriftCommand("grep -rn 'foo' /home/u/code/lib/");
|
|
69
|
-
expect(h?.kind).toBe("code_search");
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
describe("isDriftCommand — TRUE NEGATIVES (must NOT nudge)", () => {
|
|
73
|
-
// These are the commands the v1 nudge fired on unnecessarily.
|
|
74
|
-
// Each one should return null — there is no unerr alternative.
|
|
75
|
-
it("does NOT flag npm version", () => {
|
|
76
|
-
expect(isDriftCommand("npm version 0.0.0-beta.11 --no-git-tag-version")).toBeNull();
|
|
77
|
-
});
|
|
78
|
-
it("does NOT flag git tag / git status / git log", () => {
|
|
79
|
-
expect(isDriftCommand("git tag v1.0.0")).toBeNull();
|
|
80
|
-
expect(isDriftCommand("git status")).toBeNull();
|
|
81
|
-
expect(isDriftCommand("git log --oneline -10")).toBeNull();
|
|
82
|
-
});
|
|
83
|
-
it("does NOT flag mkdir / chmod / rm", () => {
|
|
84
|
-
expect(isDriftCommand("mkdir -p /tmp/foo")).toBeNull();
|
|
85
|
-
expect(isDriftCommand("chmod +x /tmp/foo.sh")).toBeNull();
|
|
86
|
-
expect(isDriftCommand("rm -rf /tmp/foo")).toBeNull();
|
|
87
|
-
});
|
|
88
|
-
it("does NOT flag a clean eslint run", () => {
|
|
89
|
-
expect(isDriftCommand("node_modules/.bin/eslint 'app/(launch)'")).toBeNull();
|
|
90
|
-
});
|
|
91
|
-
it("does NOT flag pnpm install / build / test", () => {
|
|
92
|
-
expect(isDriftCommand("pnpm install")).toBeNull();
|
|
93
|
-
expect(isDriftCommand("pnpm run build")).toBeNull();
|
|
94
|
-
expect(isDriftCommand("pnpm exec vitest run")).toBeNull();
|
|
95
|
-
});
|
|
96
|
-
it("does NOT flag grep on a log file (no code extension)", () => {
|
|
97
|
-
expect(isDriftCommand("grep ERROR build.log")).toBeNull();
|
|
98
|
-
expect(isDriftCommand("grep 'foo' /var/log/syslog")).toBeNull();
|
|
99
|
-
});
|
|
100
|
-
it("does NOT flag cat on a non-code file", () => {
|
|
101
|
-
expect(isDriftCommand("cat README.md")).toBeNull();
|
|
102
|
-
expect(isDriftCommand("cat package.json")).toBeNull();
|
|
103
|
-
expect(isDriftCommand("cat /tmp/output.txt")).toBeNull();
|
|
104
|
-
});
|
|
105
|
-
it("does NOT flag ls without -R", () => {
|
|
106
|
-
expect(isDriftCommand("ls src/")).toBeNull();
|
|
107
|
-
expect(isDriftCommand("ls -la /tmp/")).toBeNull();
|
|
108
|
-
});
|
|
109
|
-
it("does NOT flag find without -name on code", () => {
|
|
110
|
-
expect(isDriftCommand("find . -type d")).toBeNull();
|
|
111
|
-
expect(isDriftCommand("find /tmp -mmin -5")).toBeNull();
|
|
112
|
-
});
|
|
113
|
-
it("does NOT flag find -name '*.log'", () => {
|
|
114
|
-
expect(isDriftCommand("find . -name '*.log'")).toBeNull();
|
|
115
|
-
});
|
|
116
|
-
it("does NOT flag empty or whitespace command", () => {
|
|
117
|
-
expect(isDriftCommand("")).toBeNull();
|
|
118
|
-
expect(isDriftCommand(" ")).toBeNull();
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
describe("formatDriftNudge — output shape", () => {
|
|
122
|
-
it("produces a one-line nudge with the alternative", () => {
|
|
123
|
-
// Post-trim format (table rows #1-4): drops the internal "drift(<kind>):
|
|
124
|
-
// try" preamble. Line starts with "[unerr]" + the paste-ready call.
|
|
125
|
-
const hint = isDriftCommand("grep -r foo src/");
|
|
126
|
-
const line = formatDriftNudge(hint);
|
|
127
|
-
expect(line).toMatch(/^\[unerr\] search_code\(/);
|
|
128
|
-
expect(line).toContain("search_code");
|
|
129
|
-
expect(line).not.toContain("drift("); // taxonomy prefix removed
|
|
130
|
-
expect(line.length).toBeLessThan(200);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
describe("nudge-state — session flag persistence", () => {
|
|
134
|
-
let tmpRoot;
|
|
135
|
-
beforeEach(() => {
|
|
136
|
-
tmpRoot = join(tmpdir(), `unerr-nudge-${Date.now()}-${Math.random()}`);
|
|
137
|
-
mkdirSync(join(tmpRoot, ".unerr"), { recursive: true });
|
|
138
|
-
_resetNudgeState(tmpRoot);
|
|
139
|
-
});
|
|
140
|
-
afterEach(() => {
|
|
141
|
-
try {
|
|
142
|
-
rmSync(tmpRoot, { recursive: true, force: true });
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
// ignore
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
it("reads default state for a fresh session", () => {
|
|
149
|
-
const s = readNudgeState(tmpRoot);
|
|
150
|
-
expect(s.tier0_emitted).toBe(false);
|
|
151
|
-
expect(s.tier1_emitted_kinds).toEqual([]);
|
|
152
|
-
expect(s.drift_count).toBe(0);
|
|
153
|
-
expect(s.tier2_emitted).toBe(false);
|
|
154
|
-
});
|
|
155
|
-
it("persists tier1 emissions per kind", () => {
|
|
156
|
-
updateNudgeState(tmpRoot, (s) => {
|
|
157
|
-
s.tier1_emitted_kinds.push("code_search");
|
|
158
|
-
s.drift_count = 1;
|
|
159
|
-
});
|
|
160
|
-
const s2 = readNudgeState(tmpRoot);
|
|
161
|
-
expect(s2.tier1_emitted_kinds).toEqual(["code_search"]);
|
|
162
|
-
expect(s2.drift_count).toBe(1);
|
|
163
|
-
});
|
|
164
|
-
it("markUnerrToolUsed resets drift_count and clears Tier-2", () => {
|
|
165
|
-
updateNudgeState(tmpRoot, (s) => {
|
|
166
|
-
s.drift_count = 5;
|
|
167
|
-
s.tier2_emitted = true;
|
|
168
|
-
});
|
|
169
|
-
markUnerrToolUsed(tmpRoot);
|
|
170
|
-
const s = readNudgeState(tmpRoot);
|
|
171
|
-
expect(s.drift_count).toBe(0);
|
|
172
|
-
expect(s.tier2_emitted).toBe(false);
|
|
173
|
-
expect(s.last_unerr_tool_at).toBeDefined();
|
|
174
|
-
});
|
|
175
|
-
it("readNudgeState recovers gracefully from corrupt state", () => {
|
|
176
|
-
// Make sure the state dir exists, then write garbage
|
|
177
|
-
mkdirSync(join(tmpRoot, ".unerr", "state"), { recursive: true });
|
|
178
|
-
writeFileSync(join(tmpRoot, ".unerr", "state", `nudge-pid-${process.pid}.flags`), "{not json", { flag: "w" });
|
|
179
|
-
// Should not throw
|
|
180
|
-
const s = readNudgeState(tmpRoot);
|
|
181
|
-
expect(s.tier0_emitted).toBe(false);
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
describe("anti-drift correctness — guarantees that protect MCP tool adoption", () => {
|
|
185
|
-
// These are the safety properties the new system MUST preserve.
|
|
186
|
-
it("every drift-positive command has a non-empty suggestion", () => {
|
|
187
|
-
const positives = [
|
|
188
|
-
"grep -r foo src/",
|
|
189
|
-
"rg pattern src/proxy/",
|
|
190
|
-
"find . -name '*.ts'",
|
|
191
|
-
"cat src/foo.ts",
|
|
192
|
-
"head src/bar.py",
|
|
193
|
-
"tail -50 src/baz.rs",
|
|
194
|
-
"ls -R src/",
|
|
195
|
-
];
|
|
196
|
-
for (const cmd of positives) {
|
|
197
|
-
const h = isDriftCommand(cmd);
|
|
198
|
-
expect(h).not.toBeNull();
|
|
199
|
-
expect(h?.suggest.length).toBeGreaterThan(20);
|
|
200
|
-
expect(h?.suggest).toMatch(/search_code|file_read|file_outline|get_/);
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
it("nudge text length is always shorter than ~10× a small command", () => {
|
|
204
|
-
// The drift nudge must be tight enough that even a 30-byte command
|
|
205
|
-
// gets a useful one-liner, not a paragraph.
|
|
206
|
-
const h = isDriftCommand("grep -r x src/");
|
|
207
|
-
expect(formatDriftNudge(h).length).toBeLessThan(150);
|
|
208
|
-
});
|
|
209
|
-
it("no drift-positive overlaps with build/test/install commands", () => {
|
|
210
|
-
// If we ever falsely flag these, Phase B rollout would break agent flow.
|
|
211
|
-
const benign = [
|
|
212
|
-
"pnpm test",
|
|
213
|
-
"pnpm install --frozen-lockfile",
|
|
214
|
-
"npm run build",
|
|
215
|
-
"cargo test",
|
|
216
|
-
"go test ./...",
|
|
217
|
-
"make all",
|
|
218
|
-
"docker build -t foo .",
|
|
219
|
-
"kubectl apply -f k8s.yaml",
|
|
220
|
-
];
|
|
221
|
-
for (const cmd of benign) {
|
|
222
|
-
expect(isDriftCommand(cmd)).toBeNull();
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
});
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Zero-Network Rewind — Phase 5.5 P5.5-TEST-07a
|
|
3
|
-
*
|
|
4
|
-
* Tests offline rewind via CozoDB + local git operations.
|
|
5
|
-
* Covers: file restoration, local ledger updates, timeline branching,
|
|
6
|
-
* and blast radius resolution (<200ms).
|
|
7
|
-
*/
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
12
|
-
import { offlineRewind } from "../tracking/offline-rewind.js";
|
|
13
|
-
import { ShadowLedger } from "../tracking/shadow-ledger.js";
|
|
14
|
-
vi.mock("../utils/git.js", () => ({
|
|
15
|
-
checkoutFile: vi.fn().mockResolvedValue(undefined),
|
|
16
|
-
getGit: vi.fn(),
|
|
17
|
-
isGitRepo: vi.fn().mockResolvedValue(true),
|
|
18
|
-
}));
|
|
19
|
-
class MockCozoGraphStore {
|
|
20
|
-
entities = [];
|
|
21
|
-
edges = [];
|
|
22
|
-
addEntity(entity) {
|
|
23
|
-
this.entities.push(entity);
|
|
24
|
-
}
|
|
25
|
-
addEdge(edge) {
|
|
26
|
-
this.edges.push(edge);
|
|
27
|
-
}
|
|
28
|
-
getEntitiesByFile(filePath) {
|
|
29
|
-
return this.entities
|
|
30
|
-
.filter((e) => e.file_path === filePath)
|
|
31
|
-
.map((e) => ({
|
|
32
|
-
...e,
|
|
33
|
-
risk_level: e.risk_level,
|
|
34
|
-
}));
|
|
35
|
-
}
|
|
36
|
-
getCallersOf(entityKey) {
|
|
37
|
-
return this.edges
|
|
38
|
-
.filter((e) => e.to_key === entityKey)
|
|
39
|
-
.map((e) => {
|
|
40
|
-
const entity = this.entities.find((ent) => ent.key === e.from_key);
|
|
41
|
-
return entity
|
|
42
|
-
? {
|
|
43
|
-
...entity,
|
|
44
|
-
risk_level: entity.risk_level,
|
|
45
|
-
}
|
|
46
|
-
: null;
|
|
47
|
-
})
|
|
48
|
-
.filter(Boolean);
|
|
49
|
-
}
|
|
50
|
-
clearDriftOverlay() {
|
|
51
|
-
/* no-op for tests */
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
let testDir;
|
|
55
|
-
let unerrDir;
|
|
56
|
-
let graph;
|
|
57
|
-
let ledger;
|
|
58
|
-
beforeEach(() => {
|
|
59
|
-
testDir = join(tmpdir(), `unerr-test-rewind-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
60
|
-
unerrDir = join(testDir, ".unerr");
|
|
61
|
-
mkdirSync(join(unerrDir, "ledger"), { recursive: true });
|
|
62
|
-
mkdirSync(join(unerrDir, "state"), { recursive: true });
|
|
63
|
-
graph = new MockCozoGraphStore();
|
|
64
|
-
ledger = new ShadowLedger(unerrDir);
|
|
65
|
-
});
|
|
66
|
-
afterEach(() => {
|
|
67
|
-
vi.restoreAllMocks();
|
|
68
|
-
try {
|
|
69
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
/* ignore */
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
function recordEntry(tool, branch, headSha, args) {
|
|
76
|
-
return ledger.record(tool, args ?? {}, {}, branch, headSha);
|
|
77
|
-
}
|
|
78
|
-
describe("Zero-Network Rewind (P5.5-TEST-07a)", () => {
|
|
79
|
-
it("returns error when target entry not found in local ledger", async () => {
|
|
80
|
-
const result = await offlineRewind({
|
|
81
|
-
targetEntryId: "nonexistent",
|
|
82
|
-
cwd: testDir,
|
|
83
|
-
unerrDir,
|
|
84
|
-
graph: graph,
|
|
85
|
-
ledger,
|
|
86
|
-
});
|
|
87
|
-
expect(result.status).toBe("error");
|
|
88
|
-
expect(result.errorMessage).toContain("not found");
|
|
89
|
-
});
|
|
90
|
-
it("dry run returns blast radius without applying changes", async () => {
|
|
91
|
-
const target = recordEntry("sync_local_diff", "main", "sha-target", {
|
|
92
|
-
files: ["src/ok.ts"],
|
|
93
|
-
});
|
|
94
|
-
recordEntry("sync_local_diff", "main", "sha-bad", {
|
|
95
|
-
files: ["src/bad.ts"],
|
|
96
|
-
});
|
|
97
|
-
const result = await offlineRewind({
|
|
98
|
-
targetEntryId: target.id,
|
|
99
|
-
cwd: testDir,
|
|
100
|
-
unerrDir,
|
|
101
|
-
graph: graph,
|
|
102
|
-
ledger,
|
|
103
|
-
dryRun: true,
|
|
104
|
-
});
|
|
105
|
-
expect(result.status).toBe("dry_run");
|
|
106
|
-
expect(result.rewindEntryId).toBeNull();
|
|
107
|
-
expect(result.filesRestored.length).toBe(0);
|
|
108
|
-
});
|
|
109
|
-
it("local shadow.jsonl updated with status: simulated", async () => {
|
|
110
|
-
const target = recordEntry("sync_local_diff", "main", "sha-111", {
|
|
111
|
-
files: ["src/a.ts"],
|
|
112
|
-
});
|
|
113
|
-
recordEntry("sync_local_diff", "main", "sha-222", {
|
|
114
|
-
files: ["src/b.ts"],
|
|
115
|
-
});
|
|
116
|
-
const result = await offlineRewind({
|
|
117
|
-
targetEntryId: target.id,
|
|
118
|
-
cwd: testDir,
|
|
119
|
-
unerrDir,
|
|
120
|
-
graph: graph,
|
|
121
|
-
ledger,
|
|
122
|
-
});
|
|
123
|
-
expect(result.status).toBe("simulated");
|
|
124
|
-
const allEntries = ledger.readAllEntries();
|
|
125
|
-
const rewindEntry = allEntries.find((e) => e.id === result.rewindEntryId);
|
|
126
|
-
expect(rewindEntry).toBeDefined();
|
|
127
|
-
expect(rewindEntry?.tool).toBe("revert_to_working_state");
|
|
128
|
-
expect((rewindEntry?.result_summary).rewind_status).toBe("simulated");
|
|
129
|
-
expect((rewindEntry?.args_summary).offline).toBe(true);
|
|
130
|
-
});
|
|
131
|
-
it("local timeline_branch incremented in branch_context.json", async () => {
|
|
132
|
-
const target = recordEntry("sync_local_diff", "main", "sha-aaa");
|
|
133
|
-
recordEntry("sync_local_diff", "main", "sha-bbb");
|
|
134
|
-
const result = await offlineRewind({
|
|
135
|
-
targetEntryId: target.id,
|
|
136
|
-
cwd: testDir,
|
|
137
|
-
unerrDir,
|
|
138
|
-
graph: graph,
|
|
139
|
-
ledger,
|
|
140
|
-
});
|
|
141
|
-
expect(result.timelineBranch).toBe(2);
|
|
142
|
-
const contextPath = join(unerrDir, "ledger", "branch_context.json");
|
|
143
|
-
expect(existsSync(contextPath)).toBe(true);
|
|
144
|
-
const context = JSON.parse(readFileSync(contextPath, "utf-8"));
|
|
145
|
-
expect(context.timeline_branch).toBe(2);
|
|
146
|
-
});
|
|
147
|
-
it("rewind produces a valid rewind entry", async () => {
|
|
148
|
-
const target = recordEntry("sync_local_diff", "main", "sha-ccc");
|
|
149
|
-
recordEntry("sync_local_diff", "main", "sha-ddd");
|
|
150
|
-
const result = await offlineRewind({
|
|
151
|
-
targetEntryId: target.id,
|
|
152
|
-
cwd: testDir,
|
|
153
|
-
unerrDir,
|
|
154
|
-
graph: graph,
|
|
155
|
-
ledger,
|
|
156
|
-
});
|
|
157
|
-
expect(result.status).toBe("simulated");
|
|
158
|
-
expect(result.rewindEntryId).toBeTruthy();
|
|
159
|
-
});
|
|
160
|
-
it("blast radius resolution uses CozoDB entities and callers", async () => {
|
|
161
|
-
graph.addEntity({
|
|
162
|
-
key: "fn-process",
|
|
163
|
-
kind: "function",
|
|
164
|
-
name: "processOrder",
|
|
165
|
-
file_path: "src/orders.ts",
|
|
166
|
-
start_line: 10,
|
|
167
|
-
signature: "processOrder()",
|
|
168
|
-
body: "",
|
|
169
|
-
fan_in: 5,
|
|
170
|
-
fan_out: 2,
|
|
171
|
-
risk_level: "high",
|
|
172
|
-
});
|
|
173
|
-
graph.addEntity({
|
|
174
|
-
key: "fn-checkout",
|
|
175
|
-
kind: "function",
|
|
176
|
-
name: "checkout",
|
|
177
|
-
file_path: "src/checkout.ts",
|
|
178
|
-
start_line: 1,
|
|
179
|
-
signature: "checkout()",
|
|
180
|
-
body: "",
|
|
181
|
-
fan_in: 3,
|
|
182
|
-
fan_out: 1,
|
|
183
|
-
risk_level: "medium",
|
|
184
|
-
});
|
|
185
|
-
graph.addEdge({
|
|
186
|
-
from_key: "fn-checkout",
|
|
187
|
-
to_key: "fn-process",
|
|
188
|
-
type: "calls",
|
|
189
|
-
});
|
|
190
|
-
const ledgerPath = join(unerrDir, "ledger", "shadow.jsonl");
|
|
191
|
-
const targetEntry = {
|
|
192
|
-
id: "tgt-blast",
|
|
193
|
-
ts: new Date(Date.now() - 2000).toISOString(),
|
|
194
|
-
tool: "sync_local_diff",
|
|
195
|
-
args_summary: { files: ["src/ok.ts"] },
|
|
196
|
-
result_summary: {},
|
|
197
|
-
branch: "main",
|
|
198
|
-
head_sha: "sha-tgt",
|
|
199
|
-
session_id: "test-session",
|
|
200
|
-
correlation_id: null,
|
|
201
|
-
};
|
|
202
|
-
const badEntry = {
|
|
203
|
-
id: "bad-blast",
|
|
204
|
-
ts: new Date(Date.now() - 1000).toISOString(),
|
|
205
|
-
tool: "sync_local_diff",
|
|
206
|
-
args_summary: { files: ["src/orders.ts"] },
|
|
207
|
-
result_summary: {},
|
|
208
|
-
branch: "main",
|
|
209
|
-
head_sha: "sha-bad",
|
|
210
|
-
session_id: "test-session",
|
|
211
|
-
correlation_id: null,
|
|
212
|
-
};
|
|
213
|
-
writeFileSync(ledgerPath, `${JSON.stringify(targetEntry)}\n${JSON.stringify(badEntry)}\n`);
|
|
214
|
-
const freshLedger = new ShadowLedger(unerrDir);
|
|
215
|
-
const result = await offlineRewind({
|
|
216
|
-
targetEntryId: "tgt-blast",
|
|
217
|
-
cwd: testDir,
|
|
218
|
-
unerrDir,
|
|
219
|
-
graph: graph,
|
|
220
|
-
ledger: freshLedger,
|
|
221
|
-
dryRun: true,
|
|
222
|
-
});
|
|
223
|
-
expect(result.status).toBe("dry_run");
|
|
224
|
-
expect(result.blastRadius.affectedEntities.length).toBe(1);
|
|
225
|
-
expect(result.blastRadius.affectedEntities[0]?.key).toBe("fn-process");
|
|
226
|
-
expect(result.blastRadius.affectedEntities[0]?.riskLevel).toBe("high");
|
|
227
|
-
expect(result.blastRadius.affectedCallers).toBe(1);
|
|
228
|
-
expect(result.blastRadius.resolvedInMs).toBeLessThan(200);
|
|
229
|
-
});
|
|
230
|
-
it("sequential offline rewinds increment timeline branch correctly", async () => {
|
|
231
|
-
const target = recordEntry("sync_local_diff", "main", "sha-seq-1");
|
|
232
|
-
recordEntry("sync_local_diff", "main", "sha-seq-2");
|
|
233
|
-
const r1 = await offlineRewind({
|
|
234
|
-
targetEntryId: target.id,
|
|
235
|
-
cwd: testDir,
|
|
236
|
-
unerrDir,
|
|
237
|
-
graph: graph,
|
|
238
|
-
ledger,
|
|
239
|
-
});
|
|
240
|
-
expect(r1.timelineBranch).toBe(2);
|
|
241
|
-
recordEntry("sync_local_diff", "main", "sha-seq-3");
|
|
242
|
-
const r2 = await offlineRewind({
|
|
243
|
-
targetEntryId: target.id,
|
|
244
|
-
cwd: testDir,
|
|
245
|
-
unerrDir,
|
|
246
|
-
graph: graph,
|
|
247
|
-
ledger,
|
|
248
|
-
});
|
|
249
|
-
expect(r2.timelineBranch).toBe(3);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ST-3a: Open threads — mark_blocker entries without a matching mark_resolution.
|
|
3
|
-
*/
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import { computeOpenThreads } from "../timeline/open-threads.js";
|
|
6
|
-
function blocker(id, text, ts = 0) {
|
|
7
|
-
return {
|
|
8
|
-
marker_id: id,
|
|
9
|
-
type: "mark_blocker",
|
|
10
|
-
text,
|
|
11
|
-
session_id: "s1",
|
|
12
|
-
turn_id: "t1",
|
|
13
|
-
ts,
|
|
14
|
-
blocker_ref: "",
|
|
15
|
-
file_path: "",
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
function resolution(refId, ts = 0) {
|
|
19
|
-
return {
|
|
20
|
-
marker_id: `res-${refId}`,
|
|
21
|
-
type: "mark_resolution",
|
|
22
|
-
text: `resolved ${refId}`,
|
|
23
|
-
session_id: "s1",
|
|
24
|
-
turn_id: "t1",
|
|
25
|
-
ts,
|
|
26
|
-
blocker_ref: refId,
|
|
27
|
-
file_path: "",
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
describe("computeOpenThreads", () => {
|
|
31
|
-
it("returns all blockers when no resolutions exist", () => {
|
|
32
|
-
const open = computeOpenThreads([
|
|
33
|
-
blocker("b1", "type error", 100),
|
|
34
|
-
blocker("b2", "auth flow stuck", 200),
|
|
35
|
-
]);
|
|
36
|
-
expect(open.map((t) => t.marker_id)).toEqual(["b2", "b1"]); // newest first
|
|
37
|
-
});
|
|
38
|
-
it("filters out blockers with a matching resolution", () => {
|
|
39
|
-
const open = computeOpenThreads([
|
|
40
|
-
blocker("b1", "type error", 100),
|
|
41
|
-
blocker("b2", "auth flow stuck", 200),
|
|
42
|
-
resolution("b1", 150),
|
|
43
|
-
]);
|
|
44
|
-
expect(open).toHaveLength(1);
|
|
45
|
-
expect(open[0]?.marker_id).toBe("b2");
|
|
46
|
-
});
|
|
47
|
-
it("returns empty when all blockers resolved", () => {
|
|
48
|
-
const open = computeOpenThreads([
|
|
49
|
-
blocker("b1", "x", 100),
|
|
50
|
-
blocker("b2", "y", 200),
|
|
51
|
-
resolution("b1", 300),
|
|
52
|
-
resolution("b2", 400),
|
|
53
|
-
]);
|
|
54
|
-
expect(open).toEqual([]);
|
|
55
|
-
});
|
|
56
|
-
it("ignores non-blocker / non-resolution markers", () => {
|
|
57
|
-
const open = computeOpenThreads([
|
|
58
|
-
{
|
|
59
|
-
marker_id: "i1",
|
|
60
|
-
type: "mark_intent",
|
|
61
|
-
text: "refactor",
|
|
62
|
-
session_id: "s1",
|
|
63
|
-
turn_id: "t1",
|
|
64
|
-
ts: 50,
|
|
65
|
-
blocker_ref: "",
|
|
66
|
-
file_path: "",
|
|
67
|
-
},
|
|
68
|
-
blocker("b1", "stuck", 100),
|
|
69
|
-
]);
|
|
70
|
-
expect(open).toHaveLength(1);
|
|
71
|
-
expect(open[0]?.marker_id).toBe("b1");
|
|
72
|
-
});
|
|
73
|
-
it("a resolution without blocker_ref does not silence any blocker", () => {
|
|
74
|
-
const open = computeOpenThreads([
|
|
75
|
-
blocker("b1", "x", 100),
|
|
76
|
-
{
|
|
77
|
-
marker_id: "r1",
|
|
78
|
-
type: "mark_resolution",
|
|
79
|
-
text: "?",
|
|
80
|
-
session_id: "s1",
|
|
81
|
-
turn_id: "t1",
|
|
82
|
-
ts: 200,
|
|
83
|
-
blocker_ref: "",
|
|
84
|
-
file_path: "",
|
|
85
|
-
},
|
|
86
|
-
]);
|
|
87
|
-
expect(open).toHaveLength(1);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { compressOutput, } from "../proxy/output-compressor.js";
|
|
3
|
-
const LARGE_DIFF = `diff --git a/src/proxy/proxy.ts b/src/proxy/proxy.ts
|
|
4
|
-
--- a/src/proxy/proxy.ts
|
|
5
|
-
+++ b/src/proxy/proxy.ts
|
|
6
|
-
@@ -10,6 +10,10 @@ import { join } from "node:path";
|
|
7
|
-
import { PidLock } from "./pid-lock.js";
|
|
8
|
-
+import { createLifecycleActor } from "./lifecycle-actor.js";
|
|
9
|
-
+import { createEnvelopePipeline } from "./response-envelope.js";
|
|
10
|
-
|
|
11
|
-
const log = {
|
|
12
|
-
info: (msg: string) => process.stderr.write(msg),
|
|
13
|
-
};
|
|
14
|
-
${Array.from({ length: 100 }, (_, i) => `+ line ${i}: added utility function`).join("\n")}
|
|
15
|
-
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
|
|
16
|
-
--- a/src/utils/helpers.ts
|
|
17
|
-
+++ b/src/utils/helpers.ts
|
|
18
|
-
@@ -1,3 +1,5 @@
|
|
19
|
-
+export function helper1() { return 1; }
|
|
20
|
-
+export function helper2() { return 2; }
|
|
21
|
-
${Array.from({ length: 50 }, (_, i) => ` unchanged line ${i}`).join("\n")}
|
|
22
|
-
diff --git a/src/tracking/drift-tracker.ts b/src/tracking/drift-tracker.ts
|
|
23
|
-
--- a/src/tracking/drift-tracker.ts
|
|
24
|
-
+++ b/src/tracking/drift-tracker.ts
|
|
25
|
-
@@ -5,3 +5,8 @@
|
|
26
|
-
+ERROR: TypeScript compilation failed
|
|
27
|
-
+TypeError: Cannot read property 'length' of undefined
|
|
28
|
-
${Array.from({ length: 30 }, (_, i) => `+ drift change ${i}`).join("\n")}`;
|
|
29
|
-
describe("compressOutput", () => {
|
|
30
|
-
it("passes through small outputs unchanged", () => {
|
|
31
|
-
const small = "hello world\nline 2\nline 3";
|
|
32
|
-
const result = compressOutput(small, { tokenBudget: 10000 });
|
|
33
|
-
expect(result.output).toBe(small);
|
|
34
|
-
expect(result.sectionsOmitted).toBe(0);
|
|
35
|
-
});
|
|
36
|
-
it("compresses large outputs below token budget", () => {
|
|
37
|
-
const result = compressOutput(LARGE_DIFF, { tokenBudget: 500 });
|
|
38
|
-
expect(result.compressedTokens).toBeLessThan(result.originalTokens);
|
|
39
|
-
expect(result.sectionsOmitted).toBeGreaterThan(0);
|
|
40
|
-
});
|
|
41
|
-
it("preserves error sections with highest priority", () => {
|
|
42
|
-
const result = compressOutput(LARGE_DIFF, { tokenBudget: 300 });
|
|
43
|
-
expect(result.output).toContain("ERROR");
|
|
44
|
-
expect(result.output).toContain("TypeError");
|
|
45
|
-
});
|
|
46
|
-
it("adds omission markers for removed sections", () => {
|
|
47
|
-
const result = compressOutput(LARGE_DIFF, { tokenBudget: 200 });
|
|
48
|
-
expect(result.output).toContain("[...");
|
|
49
|
-
expect(result.output).toContain("lines omitted");
|
|
50
|
-
});
|
|
51
|
-
it("annotates high-risk entity sections", () => {
|
|
52
|
-
const riskMap = new Map([
|
|
53
|
-
[
|
|
54
|
-
"src/proxy/proxy.ts::startProxy",
|
|
55
|
-
{
|
|
56
|
-
riskLevel: "high",
|
|
57
|
-
fanIn: 14,
|
|
58
|
-
isChokepoint: true,
|
|
59
|
-
conventions: ["camelCase"],
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
]);
|
|
63
|
-
const result = compressOutput(LARGE_DIFF, {
|
|
64
|
-
tokenBudget: 800,
|
|
65
|
-
entityRiskMap: riskMap,
|
|
66
|
-
});
|
|
67
|
-
expect(result.annotations.length).toBeGreaterThan(0);
|
|
68
|
-
if (result.annotations.some((a) => a.includes("HIGH_RISK"))) {
|
|
69
|
-
expect(result.output).toContain("[HIGH_RISK");
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
it("annotates chokepoint entities", () => {
|
|
73
|
-
const riskMap = new Map([
|
|
74
|
-
[
|
|
75
|
-
"src/proxy/proxy.ts::startProxy",
|
|
76
|
-
{ riskLevel: "high", fanIn: 20, isChokepoint: true },
|
|
77
|
-
],
|
|
78
|
-
]);
|
|
79
|
-
const result = compressOutput(LARGE_DIFF, {
|
|
80
|
-
tokenBudget: 800,
|
|
81
|
-
entityRiskMap: riskMap,
|
|
82
|
-
});
|
|
83
|
-
if (result.annotations.some((a) => a.includes("CHOKEPOINT"))) {
|
|
84
|
-
expect(result.output).toContain("[CHOKEPOINT]");
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
it("reports compression metrics", () => {
|
|
88
|
-
const result = compressOutput(LARGE_DIFF, { tokenBudget: 300 });
|
|
89
|
-
expect(result.originalTokens).toBeGreaterThan(0);
|
|
90
|
-
expect(result.compressedTokens).toBeGreaterThan(0);
|
|
91
|
-
expect(result.compressedTokens).toBeLessThanOrEqual(result.originalTokens);
|
|
92
|
-
});
|
|
93
|
-
});
|