@monotykamary/pi-vcc 0.3.17 → 0.4.0
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 +5 -4
- package/package.json +1 -1
- package/src/core/build-sections.ts +12 -131
- package/src/core/format.ts +4 -14
- package/src/core/summarize.ts +98 -22
- package/src/sections.ts +0 -4
- package/stress-debug.ts +202 -0
- package/stress-degradation-curve.ts +251 -0
- package/tests/breadcrumbs.test.ts +312 -0
- package/tests/deep-extraction.test.ts +28 -19
- package/tests/stress-100-compactions.test.ts +373 -0
|
@@ -0,0 +1,312 @@
|
|
|
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
|
+
});
|
|
@@ -114,8 +114,8 @@ describe("outstanding context: deep error extraction", () => {
|
|
|
114
114
|
});
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
describe("files and changes: symbol annotations", () => {
|
|
118
|
-
it("
|
|
117
|
+
describe("files and changes: no symbol annotations (moved to Type Catalog)", () => {
|
|
118
|
+
it("lists modified files without symbol annotations", () => {
|
|
119
119
|
const blocks: NormalizedBlock[] = [
|
|
120
120
|
{ kind: "tool_call", name: "Edit", args: { file_path: "auth.ts", newText: "export function login() {}\nexport function verifyToken() {}\nconst _internal = 1;" } },
|
|
121
121
|
{ kind: "tool_result", name: "Edit", text: "ok", isError: false },
|
|
@@ -123,11 +123,15 @@ describe("files and changes: symbol annotations", () => {
|
|
|
123
123
|
const r = buildSections({ blocks });
|
|
124
124
|
expect(r.filesAndChanges.length).toBeGreaterThan(0);
|
|
125
125
|
expect(r.filesAndChanges[0]).toContain("Modified");
|
|
126
|
-
expect(r.filesAndChanges[0]).toContain("
|
|
127
|
-
|
|
126
|
+
expect(r.filesAndChanges[0]).toContain("auth.ts");
|
|
127
|
+
// Symbol annotations removed from Files And Changes (redundant with Type Catalog)
|
|
128
|
+
expect(r.filesAndChanges[0]).not.toContain("(");
|
|
129
|
+
// Symbols should appear in the Type Catalog instead
|
|
130
|
+
expect(r.typeCatalog.some(l => l.includes("login"))).toBe(true);
|
|
131
|
+
expect(r.typeCatalog.some(l => l.includes("verifyToken"))).toBe(true);
|
|
128
132
|
});
|
|
129
133
|
|
|
130
|
-
it("
|
|
134
|
+
it("lists read files without symbol annotations", () => {
|
|
131
135
|
const blocks: NormalizedBlock[] = [
|
|
132
136
|
{ kind: "tool_call", name: "Read", args: { file_path: "types.ts" } },
|
|
133
137
|
{ kind: "tool_result", name: "Read", text: "export interface User { name: string; }\nexport type AuthPayload = { email: string; }", isError: false },
|
|
@@ -135,11 +139,14 @@ describe("files and changes: symbol annotations", () => {
|
|
|
135
139
|
const r = buildSections({ blocks });
|
|
136
140
|
expect(r.filesAndChanges.length).toBeGreaterThan(0);
|
|
137
141
|
expect(r.filesAndChanges[0]).toContain("Read");
|
|
138
|
-
expect(r.filesAndChanges[0]).toContain("
|
|
139
|
-
|
|
142
|
+
expect(r.filesAndChanges[0]).toContain("types.ts");
|
|
143
|
+
// Symbol annotations removed (redundant with Type Catalog)
|
|
144
|
+
expect(r.filesAndChanges[0]).not.toContain("(");
|
|
145
|
+
// Symbols should appear in the Type Catalog instead
|
|
146
|
+
expect(r.typeCatalog.some(l => l.includes("interface User"))).toBe(true);
|
|
140
147
|
});
|
|
141
148
|
|
|
142
|
-
it("
|
|
149
|
+
it("lists files without annotations when no symbols are found", () => {
|
|
143
150
|
const blocks: NormalizedBlock[] = [
|
|
144
151
|
{ kind: "tool_call", name: "Read", args: { file_path: "config.json" } },
|
|
145
152
|
{ kind: "tool_result", name: "Read", text: '{ "name": "test" }', isError: false },
|
|
@@ -149,7 +156,7 @@ describe("files and changes: symbol annotations", () => {
|
|
|
149
156
|
expect(r.filesAndChanges[0]).not.toContain("(");
|
|
150
157
|
});
|
|
151
158
|
|
|
152
|
-
it("handles Mixed Edit+Read
|
|
159
|
+
it("handles Mixed Edit+Read: Modified takes priority", () => {
|
|
153
160
|
const blocks: NormalizedBlock[] = [
|
|
154
161
|
{ kind: "tool_call", name: "Read", args: { file_path: "utils.ts" } },
|
|
155
162
|
{ kind: "tool_result", name: "Read", text: "export function helper() {}\nexport class Util {}", isError: false },
|
|
@@ -159,29 +166,32 @@ describe("files and changes: symbol annotations", () => {
|
|
|
159
166
|
const r = buildSections({ blocks });
|
|
160
167
|
expect(r.filesAndChanges[0]).toContain("Modified");
|
|
161
168
|
expect(r.filesAndChanges[0]).toContain("utils.ts");
|
|
162
|
-
//
|
|
163
|
-
expect(r.
|
|
169
|
+
// Full signatures in Type Catalog, not in Files And Changes
|
|
170
|
+
expect(r.typeCatalog.some(l => l.includes("newHelper"))).toBe(true);
|
|
164
171
|
});
|
|
165
172
|
|
|
166
|
-
it("
|
|
173
|
+
it("Python def and class exports appear in Type Catalog only", () => {
|
|
167
174
|
const blocks: NormalizedBlock[] = [
|
|
168
175
|
{ kind: "tool_call", name: "Read", args: { file_path: "auth.py" } },
|
|
169
176
|
{ kind: "tool_result", name: "Read", text: "def login(email, pwd):\n pass\n\nclass AuthProvider:\n pass", isError: false },
|
|
170
177
|
];
|
|
171
178
|
const r = buildSections({ blocks });
|
|
172
|
-
expect(r.filesAndChanges[0]).toContain("
|
|
173
|
-
expect(r.filesAndChanges[0]).toContain("
|
|
179
|
+
expect(r.filesAndChanges[0]).toContain("auth.py");
|
|
180
|
+
expect(r.filesAndChanges[0]).not.toContain("(");
|
|
181
|
+
expect(r.typeCatalog.some(l => l.includes("login"))).toBe(true);
|
|
182
|
+
expect(r.typeCatalog.some(l => l.includes("AuthProvider"))).toBe(true);
|
|
174
183
|
});
|
|
175
184
|
|
|
176
|
-
it("
|
|
185
|
+
it("Go exported functions appear in Type Catalog only", () => {
|
|
177
186
|
const blocks: NormalizedBlock[] = [
|
|
178
187
|
{ kind: "tool_call", name: "Read", args: { file_path: "handler.go" } },
|
|
179
188
|
{ kind: "tool_result", name: "Read", text: "func HandleRequest(w http.ResponseWriter, r *http.Request) {\n\t// ...\n}\n\nfunc internalHelper() {}", isError: false },
|
|
180
189
|
];
|
|
181
190
|
const r = buildSections({ blocks });
|
|
182
|
-
expect(r.filesAndChanges[0]).toContain("
|
|
191
|
+
expect(r.filesAndChanges[0]).toContain("handler.go");
|
|
192
|
+
expect(r.typeCatalog.some(l => l.includes("HandleRequest"))).toBe(true);
|
|
183
193
|
// unexported Go functions (lowercase) should not be included
|
|
184
|
-
expect(r.
|
|
194
|
+
expect(r.typeCatalog.every(l => !l.includes("internalHelper"))).toBe(true);
|
|
185
195
|
});
|
|
186
196
|
});
|
|
187
197
|
|
|
@@ -219,7 +229,6 @@ describe("type catalog", () => {
|
|
|
219
229
|
expect(r.typeCatalog.length).toBeGreaterThan(0);
|
|
220
230
|
// Modified file should appear first
|
|
221
231
|
expect(r.typeCatalog[0]).toContain("modified.ts");
|
|
222
|
-
expect(r.typeCatalog[0]).toContain("[modified]");
|
|
223
232
|
});
|
|
224
233
|
|
|
225
234
|
it("returns empty for files with no exportable signatures", () => {
|
|
@@ -304,7 +313,7 @@ describe("format output integration", () => {
|
|
|
304
313
|
|
|
305
314
|
it("Type Catalog is volatile (fresh only) on merge", () => {
|
|
306
315
|
const { compile } = require("../src/core/summarize");
|
|
307
|
-
const prev = "[Session Goal]\n- goal\n\n[Type Catalog]\n- old-file.ts
|
|
316
|
+
const prev = "[Session Goal]\n- goal\n\n[Type Catalog]\n- old-file.ts:\n export function old()";
|
|
308
317
|
const fresh = compile({ messages: [], previousSummary: prev });
|
|
309
318
|
// Type Catalog should be fresh-only, not merged from previous
|
|
310
319
|
expect(fresh).not.toContain("old-file.ts");
|