File size: 13,793 Bytes
88c4c60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
// Adversarial / edge-case tests for PR #1175
// Goals: find corruption, boundary bugs, false positives, integration regressions
import { describe, it, expect } from "vitest";
import { autoDetectFilter } from "../../open-sse/rtk/autodetect.js";
import { buildOutput } from "../../open-sse/rtk/filters/buildOutput.js";
import { gitDiff } from "../../open-sse/rtk/filters/gitDiff.js";
import { gitStatus } from "../../open-sse/rtk/filters/gitStatus.js";
import { safeApply } from "../../open-sse/rtk/applyFilter.js";
import { compressMessages } from "../../open-sse/rtk/index.js";
import { DETECT_WINDOW, MIN_COMPRESS_SIZE } from "../../open-sse/rtk/constants.js";

// ============================================================
// 1. PRIORITY / OVERLAPPING PATTERNS
// ============================================================
describe("PR #1175 - priority with overlapping patterns", () => {
  it("git-diff wins over buildOutput when both present", () => {
    const input = [
      "diff --git a/Cargo.toml b/Cargo.toml",
      "index abc..def 100644",
      "--- a/Cargo.toml",
      "+++ b/Cargo.toml",
      "@@ -1,3 +1,3 @@",
      "-version = \"0.1.0\"",
      "+version = \"0.2.0\"",
      "   Compiling foo v0.1.0"
    ].join("\n");
    expect(autoDetectFilter(input)).toBe(gitDiff);
  });

  it("git-status (long form) wins over buildOutput", () => {
    const input = [
      "On branch main",
      "Changes not staged for commit:",
      "\tmodified:   Cargo.toml",
      "   Compiling foo v0.1.0"
    ].join("\n");
    expect(autoDetectFilter(input)).toBe(gitStatus);
  });
});

// ============================================================
// 2. BOUNDARY: DETECT_WINDOW
// ============================================================
describe("PR #1175 - DETECT_WINDOW boundary", () => {
  it("build pattern beyond DETECT_WINDOW chars: NOT detected", () => {
    const padding = "x".repeat(DETECT_WINDOW + 100);
    const input = `${padding}\n   Compiling foo v0.1.0\n    Finished release in 1.2s`;
    const filter = autoDetectFilter(input);
    // Pattern lives past detection window β€” won't be seen
    expect(filter).not.toBe(buildOutput);
  });

  it("build pattern at very start: detected", () => {
    const input = "   Compiling foo v0.1.0\n" + "y".repeat(2000);
    expect(autoDetectFilter(input)).toBe(buildOutput);
  });
});

// ============================================================
// 3. LINE ENDINGS / WHITESPACE QUIRKS
// ============================================================
describe("PR #1175 - line endings & whitespace", () => {
  it("CRLF line endings still detect", () => {
    const input = "npm warn deprecated foo@1.0.0\r\nadded 5 packages in 2s\r\n";
    expect(autoDetectFilter(input)).toBe(buildOutput);
  });

  it("Tab-prefixed Compiling (real cargo output uses leading spaces, not tab)", () => {
    const input = "\tCompiling foo v0.1.0\n\tCompiling bar v0.2.0\n\tFinished dev in 1s";
    const filter = autoDetectFilter(input);
    // \s matches tab, so should detect
    expect(filter).toBe(buildOutput);
  });

  it("Compiling without leading spaces", () => {
    const input = "Compiling foo v0.1.0\nCompiling bar v0.2.0\nFinished dev in 1s";
    expect(autoDetectFilter(input)).toBe(buildOutput);
  });
});

