@monotykamary/pi-vcc 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/npm-shrinkwrap.json +2 -2
  2. package/package.json +1 -1
  3. package/__tests__/helpers.ts +0 -135
  4. package/__tests__/root-causes.test.ts +0 -858
  5. package/demo.gif +0 -0
  6. package/knip.json +0 -4
  7. package/scripts/audit-sessions.ts +0 -88
  8. package/scripts/benchmark-real-sessions.ts +0 -25
  9. package/scripts/compare-before-after.ts +0 -36
  10. package/scripts/dump-branch-output.ts +0 -20
  11. package/stress-debug.ts +0 -202
  12. package/stress-degradation-curve.ts +0 -251
  13. package/tests/australian-government.test.ts +0 -479
  14. package/tests/before-compact-hook.test.ts +0 -185
  15. package/tests/before-compact.test.ts +0 -227
  16. package/tests/breadcrumbs.test.ts +0 -312
  17. package/tests/brief.test.ts +0 -215
  18. package/tests/build-sections.test.ts +0 -59
  19. package/tests/compile.test.ts +0 -89
  20. package/tests/content.test.ts +0 -66
  21. package/tests/deep-extraction.test.ts +0 -322
  22. package/tests/extract-goals.test.ts +0 -86
  23. package/tests/extract-preferences.test.ts +0 -30
  24. package/tests/filter-noise.test.ts +0 -61
  25. package/tests/fixtures.ts +0 -61
  26. package/tests/format-recall.test.ts +0 -87
  27. package/tests/format.test.ts +0 -73
  28. package/tests/government-domain.test.ts +0 -542
  29. package/tests/lineage.test.ts +0 -33
  30. package/tests/load-messages.test.ts +0 -49
  31. package/tests/model-threshold-hook.test.ts +0 -311
  32. package/tests/model-threshold.test.ts +0 -223
  33. package/tests/normalize.test.ts +0 -104
  34. package/tests/proactive-threshold.test.ts +0 -490
  35. package/tests/real-sessions.test.ts +0 -38
  36. package/tests/recall-expand.test.ts +0 -15
  37. package/tests/recall-scope.test.ts +0 -32
  38. package/tests/recall-tool-scope.test.ts +0 -67
  39. package/tests/render-entries.test.ts +0 -92
  40. package/tests/report.test.ts +0 -58
  41. package/tests/sanitize.test.ts +0 -24
  42. package/tests/search-entries.test.ts +0 -233
  43. package/tests/stress-100-compactions.test.ts +0 -373
  44. package/tests/support/load-session.ts +0 -23
  45. package/tests/support/real-sessions.ts +0 -51
  46. package/tests/threshold-integration.test.ts +0 -335
