@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,149 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for SessionEvents tracking and cumulative stats persistence (Task 1.4).
|
|
3
|
-
*
|
|
4
|
-
* Tests validate:
|
|
5
|
-
* - SessionEvents creation and counting
|
|
6
|
-
* - Event recording functions increment correct counters
|
|
7
|
-
* - totalCaughtEvents aggregation
|
|
8
|
-
* - Cumulative stats persistence and weekly reset
|
|
9
|
-
* - Cumulative stats accumulation across sessions
|
|
10
|
-
*/
|
|
11
|
-
import * as fs from "node:fs";
|
|
12
|
-
import * as os from "node:os";
|
|
13
|
-
import * as path from "node:path";
|
|
14
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
15
|
-
import { createSessionEvents, createSessionStats, loadCumulativeStats, persistCumulativeStats, recordChokepointWarning, recordCircularDep, recordDeadCodeReference, recordSignaturePreservation, recordViolation, totalCaughtEvents, } from "../proxy/session-stats.js";
|
|
16
|
-
// ── SessionEvents ─────────────────────────────────────────────────
|
|
17
|
-
describe("SessionEvents", () => {
|
|
18
|
-
it("creates zeroed events", () => {
|
|
19
|
-
const events = createSessionEvents();
|
|
20
|
-
expect(events.conventionViolationsCaught).toBe(0);
|
|
21
|
-
expect(events.chokepointWarningsIssued).toBe(0);
|
|
22
|
-
expect(events.circularDepsDetected).toBe(0);
|
|
23
|
-
expect(events.signaturePreservations).toBe(0);
|
|
24
|
-
expect(events.deadCodeReferences).toBe(0);
|
|
25
|
-
});
|
|
26
|
-
it("totalCaughtEvents sums all event types", () => {
|
|
27
|
-
const events = createSessionEvents();
|
|
28
|
-
events.conventionViolationsCaught = 3;
|
|
29
|
-
events.chokepointWarningsIssued = 2;
|
|
30
|
-
events.circularDepsDetected = 1;
|
|
31
|
-
events.signaturePreservations = 4;
|
|
32
|
-
events.deadCodeReferences = 5;
|
|
33
|
-
expect(totalCaughtEvents(events)).toBe(15);
|
|
34
|
-
});
|
|
35
|
-
it("totalCaughtEvents returns 0 for empty events", () => {
|
|
36
|
-
expect(totalCaughtEvents(createSessionEvents())).toBe(0);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
// ── Event Recording ───────────────────────────────────────────────
|
|
40
|
-
describe("Event recording functions", () => {
|
|
41
|
-
it("recordViolation increments both violationsCaught and conventionViolationsCaught", () => {
|
|
42
|
-
const stats = createSessionStats();
|
|
43
|
-
recordViolation(stats);
|
|
44
|
-
recordViolation(stats);
|
|
45
|
-
expect(stats.violationsCaught).toBe(2);
|
|
46
|
-
expect(stats.events.conventionViolationsCaught).toBe(2);
|
|
47
|
-
});
|
|
48
|
-
it("recordChokepointWarning increments chokepointWarningsIssued", () => {
|
|
49
|
-
const stats = createSessionStats();
|
|
50
|
-
recordChokepointWarning(stats);
|
|
51
|
-
expect(stats.events.chokepointWarningsIssued).toBe(1);
|
|
52
|
-
});
|
|
53
|
-
it("recordCircularDep increments circularDepsDetected", () => {
|
|
54
|
-
const stats = createSessionStats();
|
|
55
|
-
recordCircularDep(stats);
|
|
56
|
-
recordCircularDep(stats);
|
|
57
|
-
recordCircularDep(stats);
|
|
58
|
-
expect(stats.events.circularDepsDetected).toBe(3);
|
|
59
|
-
});
|
|
60
|
-
it("recordSignaturePreservation increments signaturePreservations", () => {
|
|
61
|
-
const stats = createSessionStats();
|
|
62
|
-
recordSignaturePreservation(stats);
|
|
63
|
-
expect(stats.events.signaturePreservations).toBe(1);
|
|
64
|
-
});
|
|
65
|
-
it("recordDeadCodeReference increments deadCodeReferences", () => {
|
|
66
|
-
const stats = createSessionStats();
|
|
67
|
-
recordDeadCodeReference(stats);
|
|
68
|
-
recordDeadCodeReference(stats);
|
|
69
|
-
expect(stats.events.deadCodeReferences).toBe(2);
|
|
70
|
-
});
|
|
71
|
-
it("events are embedded in SessionStats", () => {
|
|
72
|
-
const stats = createSessionStats();
|
|
73
|
-
recordViolation(stats);
|
|
74
|
-
recordChokepointWarning(stats);
|
|
75
|
-
recordCircularDep(stats);
|
|
76
|
-
expect(totalCaughtEvents(stats.events)).toBe(3);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
// ── Cumulative Stats ──────────────────────────────────────────────
|
|
80
|
-
describe("Cumulative stats persistence", () => {
|
|
81
|
-
let origHome;
|
|
82
|
-
let tmpDir;
|
|
83
|
-
beforeEach(() => {
|
|
84
|
-
tmpDir = path.join(os.tmpdir(), `unerr-cumulative-test-${Date.now()}`);
|
|
85
|
-
fs.mkdirSync(path.join(tmpDir, ".unerr"), { recursive: true });
|
|
86
|
-
origHome = process.env.HOME;
|
|
87
|
-
process.env.HOME = tmpDir;
|
|
88
|
-
});
|
|
89
|
-
afterEach(() => {
|
|
90
|
-
process.env.HOME = origHome;
|
|
91
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
92
|
-
});
|
|
93
|
-
it("returns zeroed cumulative stats on first load", () => {
|
|
94
|
-
const cumulative = loadCumulativeStats();
|
|
95
|
-
expect(cumulative.totalTokensSaved).toBe(0);
|
|
96
|
-
expect(cumulative.totalDollarsSaved).toBe(0);
|
|
97
|
-
expect(cumulative.totalSessions).toBe(0);
|
|
98
|
-
expect(cumulative.violationsCaughtAllTime).toBe(0);
|
|
99
|
-
expect(cumulative.chokepointWarningsAllTime).toBe(0);
|
|
100
|
-
expect(cumulative.weekStart).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
101
|
-
});
|
|
102
|
-
it("persists and accumulates session stats", () => {
|
|
103
|
-
const stats1 = createSessionStats();
|
|
104
|
-
stats1.toolCallsLocal = 50;
|
|
105
|
-
stats1.estimatedTokensSaved = 160000;
|
|
106
|
-
stats1.events.conventionViolationsCaught = 3;
|
|
107
|
-
stats1.events.chokepointWarningsIssued = 1;
|
|
108
|
-
const c1 = persistCumulativeStats(stats1);
|
|
109
|
-
expect(c1.totalSessions).toBe(1);
|
|
110
|
-
expect(c1.totalTokensSaved).toBe(160000);
|
|
111
|
-
expect(c1.violationsCaughtAllTime).toBe(4); // 3 + 1
|
|
112
|
-
// Second session
|
|
113
|
-
const stats2 = createSessionStats();
|
|
114
|
-
stats2.toolCallsLocal = 30;
|
|
115
|
-
stats2.estimatedTokensSaved = 96000;
|
|
116
|
-
stats2.events.conventionViolationsCaught = 2;
|
|
117
|
-
const c2 = persistCumulativeStats(stats2);
|
|
118
|
-
expect(c2.totalSessions).toBe(2);
|
|
119
|
-
expect(c2.totalTokensSaved).toBe(256000);
|
|
120
|
-
expect(c2.violationsCaughtAllTime).toBe(6);
|
|
121
|
-
});
|
|
122
|
-
it("resets on new week", () => {
|
|
123
|
-
// Write stats with a different week
|
|
124
|
-
const filePath = path.join(tmpDir, ".unerr", "cumulative-stats.json");
|
|
125
|
-
fs.writeFileSync(filePath, JSON.stringify({
|
|
126
|
-
totalTokensSaved: 500000,
|
|
127
|
-
totalDollarsSaved: 30.0,
|
|
128
|
-
totalSessions: 10,
|
|
129
|
-
weekStart: "2025-01-06", // Old week
|
|
130
|
-
violationsCaughtAllTime: 50,
|
|
131
|
-
chokepointWarningsAllTime: 20,
|
|
132
|
-
}));
|
|
133
|
-
const cumulative = loadCumulativeStats();
|
|
134
|
-
// Should be reset since weekStart doesn't match current
|
|
135
|
-
expect(cumulative.totalSessions).toBe(0);
|
|
136
|
-
expect(cumulative.totalTokensSaved).toBe(0);
|
|
137
|
-
});
|
|
138
|
-
it("writes cumulative file to ~/.unerr/", () => {
|
|
139
|
-
const stats = createSessionStats();
|
|
140
|
-
stats.toolCallsLocal = 10;
|
|
141
|
-
stats.estimatedTokensSaved = 32000;
|
|
142
|
-
persistCumulativeStats(stats);
|
|
143
|
-
const filePath = path.join(tmpDir, ".unerr", "cumulative-stats.json");
|
|
144
|
-
expect(fs.existsSync(filePath)).toBe(true);
|
|
145
|
-
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
146
|
-
expect(data.totalSessions).toBe(1);
|
|
147
|
-
expect(data.totalTokensSaved).toBe(32000);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer 6 Sprint FE-E — session legend tracker.
|
|
3
|
-
*/
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import { createSessionLegendTracker } from "../proxy/session-legend.js";
|
|
6
|
-
describe("SessionLegendTracker", () => {
|
|
7
|
-
it("consumes columnar legend only once until invalidated", () => {
|
|
8
|
-
const L = createSessionLegendTracker();
|
|
9
|
-
expect(L.consumeColumnarLegend()).toBe(true);
|
|
10
|
-
expect(L.consumeColumnarLegend()).toBe(false);
|
|
11
|
-
L.invalidateAll();
|
|
12
|
-
expect(L.consumeColumnarLegend()).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
it("invalidateAll resets columnar channel", () => {
|
|
15
|
-
const L = createSessionLegendTracker();
|
|
16
|
-
expect(L.consumeColumnarLegend()).toBe(true);
|
|
17
|
-
L.invalidateAll();
|
|
18
|
-
expect(L.consumeColumnarLegend()).toBe(true);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { generateSessionResumePayload } from "../proxy/session-persistence.js";
|
|
6
|
-
function writeLastSession(unerrDir, record) {
|
|
7
|
-
const stateDir = join(unerrDir, "state");
|
|
8
|
-
mkdirSync(stateDir, { recursive: true });
|
|
9
|
-
writeFileSync(join(stateDir, "last_session.json"), JSON.stringify(record), "utf-8");
|
|
10
|
-
}
|
|
11
|
-
function makeSessionRecord(overrides = {}) {
|
|
12
|
-
return {
|
|
13
|
-
session_id: "test-session",
|
|
14
|
-
written_at: new Date().toISOString(),
|
|
15
|
-
started_at: new Date(Date.now() - 3600000).toISOString(),
|
|
16
|
-
ended_at: new Date(Date.now() - 60000).toISOString(), // 1 min ago
|
|
17
|
-
duration_ms: 3540000,
|
|
18
|
-
tool_calls: 25,
|
|
19
|
-
chains: 8,
|
|
20
|
-
files_modified: ["src/auth.ts", "src/proxy.ts", "src/config.ts"],
|
|
21
|
-
entities_touched: ["src/auth.ts::login", "src/proxy.ts::startProxy"],
|
|
22
|
-
tools_used: { get_function: 10, get_callers: 5, file_read: 10 },
|
|
23
|
-
feature_areas: ["src/auth"],
|
|
24
|
-
facts_recorded: 2,
|
|
25
|
-
facts_surfaced: ["fact-1", "fact-2"],
|
|
26
|
-
revert_count: 1,
|
|
27
|
-
rot_score: 0.2,
|
|
28
|
-
token_estimate: 15000,
|
|
29
|
-
branch: "main",
|
|
30
|
-
...overrides,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
describe("session-persistence", () => {
|
|
34
|
-
let testDir;
|
|
35
|
-
beforeEach(() => {
|
|
36
|
-
testDir = join(tmpdir(), `unerr-persist-${Date.now()}`);
|
|
37
|
-
mkdirSync(testDir, { recursive: true });
|
|
38
|
-
});
|
|
39
|
-
afterEach(() => {
|
|
40
|
-
try {
|
|
41
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
// best-effort cleanup
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
describe("generateSessionResumePayload", () => {
|
|
48
|
-
it("returns payload for recent session", async () => {
|
|
49
|
-
const record = makeSessionRecord();
|
|
50
|
-
writeLastSession(testDir, record);
|
|
51
|
-
const payload = await generateSessionResumePayload(testDir);
|
|
52
|
-
expect(payload).not.toBeNull();
|
|
53
|
-
expect(payload.session_resumed).toBe(true);
|
|
54
|
-
expect(payload.previous_session.session_id).toBe("test-session");
|
|
55
|
-
expect(payload.previous_session.tool_calls).toBe(25);
|
|
56
|
-
expect(payload.continuity.staleness).toBe("fresh");
|
|
57
|
-
expect(payload.continuity.hot_files.length).toBeGreaterThan(0);
|
|
58
|
-
});
|
|
59
|
-
it("returns null when no prior session", async () => {
|
|
60
|
-
const payload = await generateSessionResumePayload(testDir);
|
|
61
|
-
expect(payload).toBeNull();
|
|
62
|
-
});
|
|
63
|
-
it("returns null for sessions older than 24h", async () => {
|
|
64
|
-
const record = makeSessionRecord({
|
|
65
|
-
ended_at: new Date(Date.now() - 25 * 3600000).toISOString(),
|
|
66
|
-
});
|
|
67
|
-
writeLastSession(testDir, record);
|
|
68
|
-
const payload = await generateSessionResumePayload(testDir);
|
|
69
|
-
expect(payload).toBeNull();
|
|
70
|
-
});
|
|
71
|
-
it("marks staleness as 'warm' for sessions 4-24h old", async () => {
|
|
72
|
-
const record = makeSessionRecord({
|
|
73
|
-
ended_at: new Date(Date.now() - 8 * 3600000).toISOString(),
|
|
74
|
-
});
|
|
75
|
-
writeLastSession(testDir, record);
|
|
76
|
-
const payload = await generateSessionResumePayload(testDir);
|
|
77
|
-
expect(payload).not.toBeNull();
|
|
78
|
-
expect(payload.continuity.staleness).toBe("warm");
|
|
79
|
-
});
|
|
80
|
-
it("includes revert warning in incomplete hint", async () => {
|
|
81
|
-
const record = makeSessionRecord({ revert_count: 3 });
|
|
82
|
-
writeLastSession(testDir, record);
|
|
83
|
-
const payload = await generateSessionResumePayload(testDir);
|
|
84
|
-
expect(payload.continuity.incomplete_hint).toContain("revert");
|
|
85
|
-
});
|
|
86
|
-
it("includes hot files from previous session", async () => {
|
|
87
|
-
const record = makeSessionRecord({
|
|
88
|
-
files_modified: ["src/a.ts", "src/b.ts", "src/c.ts"],
|
|
89
|
-
});
|
|
90
|
-
writeLastSession(testDir, record);
|
|
91
|
-
const payload = await generateSessionResumePayload(testDir);
|
|
92
|
-
expect(payload.continuity.hot_files).toContain("src/a.ts");
|
|
93
|
-
});
|
|
94
|
-
it("recalls facts when factStore is provided", async () => {
|
|
95
|
-
const record = makeSessionRecord();
|
|
96
|
-
writeLastSession(testDir, record);
|
|
97
|
-
const mockFactStore = {
|
|
98
|
-
async recallByScope(scope) {
|
|
99
|
-
if (scope === "src/auth.ts") {
|
|
100
|
-
return [
|
|
101
|
-
{
|
|
102
|
-
fact_id: "f1",
|
|
103
|
-
fact_type: "semantic",
|
|
104
|
-
scope: "src/auth.ts",
|
|
105
|
-
subject: "auth",
|
|
106
|
-
content: "Auth uses JWT",
|
|
107
|
-
base_confidence: 0.9,
|
|
108
|
-
effective_confidence: 0.85,
|
|
109
|
-
reinforcement_count: 3,
|
|
110
|
-
created_at: Date.now(),
|
|
111
|
-
last_reinforced_at: Date.now(),
|
|
112
|
-
last_contradicted_at: 0,
|
|
113
|
-
source: "agent_explicit",
|
|
114
|
-
},
|
|
115
|
-
];
|
|
116
|
-
}
|
|
117
|
-
return [];
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
const payload = await generateSessionResumePayload(testDir, mockFactStore);
|
|
121
|
-
expect(payload.recalled_facts.length).toBeGreaterThan(0);
|
|
122
|
-
expect(payload.recalled_facts[0].content).toBe("Auth uses JWT");
|
|
123
|
-
});
|
|
124
|
-
it("returns empty recalled_facts when factStore is null", async () => {
|
|
125
|
-
const record = makeSessionRecord();
|
|
126
|
-
writeLastSession(testDir, record);
|
|
127
|
-
const payload = await generateSessionResumePayload(testDir, null);
|
|
128
|
-
expect(payload.recalled_facts).toEqual([]);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
});
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { formatSessionResumeBlock, } from "../proxy/session-persistence.js";
|
|
3
|
-
function makePayload(overrides) {
|
|
4
|
-
return {
|
|
5
|
-
session_resumed: true,
|
|
6
|
-
previous_session: {
|
|
7
|
-
session_id: "test-sess",
|
|
8
|
-
duration_ms: 300000,
|
|
9
|
-
tool_calls: 15,
|
|
10
|
-
chains: 3,
|
|
11
|
-
files_modified: ["src/auth.ts", "src/api.ts"],
|
|
12
|
-
entities_touched: ["login", "register"],
|
|
13
|
-
tools_used: { file_read: 8, get_function: 5 },
|
|
14
|
-
feature_areas: ["auth"],
|
|
15
|
-
revert_count: 0,
|
|
16
|
-
facts_recorded: 2,
|
|
17
|
-
branch: "main",
|
|
18
|
-
ended_at: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
|
19
|
-
},
|
|
20
|
-
continuity: {
|
|
21
|
-
hot_files: ["src/auth.ts", "src/api.ts"],
|
|
22
|
-
incomplete_hint: "Continuing work on src/auth.ts, src/api.ts",
|
|
23
|
-
staleness: "fresh",
|
|
24
|
-
},
|
|
25
|
-
recalled_facts: [
|
|
26
|
-
{
|
|
27
|
-
fact_id: "f1",
|
|
28
|
-
type: "negative",
|
|
29
|
-
content: "Never store passwords in plain text",
|
|
30
|
-
confidence: 0.95,
|
|
31
|
-
source: "negative_knowledge",
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
fact_id: "f2",
|
|
35
|
-
type: "semantic",
|
|
36
|
-
content: "Auth uses bcrypt for hashing",
|
|
37
|
-
confidence: 0.8,
|
|
38
|
-
source: "agent_explicit",
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
decayed_since_last_session: [],
|
|
42
|
-
...overrides,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
describe("formatSessionResumeBlock", () => {
|
|
46
|
-
it("formats a complete resume block with elapsed time and facts", () => {
|
|
47
|
-
const block = formatSessionResumeBlock(makePayload());
|
|
48
|
-
expect(block).toContain("[unerr:session-resume]");
|
|
49
|
-
expect(block).toContain("2h ago");
|
|
50
|
-
expect(block).toContain("src/auth.ts");
|
|
51
|
-
expect(block).toContain("Never store passwords in plain text");
|
|
52
|
-
expect(block).toContain("95%");
|
|
53
|
-
});
|
|
54
|
-
it("returns empty string for null payload", () => {
|
|
55
|
-
expect(formatSessionResumeBlock(null)).toBe("");
|
|
56
|
-
});
|
|
57
|
-
it("filters out low-confidence facts", () => {
|
|
58
|
-
const payload = makePayload({
|
|
59
|
-
recalled_facts: [
|
|
60
|
-
{
|
|
61
|
-
fact_id: "f1",
|
|
62
|
-
type: "semantic",
|
|
63
|
-
content: "Low confidence fact",
|
|
64
|
-
confidence: 0.3,
|
|
65
|
-
source: "session_analysis",
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
});
|
|
69
|
-
const block = formatSessionResumeBlock(payload);
|
|
70
|
-
expect(block).not.toContain("Low confidence fact");
|
|
71
|
-
});
|
|
72
|
-
it("truncates to 500 chars max", () => {
|
|
73
|
-
const payload = makePayload({
|
|
74
|
-
recalled_facts: Array.from({ length: 20 }, (_, i) => ({
|
|
75
|
-
fact_id: `f${i}`,
|
|
76
|
-
type: "semantic",
|
|
77
|
-
content: `Very long fact number ${i} with lots of extra detail that pads the content significantly`,
|
|
78
|
-
confidence: 0.95,
|
|
79
|
-
source: "agent_explicit",
|
|
80
|
-
})),
|
|
81
|
-
});
|
|
82
|
-
const block = formatSessionResumeBlock(payload);
|
|
83
|
-
expect(block.length).toBeLessThanOrEqual(500);
|
|
84
|
-
});
|
|
85
|
-
it("includes incomplete hint when meaningful", () => {
|
|
86
|
-
const payload = makePayload({
|
|
87
|
-
continuity: {
|
|
88
|
-
hot_files: ["src/auth.ts"],
|
|
89
|
-
incomplete_hint: "2 revert(s) in last session — approach may need rethinking",
|
|
90
|
-
staleness: "fresh",
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
const block = formatSessionResumeBlock(payload);
|
|
94
|
-
expect(block).toContain("revert(s)");
|
|
95
|
-
});
|
|
96
|
-
it("skips generic incomplete hint", () => {
|
|
97
|
-
const payload = makePayload({
|
|
98
|
-
continuity: {
|
|
99
|
-
hot_files: ["src/auth.ts"],
|
|
100
|
-
incomplete_hint: "No specific continuity context",
|
|
101
|
-
staleness: "fresh",
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
const block = formatSessionResumeBlock(payload);
|
|
105
|
-
expect(block).not.toContain("No specific continuity context");
|
|
106
|
-
});
|
|
107
|
-
});
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { generateSessionResume } from "../proxy/session-resume.js";
|
|
3
|
-
const MOCK_ENTRIES = [
|
|
4
|
-
{
|
|
5
|
-
id: "e1",
|
|
6
|
-
ts: "2026-04-30T10:00:00Z",
|
|
7
|
-
tool: "get_function",
|
|
8
|
-
args_summary: { key: "src/auth.ts::login" },
|
|
9
|
-
result_summary: {},
|
|
10
|
-
session_id: "s1",
|
|
11
|
-
branch: "main",
|
|
12
|
-
head_sha: "aaa",
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
id: "e2",
|
|
16
|
-
ts: "2026-04-30T10:01:00Z",
|
|
17
|
-
tool: "sync_local_diff",
|
|
18
|
-
args_summary: { files: ["src/auth.ts", "src/utils/token.ts"] },
|
|
19
|
-
result_summary: {},
|
|
20
|
-
session_id: "s1",
|
|
21
|
-
branch: "main",
|
|
22
|
-
head_sha: "bbb",
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: "e3",
|
|
26
|
-
ts: "2026-04-30T10:05:00Z",
|
|
27
|
-
tool: "check_rules",
|
|
28
|
-
args_summary: { key: "src/auth.ts" },
|
|
29
|
-
result_summary: {},
|
|
30
|
-
session_id: "s1",
|
|
31
|
-
branch: "main",
|
|
32
|
-
head_sha: "ccc",
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
id: "e4",
|
|
36
|
-
ts: "2026-04-30T10:10:00Z",
|
|
37
|
-
tool: "get_callers",
|
|
38
|
-
args_summary: { key: "src/auth.ts::login" },
|
|
39
|
-
result_summary: {},
|
|
40
|
-
session_id: "s1",
|
|
41
|
-
branch: "main",
|
|
42
|
-
head_sha: "ddd",
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
id: "e5",
|
|
46
|
-
ts: "2026-04-30T10:15:00Z",
|
|
47
|
-
tool: "sync_local_diff",
|
|
48
|
-
args_summary: { files: ["src/auth.ts"] },
|
|
49
|
-
result_summary: { commit_sha: "fff" },
|
|
50
|
-
session_id: "s1",
|
|
51
|
-
branch: "feature/auth",
|
|
52
|
-
head_sha: "eee",
|
|
53
|
-
},
|
|
54
|
-
];
|
|
55
|
-
describe("generateSessionResume", () => {
|
|
56
|
-
it("generates resume from shadow ledger entries", () => {
|
|
57
|
-
const result = generateSessionResume(MOCK_ENTRIES);
|
|
58
|
-
expect(result).not.toBeNull();
|
|
59
|
-
expect(result?.summary).toContain("tool calls");
|
|
60
|
-
expect(result?.filesModified).toContain("src/auth.ts");
|
|
61
|
-
expect(result?.toolsUsed.sync_local_diff).toBe(2);
|
|
62
|
-
expect(result?.toolsUsed.get_function).toBe(1);
|
|
63
|
-
});
|
|
64
|
-
it("detects last branch", () => {
|
|
65
|
-
const result = generateSessionResume(MOCK_ENTRIES);
|
|
66
|
-
expect(result?.lastBranch).toBe("feature/auth");
|
|
67
|
-
});
|
|
68
|
-
it("calculates session duration", () => {
|
|
69
|
-
const result = generateSessionResume(MOCK_ENTRIES);
|
|
70
|
-
expect(result?.sessionDurationMs).toBe(15 * 60 * 1000);
|
|
71
|
-
});
|
|
72
|
-
it("tracks incomplete entities (modified but not committed)", () => {
|
|
73
|
-
const noCommitEntries = MOCK_ENTRIES.slice(0, 4);
|
|
74
|
-
const result = generateSessionResume(noCommitEntries);
|
|
75
|
-
expect(result?.incompleteEntities.length).toBeGreaterThan(0);
|
|
76
|
-
expect(result?.incompleteEntities).toContain("src/auth.ts");
|
|
77
|
-
});
|
|
78
|
-
it("returns null for empty entries", () => {
|
|
79
|
-
expect(generateSessionResume([])).toBeNull();
|
|
80
|
-
});
|
|
81
|
-
it("limits file list in summary", () => {
|
|
82
|
-
const manyFiles = Array.from({ length: 20 }, (_, i) => `src/file${i}.ts`);
|
|
83
|
-
const entries = [
|
|
84
|
-
{
|
|
85
|
-
id: "e1",
|
|
86
|
-
ts: "2026-04-30T10:00:00Z",
|
|
87
|
-
tool: "sync_local_diff",
|
|
88
|
-
args_summary: { files: manyFiles },
|
|
89
|
-
result_summary: {},
|
|
90
|
-
session_id: "s1",
|
|
91
|
-
},
|
|
92
|
-
];
|
|
93
|
-
const result = generateSessionResume(entries);
|
|
94
|
-
expect(result?.summary).toContain("+");
|
|
95
|
-
expect(result?.filesModified.length).toBe(20);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { closeMetricsStore, openMetricsStore, } from "../tracking/metrics-store.js";
|
|
6
|
-
import { readLastSession, writeSessionSummary, } from "../tracking/session-summary-writer.js";
|
|
7
|
-
function makeEntry(overrides = {}) {
|
|
8
|
-
return {
|
|
9
|
-
id: `entry-${Math.random().toString(36).slice(2, 8)}`,
|
|
10
|
-
ts: new Date().toISOString(),
|
|
11
|
-
tool: "get_function",
|
|
12
|
-
args_summary: {},
|
|
13
|
-
result_summary: {},
|
|
14
|
-
branch: "main",
|
|
15
|
-
head_sha: "abc123",
|
|
16
|
-
session_id: "test-session",
|
|
17
|
-
correlation_id: null,
|
|
18
|
-
...overrides,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
describe("session-summary-writer", () => {
|
|
22
|
-
let testDir;
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
testDir = join(tmpdir(), `unerr-test-${Date.now()}`);
|
|
25
|
-
mkdirSync(testDir, { recursive: true });
|
|
26
|
-
});
|
|
27
|
-
afterEach(() => {
|
|
28
|
-
try {
|
|
29
|
-
closeMetricsStore(testDir);
|
|
30
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
// cleanup best-effort
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
describe("writeSessionSummary", () => {
|
|
37
|
-
it("upserts session summary row into metrics.db", () => {
|
|
38
|
-
const ctx = {
|
|
39
|
-
sessionId: "test-session-001",
|
|
40
|
-
entries: [
|
|
41
|
-
makeEntry({
|
|
42
|
-
tool: "get_function",
|
|
43
|
-
args_summary: { key: "src/a.ts::fn" },
|
|
44
|
-
}),
|
|
45
|
-
makeEntry({
|
|
46
|
-
tool: "get_callers",
|
|
47
|
-
args_summary: { key: "src/a.ts::fn" },
|
|
48
|
-
}),
|
|
49
|
-
makeEntry({ tool: "record_fact" }),
|
|
50
|
-
],
|
|
51
|
-
factsRecordedIds: ["fact-abc"],
|
|
52
|
-
factsSurfacedIds: ["fact-xyz"],
|
|
53
|
-
rotScore: 0.15,
|
|
54
|
-
tokenEstimate: 8000,
|
|
55
|
-
};
|
|
56
|
-
const record = writeSessionSummary(testDir, ctx);
|
|
57
|
-
expect(record).not.toBeNull();
|
|
58
|
-
expect(record.session_id).toBe("test-session-001");
|
|
59
|
-
expect(record.tool_calls).toBe(3);
|
|
60
|
-
expect(record.facts_recorded).toBe(1);
|
|
61
|
-
expect(record.rot_score).toBe(0.15);
|
|
62
|
-
expect(record.token_estimate).toBe(8000);
|
|
63
|
-
const row = openMetricsStore(testDir).sessionSummary("test-session-001");
|
|
64
|
-
expect(row).not.toBeNull();
|
|
65
|
-
expect(row.session_id).toBe("test-session-001");
|
|
66
|
-
expect(row.tool_calls).toBe(3);
|
|
67
|
-
expect(JSON.parse(row.files_modified)).toEqual(expect.arrayContaining([]));
|
|
68
|
-
});
|
|
69
|
-
it("writes last_session.json pointer", () => {
|
|
70
|
-
const ctx = {
|
|
71
|
-
sessionId: "sess-pointer-test",
|
|
72
|
-
entries: [makeEntry()],
|
|
73
|
-
factsRecordedIds: [],
|
|
74
|
-
factsSurfacedIds: [],
|
|
75
|
-
rotScore: 0,
|
|
76
|
-
tokenEstimate: 1000,
|
|
77
|
-
};
|
|
78
|
-
writeSessionSummary(testDir, ctx);
|
|
79
|
-
const pointerPath = join(testDir, "state", "last_session.json");
|
|
80
|
-
expect(existsSync(pointerPath)).toBe(true);
|
|
81
|
-
const pointer = JSON.parse(readFileSync(pointerPath, "utf-8"));
|
|
82
|
-
expect(pointer.session_id).toBe("sess-pointer-test");
|
|
83
|
-
});
|
|
84
|
-
it("returns null for empty entries", () => {
|
|
85
|
-
const ctx = {
|
|
86
|
-
sessionId: "empty",
|
|
87
|
-
entries: [],
|
|
88
|
-
factsRecordedIds: [],
|
|
89
|
-
factsSurfacedIds: [],
|
|
90
|
-
rotScore: 0,
|
|
91
|
-
tokenEstimate: 0,
|
|
92
|
-
};
|
|
93
|
-
const record = writeSessionSummary(testDir, ctx);
|
|
94
|
-
expect(record).toBeNull();
|
|
95
|
-
});
|
|
96
|
-
it("extracts files and entities from entries", () => {
|
|
97
|
-
const ctx = {
|
|
98
|
-
sessionId: "extraction-test",
|
|
99
|
-
entries: [
|
|
100
|
-
makeEntry({ args_summary: { file_path: "src/auth.ts" } }),
|
|
101
|
-
makeEntry({ args_summary: { key: "src/proxy.ts::startProxy" } }),
|
|
102
|
-
],
|
|
103
|
-
factsRecordedIds: [],
|
|
104
|
-
factsSurfacedIds: [],
|
|
105
|
-
rotScore: 0,
|
|
106
|
-
tokenEstimate: 2000,
|
|
107
|
-
};
|
|
108
|
-
const record = writeSessionSummary(testDir, ctx);
|
|
109
|
-
expect(record.files_modified).toContain("src/auth.ts");
|
|
110
|
-
expect(record.entities_touched).toContain("src/proxy.ts::startProxy");
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
describe("readLastSession", () => {
|
|
114
|
-
it("reads written last_session.json", () => {
|
|
115
|
-
const ctx = {
|
|
116
|
-
sessionId: "read-test",
|
|
117
|
-
entries: [makeEntry(), makeEntry()],
|
|
118
|
-
factsRecordedIds: ["f1"],
|
|
119
|
-
factsSurfacedIds: [],
|
|
120
|
-
rotScore: 0.5,
|
|
121
|
-
tokenEstimate: 5000,
|
|
122
|
-
};
|
|
123
|
-
writeSessionSummary(testDir, ctx);
|
|
124
|
-
const last = readLastSession(testDir);
|
|
125
|
-
expect(last).not.toBeNull();
|
|
126
|
-
expect(last.session_id).toBe("read-test");
|
|
127
|
-
expect(last.tool_calls).toBe(2);
|
|
128
|
-
});
|
|
129
|
-
it("returns null when no prior session", () => {
|
|
130
|
-
const last = readLastSession(testDir);
|
|
131
|
-
expect(last).toBeNull();
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
});
|