// ============================================================
// 4. ADVERSARIAL: USER CODE / STRING LITERALS
// ============================================================
describe("PR #1175 - adversarial: user code containing build strings", () => {
  it("user JS code with console.log('npm warn ...') triggers buildOutput", () => {
    // This is a realistic case: LLM is reading a file with this code
    const input = [
      "function logWarning() {",
      "  console.log('npm warn this is a warning');",
      "  return true;",
      "}",
      "function logError() {",
      "  console.log('npm error something bad');",
      "}"
    ].join("\n");
    const filter = autoDetectFilter(input);
    // Regex uses `m` flag, so ^ matches line start β€” these are inside indented code
    // BUT: regex uses 'i' so case-insensitive, and `^npm warn` requires line to START with it
    console.log("[user-code-npm-warn] detected:", filter?.filterName || "null");
    // Expectation: should NOT detect (lines start with spaces)
    expect(filter).not.toBe(buildOutput);
  });

  it("file content with 'BUILD SUCCESS' on its own line triggers buildOutput", () => {
    const input = [
      "Here is the deployment script:",
      "It outputs:",
      "BUILD SUCCESS",
      "when complete."
    ].join("\n");
    const filter = autoDetectFilter(input);
    console.log("[file-content-build-success] detected:", filter?.filterName || "null");
    // Document behavior β€” buildOutput should preserve non-pattern lines as fallback
    if (filter === buildOutput) {
      const out = buildOutput(input);
      // BUILD SUCCESS preserved
      expect(out).toContain("BUILD SUCCESS");
    }
  });

  it("real cargo error spanning multiple lines preserves context", () => {
    const input = [
      "   Compiling my-app v0.1.0",
      "error[E0432]: unresolved import `foo::bar`",
      " --> src/main.rs:2:5",
      "  |",
      "2 | use foo::bar;",
      "  |     ^^^^^^^^ no `bar` in `foo`",
      "",
      "error: aborting due to previous error",
      "",
      "For more information about this error, try `rustc --explain E0432`.",
      "error: could not compile `my-app` (bin \"my-app\") due to previous error"
    ].join("\n");
    const out = buildOutput(input);
    expect(out).toContain("error[E0432]");
    expect(out).toContain("error: aborting");
    expect(out).toContain("error: could not compile");
    // Minimal fix: cargo error context lines now preserved
    expect(out).toContain("use foo::bar");
    expect(out).toContain("no `bar`");
  });
});

// ============================================================
// 5. CORRUPTION / SAFETY: NO EMPTY OUTPUT
// ============================================================
describe("PR #1175 - corruption safety", () => {
  it("input with only progress lines (no errors/warnings/summary) returns input fallback", () => {
    const input = [
      "   Compiling a v0.1.0",
      "   Compiling b v0.1.0",
      "   Compiling c v0.1.0"
    ].join("\n");
    const out = buildOutput(input);
    // out = "Compiled 3 packages" (non-empty)
    expect(out.length).toBeGreaterThan(0);
    expect(out).toContain("Compiled 3 packages");
  });

  it("input with only Downloading lines", () => {
    const input = [
      "   Downloading foo v0.1.0",
      "   Downloading bar v0.2.0",
      "Fetching baz from registry"
    ].join("\n");
    const out = buildOutput(input);
    expect(out).toContain("Downloaded");
  });

  it("input with ONLY a single ERROR: line", () => {
    const input = "ERROR: Something failed";
    const out = buildOutput(input);
    expect(out).toContain("ERROR: Something failed");
  });

  it("unicode/emoji in deprecation warning preserved (minimal fix keeps first 3 verbatim)", () => {
    const input = [
      "npm warn deprecated πŸ“¦ foo@1.0.0: 🚫 deprecated reason",
      "added 1 package ✨",
      "Run `npm audit` for details."
    ].join("\n");
    const out = buildOutput(input);
    expect(out).toContain("πŸ“¦");
    expect(out).toContain("foo@1.0.0");
    expect(out).toContain("added 1 package ✨");
  });

  it("more than 3 deprecations: keep first 3 verbatim + count rest", () => {
    const input = [
      "npm warn deprecated a@1.0.0: reason A",
      "npm warn deprecated b@1.0.0: reason B",
      "npm warn deprecated c@1.0.0: reason C",
      "npm warn deprecated d@1.0.0: reason D",
      "npm warn deprecated e@1.0.0: reason E",
      "added 5 packages"
    ].join("\n");
    const out = buildOutput(input);
    expect(out).toContain("a@1.0.0");
    expect(out).toContain("b@1.0.0");
    expect(out).toContain("c@1.0.0");
    expect(out).not.toContain("d@1.0.0");
    expect(out).not.toContain("e@1.0.0");
    expect(out).toContain("... +2 more deprecated packages");
  });

  it("safeApply wraps buildOutput against panics", () => {
    // Pass a non-string input via direct call β€” safeApply should catch
    const out = safeApply(buildOutput, "npm warn deprecated foo\nadded 1 package\n");
    expect(typeof out).toBe("string");
  });
});