@@ -1,227 +0,0 @@
1
- import { describe, test, expect } from "bun:test";
2
- import { buildOwnCut } from "../src/hooks/before-compact";
3
-
4
- const msg = (id: string, role: "user" | "assistant" | "toolResult", content = "x") => ({
5
- id,
6
- type: "message",
7
- message: { role, content },
8
- });
9
-
10
- const comp = (id: string, firstKeptEntryId?: string) => ({
11
- id,
12
- type: "compaction",
13
- firstKeptEntryId,
14
- });
15
-
16
- describe("buildOwnCut", () => {
17
- test("no prior compaction: cuts at last user message", () => {
18
- const r = buildOwnCut([
19
- msg("m1", "user", "a"),
20
- msg("m2", "assistant", "b"),
21
- msg("m3", "user", "c"),
22
- msg("m4", "assistant", "d"),
23
- ]);
24
- expect(r.ok).toBe(true);
25
- if (!r.ok) return;
26
- expect(r.firstKeptEntryId).toBe("m3");
27
- expect(r.messages).toHaveLength(2);
28
- expect(r.compactAll).toBe(false);
29
- });
30
-
31
- test("cancels with too_few_live_messages when liveMessages <= 2", () => {
32
- const r = buildOwnCut([
33
- comp("c1", "m1"),
34
- msg("m1", "user", "x"),
35
- msg("m2", "assistant", "y"),
36
- ]);
37
- expect(r.ok).toBe(false);
38
- if (r.ok) return;
39
- expect(r.reason).toBe("too_few_live_messages");
40
- });
41
-
42
- test("orphan firstKeptEntryId triggers recovery (collect after compaction)", () => {
43
- // Prev compaction set firstKeptEntryId to a non-existent id (e.g. "" sentinel
44
- // from a previous compact-all). Recovery should collect msgs after compaction.
45
- const r = buildOwnCut([
46
- msg("old1", "user", "old"),
47
- msg("old2", "assistant", "old"),
48
- comp("c1", "ORPHAN_ID"),
49
- msg("m1", "user", "a"),
50
- msg("m2", "assistant", "b"),
51
- msg("m3", "user", "c"),
52
- msg("m4", "assistant", "d"),
53
- ]);
54
- expect(r.ok).toBe(true);
55
- if (!r.ok) return;
56
- expect(r.firstKeptEntryId).toBe("m3");
57
- expect(r.messages).toHaveLength(2);
58
- });
59
-
60
- test("resumes from firstKeptEntryId after prior compaction", () => {
61
- const r = buildOwnCut([
62
- msg("old1", "user", "old"),
63
- msg("old2", "assistant", "old"),
64
- comp("c1", "m1"),
65
- msg("m1", "user", "a"),
66
- msg("m2", "assistant", "b"),
67
- msg("m3", "user", "c"),
68
- msg("m4", "assistant", "d"),
69
- ]);
70
- expect(r.ok).toBe(true);
71
- if (!r.ok) return;
72
- expect(r.firstKeptEntryId).toBe("m3");
73
- expect(r.messages).toHaveLength(2);
74
- });
75
-
76
- test("single user prompt + autonomous tail: cut at mid-cycle boundary", () => {
77
- // The agentic scenario: user types 1 prompt, agent runs autonomously
78
- // (assistant + toolResult interleaved). No user > idx 0.
79
- // Instead of compact-all, find a completed tool-cycle boundary.
80
- const r = buildOwnCut([
81
- msg("m1", "user", "go"),
82
- msg("m2", "assistant", "calling tool"),
83
- msg("m3", "toolResult", "result"),
84
- msg("m4", "assistant", "more"),
85
- msg("m5", "toolResult", "result2"),
86
- msg("m6", "assistant", "done"),
87
- ]);
88
- expect(r.ok).toBe(true);
89
- if (!r.ok) return;
90
- expect(r.compactAll).toBe(false);
91
- // Should cut after the first completed cycle (m2→m3) or the second (m4→m5)
92
- // Mid-cycle logic picks nearest to midpoint of 6 = index 3
93
- // Cycle 1 ends at m3 (idx 3), cycle 2 ends at m5 (idx 5)
94
- // Distance: |3-3|=0 vs |5-3|=2, so picks m3
95
- expect(r.firstKeptEntryId).toBe("m4");
96
- expect(r.messages).toHaveLength(3); // m1, m2, m3
97
- });
98
-
99
- test("no user message: compact-all instead of cancelling", () => {
100
- // When there are enough live messages but none are from the user
101
- // (e.g., long assistant/tool chain), compact all rather than
102
- // cancelling and leaving the session unrecoverable.
103
- const r = buildOwnCut([
104
- msg("m1", "assistant", "a"),
105
- msg("m2", "assistant", "b"),
106
- msg("m3", "assistant", "c"),
107
- ]);
108
- expect(r.ok).toBe(true);
109
- if (!r.ok) return;
110
- expect(r.compactAll).toBe(true);
111
- expect(r.firstKeptEntryId).toBe("");
112
- expect(r.messages).toHaveLength(3);
113
- });
114
-
115
- test("compact-all then more chat: orphan recovery + normal cut", () => {
116
- // After a compact-all (firstKeptEntryId=""), user chats more turns,
117
- // next compaction should orphan-recover and find multiple users.
118
- const r = buildOwnCut([
119
- msg("o1", "user", "old"),
120
- msg("o2", "assistant", "old"),
121
- comp("c1", ""), // sentinel from prior compact-all
122
- msg("u1", "user", "new1"),
123
- msg("a1", "assistant", "reply1"),
124
- msg("u2", "user", "new2"),
125
- msg("a2", "assistant", "reply2"),
126
- msg("u3", "user", "new3"),
127
- msg("a3", "assistant", "reply3"),
128
- ]);
129
- expect(r.ok).toBe(true);
130
- if (!r.ok) return;
131
- expect(r.compactAll).toBe(false);
132
- expect(r.firstKeptEntryId).toBe("u3");
133
- expect(r.messages).toHaveLength(4); // u1, a1, u2, a2
134
- });
135
-
136
- test("compact-all then single user msg + autonomous: cut at mid-cycle boundary", () => {
137
- // After compact-all, only 1 user message in orphan range. Should find
138
- // a completed tool-cycle boundary instead of compacting all again.
139
- const r = buildOwnCut([
140
- msg("o1", "user", "old"),
141
- comp("c1", ""),
142
- msg("u1", "user", "okay"),
143
- msg("a1", "assistant", "x"),
144
- msg("t1", "toolResult", "y"),
145
- msg("a2", "assistant", "z"),
146
- ]);
147
- expect(r.ok).toBe(true);
148
- if (!r.ok) return;
149
- // Single user at idx 0 of live range → mid-cycle boundary
150
- // Live messages: [u1, a1, t1, a2]. Midpoint=2. Completed cycle ends at t1 (idx 2).
151
- expect(r.compactAll).toBe(false);
152
- expect(r.firstKeptEntryId).toBe("a2");
153
- });
154
-
155
- test("matched tool calls do NOT push cut back to previous user", () => {
156
- // Regression: toolResult messages carry toolCallId at the message level,
157
- // not as a content part. The old code looked for part.type==="toolResult"
158
- // in content (which never matches), causing every toolCall to appear
159
- // "unmatched" and pushing the cut back one user turn.
160
- const r = buildOwnCut([
161
- msg("u1", "user", "first prompt"),
162
- msg("a1", "assistant", [{ type: "text", text: "let me check" }, { type: "toolCall", id: "tc_1", name: "read", arguments: { path: "foo.ts" } }]),
163
- { id: "t1", type: "message", message: { role: "toolResult", toolCallId: "tc_1", toolName: "read", content: [{ type: "text", text: "file contents" }], isError: false } },
164
- msg("u2", "user", "second prompt"),
165
- msg("a2", "assistant", [{ type: "text", text: "done" }]),
166
- ]);
167
- expect(r.ok).toBe(true);
168
- if (!r.ok) return;
169
- // Cut should be at u2 (second user) since tc_1 has a matching toolResult
170
- expect(r.compactAll).toBe(false);
171
- expect(r.firstKeptEntryId).toBe("u2");
172
- expect(r.messages).toHaveLength(3); // u1, a1, t1
173
- });
174
-
175
- test("unmatched tool call still pushes cut back", () => {
176
- // When a toolCall genuinely has no toolResult, the cut should still
177
- // push back to keep the in-progress turn in the tail.
178
- const r = buildOwnCut([
179
- msg("u1", "user", "first prompt"),
180
- msg("a1", "assistant", "response 1"),
181
- msg("u2", "user", "second prompt"),
182
- msg("a2", "assistant", "response 2"),
183
- msg("u3", "user", "third prompt"),
184
- msg("a3", "assistant", [{ type: "text", text: "checking" }, { type: "toolCall", id: "tc_unmatched", name: "bash", arguments: { command: "ls" } }]),
185
- ]);
186
- expect(r.ok).toBe(true);
187
- if (!r.ok) return;
188
- // tc_unmatched has no toolResult → push cut back from u3 to u2
189
- expect(r.compactAll).toBe(false);
190
- expect(r.firstKeptEntryId).toBe("u2");
191
- });
192
-
193
- test("Anthropic-style session: many matched toolCalls should not cause compact-all", () => {
194
- // Simulates the real Anthropic pattern: 2 user messages with many matched
195
- // toolCall/toolResult pairs between them. The bug caused all toolCalls to
196
- // appear "unmatched" → cut pushed back to first user → compact-all.
197
- const entries: any[] = [
198
- msg("u1", "user", "help me find bugs"),
199
- ];
200
- // Add 5 matched tool cycles (assistant with toolCall + toolResult)
201
- for (let i = 1; i <= 5; i++) {
202
- entries.push({ id: `a${i}`, type: "message", message: { role: "assistant", content: [
203
- { type: "thinking", thinking: `thinking ${i}` },
204
- { type: "toolCall", id: `tc_${i}`, name: "read", arguments: { path: `file${i}.ts` } },
205
- ] } });
206
- entries.push({ id: `t${i}`, type: "message", message: { role: "toolResult", toolCallId: `tc_${i}`, toolName: "read", content: [{ type: "text", text: `file ${i} contents` }], isError: false } });
207
- }
208
- // Second user message
209
- entries.push(msg("u2", "user", "now fix the bug"));
210
- // More matched tool cycles after second user
211
- for (let i = 6; i <= 8; i++) {
212
- entries.push({ id: `a${i}`, type: "message", message: { role: "assistant", content: [
213
- { type: "toolCall", id: `tc_${i}`, name: "edit", arguments: { path: `file${i}.ts` } },
214
- ] } });
215
- entries.push({ id: `t${i}`, type: "message", message: { role: "toolResult", toolCallId: `tc_${i}`, toolName: "edit", content: [{ type: "text", text: `edited file ${i}` }], isError: false } });
216
- }
217
- entries.push(msg("a9", "assistant", "all done"));
218
-
219
- const r = buildOwnCut(entries);
220
- expect(r.ok).toBe(true);
221
- if (!r.ok) return;
222
- // Cut should be at u2 — all toolCalls after u1 have matching toolResults
223
- // and all toolCalls after u2 also have matching toolResults
224
- expect(r.compactAll).toBe(false);
225
- expect(r.firstKeptEntryId).toBe("u2");
226
- });
227
- });
@@ -1,312 +0,0 @@
1
- import { describe, it, expect } from "bun:test";
2
- import { compile } from "../src/core/summarize";
3
- import { buildSections } from "../src/core/build-sections";
4
- import type { NormalizedBlock } from "../src/types";
5
- import {
6
- userMsg,
7
- } from "./fixtures";
8
-
9
- describe("breadcrumbs: mergeHeaderSection", () => {
10
- it("leaves recall breadcrumbs when Session Goal exceeds cap", () => {
11
- const goals = Array.from({ length: 10 }, (_, i) =>
12
- `- Implement feature ${i}: auth${i} module`
13
- ).join("\n");
14
- const prevSummary = `[Session Goal]\n${goals}\n\n---\n\n[user]\nhi`;
15
- const r = compile({
16
- messages: [userMsg("continue")],
17
- previousSummary: prevSummary,
18
- });
19
- expect(r).toContain("...recall:");
20
- expect(r).toContain("auth0");
21
- expect(r).toContain("auth1");
22
- expect(r).toContain("feature 9");
23
- expect(r).not.toContain("Implement feature 0: auth0 module");
24
- });
25
-
26
- it("leaves no breadcrumbs when under cap", () => {
27
- const goals = Array.from({ length: 5 }, (_, i) =>
28
- `- Goal ${i}`
29
- ).join("\n");
30
- const prevSummary = `[Session Goal]\n${goals}\n\n---\n\n[user]\nhi`;
31
- const r = compile({
32
- messages: [userMsg("continue")],
33
- previousSummary: prevSummary,
34
- });
35
- expect(r).not.toContain("...recall:");
36
- });
37
-
38
- it("leaves breadcrumbs for Earlier Turns when cap exceeded", () => {
39
- const turns = Array.from({ length: 20 }, (_, i) =>
40
- `- Fix bug ${i} → edited bug${i}.ts`
41
- ).join("\n");
42
- const prevSummary = `[Earlier Turns]\n${turns}\n\n---\n\n[user]\nhi`;
43
- const r = compile({
44
- messages: [userMsg("next")],
45
- previousSummary: prevSummary,
46
- });
47
- expect(r).toContain("...recall:");
48
- expect(r).toContain("bug0.ts");
49
- });
50
-
51
- it("breadcrumb extraction: picks up file paths from edited actions", () => {
52
- const manyTurns = Array.from({ length: 18 }, (_, i) =>
53
- `- Turn ${i} → edited file${i}.ts`
54
- ).join("\n");
55
- const bigPrevSummary = `[Earlier Turns]\n${manyTurns}\n\n---\n\n[user]\ngo`;
56
- const r = compile({
57
- messages: [userMsg("continue")],
58
- previousSummary: bigPrevSummary,
59
- });
60
- expect(r).toContain("...recall:");
61
- expect(r).toContain("file0.ts");
62
- });
63
-
64
- it("breadcrumb from short text uses first content words", () => {
65
- const manyGoals = Array.from({ length: 10 }, (_, i) =>
66
- `- Task ${i} description`
67
- ).join("\n");
68
- const bigPrev = `[Session Goal]\n${manyGoals}\n\n---\n\n[user]\ngo`;
69
- const r = compile({
70
- messages: [userMsg("continue")],
71
- previousSummary: bigPrev,
72
- });
73
- expect(r).toContain("...recall:");
74
- expect(r).toContain("Task");
75
- });
76
- });
77
-
78
- describe("breadcrumbs: mergeFileLines (+recall:)", () => {
79
- it("replaces (+N more) with +recall: listing omitted paths", () => {
80
- const paths = Array.from({ length: 12 }, (_, i) => `src/mod${i}.ts`);
81
- const prevSummary = [
82
- "[Files And Changes]",
83
- `- Modified: ${paths.join(", ")}`,
84
- ].join("\n") + "\n\n---\n\n[user]\ngo";
85
- const r = compile({
86
- messages: [userMsg("continue")],
87
- previousSummary: prevSummary,
88
- });
89
- expect(r).toContain("+recall:");
90
- expect(r).toContain("mod10.ts");
91
- expect(r).toContain("mod11.ts");
92
- });
93
-
94
- it("no breadcrumb when all paths fit within cap", () => {
95
- const paths = ["src/a.ts", "src/b.ts"];
96
- const prevSummary = [
97
- "[Files And Changes]",
98
- `- Modified: ${paths.join(", ")}`,
99
- ].join("\n") + "\n\n---\n\n[user]\ngo";
100
- const r = compile({
101
- messages: [userMsg("continue")],
102
- previousSummary: prevSummary,
103
- });
104
- expect(r).not.toContain("+recall:");
105
- });
106
-
107
- it("breadcrumb paths are parseable on next compaction", () => {
108
- const prevSummary = [
109
- "[Files And Changes]",
110
- `- Modified: src/mod0.ts, src/mod1.ts, +recall: src/mod2.ts`,
111
- ].join("\n") + "\n\n---\n\n[user]\ngo";
112
- const r = compile({
113
- messages: [userMsg("continue")],
114
- previousSummary: prevSummary,
115
- });
116
- expect(r).toContain("mod2.ts");
117
- });
118
-
119
- it("applies +recall: cap even when only previous has files (no fresh)", () => {
120
- const paths = Array.from({ length: 15 }, (_, i) => `src/file${i}.ts`);
121
- const prevSummary = [
122
- "[Files And Changes]",
123
- `- Read: ${paths.join(", ")}`,
124
- ].join("\n") + "\n\n---\n\n[user]\ngo";
125
- const r = compile({
126
- messages: [userMsg("continue")],
127
- previousSummary: prevSummary,
128
- });
129
- expect(r).toContain("+recall:");
130
- expect(r).toContain("file10.ts");
131
- });
132
- });
133
-
134
- describe("breadcrumbs: capBrief", () => {
135
- it("shows omitted count without redundant recall terms", () => {
136
- const longTranscript = Array.from({ length: 200 }, (_, i) =>
137
- `[user]\nmessage ${i}`
138
- ).join("\n\n");
139
- const previousSummary = `[Session Goal]\n- goal\n\n---\n\n${longTranscript}`;
140
- const r = compile({
141
- previousSummary,
142
- messages: [userMsg("latest")],
143
- });
144
- expect(r).toContain("earlier lines omitted");
145
- // capBrief should NOT include recall: terms (redundant with Files And Changes)
146
- expect(r).not.toMatch(/\.\.\.\(\d+ earlier lines omitted, recall:/);
147
- });
148
- });
149
-
150
- describe("breadcrumbs: type catalog", () => {
151
- it("emits count-only message for omitted files (not redundant file list)", () => {
152
- const blocks: NormalizedBlock[] = [];
153
- for (let i = 0; i < 15; i++) {
154
- blocks.push({ kind: "tool_call", name: "Read", args: { file_path: `src/file${i}.ts` } });
155
- blocks.push({
156
- kind: "tool_result",
157
- name: "Read",
158
- text: Array.from({ length: 3 }, (_, j) =>
159
- `export function fn${i}_${j}(): void {}`
160
- ).join("\n"),
161
- isError: false,
162
- });
163
- }
164
- const r = buildSections({ blocks });
165
- // Should contain count-only message, not redundant file list
166
- expect(r.typeCatalog.some(l => /more files with signatures omitted/.test(l))).toBe(true);
167
- // Should NOT list specific file names in the omission line (redundant with Files And Changes)
168
- expect(r.typeCatalog.every(l => !l.includes("recall:"))).toBe(true);
169
- });
170
-
171
- it("no omission line when all files fit within sig cap", () => {
172
- const blocks: NormalizedBlock[] = [
173
- { kind: "tool_call", name: "Read", args: { file_path: "src/a.ts" } },
174
- {
175
- kind: "tool_result",
176
- name: "Read",
177
- text: "export function fnA(): void {}",
178
- isError: false,
179
- },
180
- ];
181
- const r = buildSections({ blocks });
182
- expect(r.typeCatalog.every(l => !l.includes("omitted"))).toBe(true);
183
- });
184
- });
185
-
186
- describe("breadcrumbs: no redundancy across sections", () => {
187
- it("Anchors section removed entirely (was redundant with Commits + Outstanding Context)", () => {
188
- // Anchors duplicates commit hashes from [Commits] and error IDs from [Outstanding Context]
189
- // The section has been removed entirely.
190
- const blocks: NormalizedBlock[] = [];
191
- for (let i = 0; i < 5; i++) {
192
- blocks.push({ kind: "tool_call", name: "Read", args: { file_path: `src/file${i}.ts` } });
193
- blocks.push({
194
- kind: "tool_result",
195
- name: "Read",
196
- text: "export function fn(): void {}",
197
- isError: false,
198
- });
199
- }
200
- const r = buildSections({ blocks });
201
- expect(r).not.toHaveProperty("anchors");
202
- });
203
-
204
- it("Current Status section removed entirely (redundant with brief transcript + Files And Changes)", () => {
205
- // "Working on:" = last user message (brief transcript)
206
- // "Last action:" = file path (Files And Changes)
207
- // "Next:" = assistant text (brief transcript)
208
- const blocks: NormalizedBlock[] = [];
209
- const r = buildSections({ blocks });
210
- expect(r).not.toHaveProperty("currentStatus");
211
- });
212
- });
213
-
214
- describe("breadcrumbs: determinism and idempotency", () => {
215
- it("compile produces identical output for identical inputs", () => {
216
- const goals = Array.from({ length: 10 }, (_, i) =>
217
- `- Goal ${i} with detail`
218
- ).join("\n");
219
- const prevSummary = `[Session Goal]\n${goals}\n\n---\n\n[user]\nhi`;
220
-
221
- const input = {
222
- messages: [userMsg("continue")],
223
- previousSummary: prevSummary,
224
- };
225
-
226
- const r1 = compile(input);
227
- const r2 = compile(input);
228
- expect(r1).toBe(r2);
229
- });
230
-
231
- it("breadcrumbs are deterministic: same dropped content produces same breadcrumb", () => {
232
- const goals = Array.from({ length: 10 }, (_, i) =>
233
- `- Implement feature ${i}: module${i}`
234
- ).join("\n");
235
- const prevSummary = `[Session Goal]\n${goals}\n\n---\n\n[user]\nhi`;
236
-
237
- const r1 = compile({ messages: [userMsg("continue")], previousSummary: prevSummary });
238
- const r2 = compile({ messages: [userMsg("continue")], previousSummary: prevSummary });
239
-
240
- expect(r1).toBe(r2);
241
- const recallLine1 = r1.split("\n").find(l => l.includes("...recall:"));
242
- const recallLine2 = r2.split("\n").find(l => l.includes("...recall:"));
243
- expect(recallLine1).toBe(recallLine2);
244
- });
245
-
246
- it("prior compaction breadcrumb lines survive across compactions", () => {
247
- const prevSummary = [
248
- "[Session Goal]",
249
- "- ...recall: auth0, auth1",
250
- "- Goal 2",
251
- "- Goal 3",
252
- "- Goal 4",
253
- "- Goal 5",
254
- "- Goal 6",
255
- "- Goal 7",
256
- "- Goal 8",
257
- "- Goal 9",
258
- ].join("\n") + "\n\n---\n\n[user]\nhi";
259
- const r = compile({
260
- messages: [userMsg("continue")],
261
- previousSummary: prevSummary,
262
- });
263
- // The breadcrumb from the prior compaction should be preserved
264
- expect(r).toContain("auth0");
265
- expect(r).toContain("auth1");
266
- });
267
-
268
- it("double-compilation of same previous summary is idempotent", () => {
269
- const goals = Array.from({ length: 10 }, (_, i) =>
270
- `- Goal ${i} with detail`
271
- ).join("\n");
272
- const prevSummary = `[Session Goal]\n${goals}\n\n---\n\n[user]\nhi`;
273
-
274
- const intermediate = compile({
275
- messages: [userMsg("continue")],
276
- previousSummary: prevSummary,
277
- });
278
-
279
- const recallNoteIdx = intermediate.lastIndexOf("Use `vcc_recall`");
280
- const stripped = recallNoteIdx >= 0
281
- ? intermediate.slice(0, recallNoteIdx).replace(/\s*(?:\n\n---\n\n)?\s*$/, "").trimEnd()
282
- : intermediate;
283
-
284
- const r1 = compile({
285
- messages: [userMsg("more work")],
286
- previousSummary: stripped,
287
- });
288
- const r2 = compile({
289
- messages: [userMsg("more work")],
290
- previousSummary: stripped,
291
- });
292
- expect(r1).toBe(r2);
293
- });
294
-
295
- it("buildSections type catalog is deterministic", () => {
296
- const blocks: NormalizedBlock[] = [];
297
- for (let i = 0; i < 15; i++) {
298
- blocks.push({ kind: "tool_call", name: "Read", args: { file_path: `src/file${i}.ts` } });
299
- blocks.push({
300
- kind: "tool_result",
301
- name: "Read",
302
- text: Array.from({ length: 3 }, (_, j) =>
303
- `export function fn${i}_${j}(): void {}`
304
- ).join("\n"),
305
- isError: false,
306
- });
307
- }
308
- const r1 = buildSections({ blocks });
309
- const r2 = buildSections({ blocks });
310
- expect(r1.typeCatalog).toEqual(r2.typeCatalog);
311
- });
312
- });