// ============================================================
// 6. INTEGRATION: compressMessages pipeline
// ============================================================
describe("PR #1175 - integration with compressMessages", () => {
  function buildBody(toolResultText) {
    return {
      messages: [
        {
          role: "user",
          content: [
            { type: "tool_result", tool_use_id: "id1", content: toolResultText }
          ]
        }
      ]
    };
  }

  it("npm install output above MIN_COMPRESS_SIZE β†’ compressed", () => {
    const padding = "npm warn deprecated foo@1.0.0: this is a deprecation warning\n".repeat(20);
    const text = padding + "added 47 packages, and audited 48 packages in 13s\n4 vulnerabilities (2 moderate, 2 critical)\nRun `npm audit` for details.";
    expect(text.length).toBeGreaterThan(MIN_COMPRESS_SIZE);
    const body = buildBody(text);
    const stats = compressMessages(body, true);
    expect(stats).toBeTruthy();
    expect(stats.hits.length).toBe(1);
    expect(stats.hits[0].filter).toBe("build-output");
    expect(stats.bytesAfter).toBeLessThan(stats.bytesBefore);
    const compressed = body.messages[0].content[0].content;
    expect(compressed).toContain("... +17 more deprecated packages");
  });

  it("input below MIN_COMPRESS_SIZE β†’ NOT compressed", () => {
    const text = "npm warn deprecated foo\nadded 1 package";
    expect(text.length).toBeLessThan(MIN_COMPRESS_SIZE);
    const body = buildBody(text);
    const stats = compressMessages(body, true);
    expect(stats.hits.length).toBe(0);
    expect(body.messages[0].content[0].content).toBe(text);
  });

  it("compressed output never grows input (safety guard)", () => {
    // Pathological: every line is something buildOutput keeps verbatim
    const text = "npm ERR! error line 1\nnpm ERR! error line 2\nnpm ERR! error line 3\n".repeat(20);
    const body = buildBody(text);
    const stats = compressMessages(body, true);
    // either no hit (grew) or hit and shrunk
    const after = body.messages[0].content[0].content;
    expect(after.length).toBeLessThanOrEqual(text.length);
  });

  it("tool_result with is_error:true is NOT compressed (preserve error traces)", () => {
    const text = "npm warn deprecated foo@1.0.0\n".repeat(30) + "added 5 packages in 2s";
    const body = {
      messages: [
        {
          role: "user",
          content: [
            { type: "tool_result", tool_use_id: "id1", content: text, is_error: true }
          ]
        }
      ]
    };
    const stats = compressMessages(body, true);
    expect(stats.hits.length).toBe(0);
    expect(body.messages[0].content[0].content).toBe(text);
  });
});

// ============================================================
// 7. PORCELAIN REGRESSION DEEPER TESTS
// ============================================================
describe("PR #1175 - porcelain regression deeper", () => {
  it("mixed staged + workdir + untracked porcelain β†’ detected (has status code first char)", () => {
    const input = [
      "M  src/staged.js",   // staged modified
      " M src/workdir.js",  // workdir modified (space first)
      "?? new.js",
      "A  src/added.js"
    ].join("\n");
    const filter = autoDetectFilter(input);
    // M and A and ?? lines have status code first β†’ 4/4 lines hit? No β€” " M" has space first
    // isMostlyPorcelain requires >= 60% hit. With new regex, hits = M/?/A = 3, total = 4, 75% β‰₯ 60%
    expect(filter).toBe(gitStatus);
  });

  it("100% workdir-only porcelain β†’ STILL detects gitStatus (minimal fix preserved old regex)", () => {
    const input = [
      " M src/a.js",
      " M src/b.js",
      " M src/c.js",
      " D src/d.js"
    ].join("\n");
    const filter = autoDetectFilter(input);
    expect(filter).toBe(gitStatus);
  });

  it("manual gitStatus() call on workdir-only porcelain still parses correctly", () => {
    const input = [
      " M src/a.js",
      " M src/b.js",
      " D src/c.js"
    ].join("\n");
    const out = gitStatus(input);
    expect(out).toContain("Modified: 3 files");
  });
});

// ============================================================
// 8. PATHOLOGICAL INPUTS
// ============================================================
describe("PR #1175 - pathological", () => {
  it("very long single line (no newlines) with build pattern", () => {
    const input = "npm warn deprecated foo@1.0.0: " + "x".repeat(5000);
    const filter = autoDetectFilter(input);
    // Pattern at start (within DETECT_WINDOW)
    expect(filter).toBe(buildOutput);
    // Should NOT crash
    const out = buildOutput(input);
    expect(typeof out).toBe("string");
  });

  it("10000 Compiling lines don't crash", () => {
    const lines = [];
    for (let i = 0; i < 10000; i++) lines.push(`   Compiling pkg${i} v0.1.0`);
    lines.push("    Finished dev in 60s");
    const input = lines.join("\n");
    const out = buildOutput(input);
    expect(out).toContain("Compiled 10000 packages");
    expect(out).toContain("Finished");
    expect(out.length).toBeLessThan(input.length / 100);
  });

  it("input with only newlines", () => {
    const input = "\n\n\n\n";
    const out = buildOutput(input);
    expect(typeof out).toBe("string");
  });

  it("null/undefined safety via safeApply", () => {
    // buildOutput would throw on null.split() β€” safeApply must catch
    const out = safeApply(buildOutput, null);
    expect(out).toBe(null);
  });
});