@iceinvein/agent-skills 0.23.9 → 0.23.13

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 (75) hide show
  1. package/package.json +1 -1
  2. package/skills/index.json +2 -2
  3. package/skills/magpie/SKILL.md +21 -20
  4. package/skills/magpie/bin/magpie.ts +63 -2
  5. package/skills/magpie/evals/quality/README.md +74 -1
  6. package/skills/magpie/evals/quality/__tests__/probe-specialist.test.ts +498 -0
  7. package/skills/magpie/evals/quality/__tests__/replay-full.test.ts +9 -0
  8. package/skills/magpie/evals/quality/probe-specialist.ts +672 -0
  9. package/skills/magpie/evals/quality/replay-full.ts +6 -2
  10. package/skills/magpie/fixtures/fake-gh-with-lockfile.sh +37 -2
  11. package/skills/magpie/fixtures/fake-gh.sh +49 -2
  12. package/skills/magpie/references/critic.md +20 -4
  13. package/skills/magpie/references/proof.md +102 -0
  14. package/skills/magpie/references/scout.md +6 -1
  15. package/skills/magpie/references/specialists.md +15 -9
  16. package/skills/magpie/scripts/__tests__/cleanup-cmd.test.ts +103 -7
  17. package/skills/magpie/scripts/__tests__/cli.test.ts +69 -0
  18. package/skills/magpie/scripts/__tests__/critic-cmd.test.ts +46 -0
  19. package/skills/magpie/scripts/__tests__/critic.test.ts +11 -1
  20. package/skills/magpie/scripts/__tests__/dedupe-cmd.test.ts +20 -1
  21. package/skills/magpie/scripts/__tests__/git-diff.test.ts +23 -4
  22. package/skills/magpie/scripts/__tests__/handoff-cmd.test.ts +68 -0
  23. package/skills/magpie/scripts/__tests__/history.test.ts +232 -0
  24. package/skills/magpie/scripts/__tests__/impact-cmd.test.ts +477 -0
  25. package/skills/magpie/scripts/__tests__/impact-render.test.ts +365 -0
  26. package/skills/magpie/scripts/__tests__/impact.test.ts +915 -0
  27. package/skills/magpie/scripts/__tests__/incremental.test.ts +59 -8
  28. package/skills/magpie/scripts/__tests__/pipeline-e2e.test.ts +1 -1
  29. package/skills/magpie/scripts/__tests__/post-cmd.test.ts +58 -0
  30. package/skills/magpie/scripts/__tests__/pr-activity.test.ts +281 -0
  31. package/skills/magpie/scripts/__tests__/preview-cmd.test.ts +4 -4
  32. package/skills/magpie/scripts/__tests__/previous-findings.test.ts +267 -0
  33. package/skills/magpie/scripts/__tests__/proof-cmd.test.ts +260 -0
  34. package/skills/magpie/scripts/__tests__/proof.test.ts +226 -0
  35. package/skills/magpie/scripts/__tests__/refresh.test.ts +71 -0
  36. package/skills/magpie/scripts/__tests__/render-annotation.test.ts +34 -0
  37. package/skills/magpie/scripts/__tests__/render-findings.test.ts +75 -0
  38. package/skills/magpie/scripts/__tests__/render-progress.test.ts +9 -0
  39. package/skills/magpie/scripts/__tests__/setup-cmd.test.ts +376 -1
  40. package/skills/magpie/scripts/__tests__/shard-coverage.test.ts +79 -0
  41. package/skills/magpie/scripts/__tests__/skill-lint.test.ts +48 -2
  42. package/skills/magpie/scripts/__tests__/status-cmd.test.ts +20 -8
  43. package/skills/magpie/scripts/__tests__/types.test.ts +46 -0
  44. package/skills/magpie/scripts/cleanup-cmd.ts +88 -0
  45. package/skills/magpie/scripts/critic-cmd.ts +2 -0
  46. package/skills/magpie/scripts/critic.ts +4 -0
  47. package/skills/magpie/scripts/dedupe-cmd.ts +4 -0
  48. package/skills/magpie/scripts/evidence-filter.ts +10 -0
  49. package/skills/magpie/scripts/git-diff.ts +50 -19
  50. package/skills/magpie/scripts/handoff-cmd.ts +42 -3
  51. package/skills/magpie/scripts/history.ts +143 -0
  52. package/skills/magpie/scripts/impact-cmd.ts +202 -0
  53. package/skills/magpie/scripts/impact-render.ts +158 -0
  54. package/skills/magpie/scripts/impact.ts +622 -0
  55. package/skills/magpie/scripts/incremental.ts +44 -5
  56. package/skills/magpie/scripts/labels-cmd.ts +7 -3
  57. package/skills/magpie/scripts/post-cmd.ts +46 -0
  58. package/skills/magpie/scripts/pr-activity.ts +243 -0
  59. package/skills/magpie/scripts/preview-cmd.ts +3 -0
  60. package/skills/magpie/scripts/previous-findings.ts +181 -0
  61. package/skills/magpie/scripts/proof-cmd.ts +226 -0
  62. package/skills/magpie/scripts/proof.ts +150 -0
  63. package/skills/magpie/scripts/refresh.ts +11 -0
  64. package/skills/magpie/scripts/render-annotation.ts +17 -0
  65. package/skills/magpie/scripts/render-cmd.ts +12 -0
  66. package/skills/magpie/scripts/render-findings.ts +37 -1
  67. package/skills/magpie/scripts/render-progress.ts +3 -0
  68. package/skills/magpie/scripts/setup-cmd.ts +142 -11
  69. package/skills/magpie/scripts/shard-coverage.ts +53 -0
  70. package/skills/magpie/scripts/status-cmd.ts +2 -0
  71. package/skills/magpie/scripts/types.ts +39 -0
  72. package/skills/magpie/skill.json +1 -1
  73. package/skills/magpie/templates/styles.css +30 -0
  74. package/skills/sluice/scripts/status.sh +32 -13
  75. package/skills/sluice/skill.json +1 -1
@@ -0,0 +1,498 @@
1
+ import { expect, test } from 'bun:test'
2
+ import { mkdtemp, readFile, writeFile } from 'node:fs/promises'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+ import type { ReviewFinding } from '../../../scripts/types.ts'
6
+ import {
7
+ assembleSpecialistPrompt,
8
+ countOutsideCitations,
9
+ EXCLUDED_FILES_BLOCK,
10
+ numberProbeFindings,
11
+ readImpactPart,
12
+ renderBriefPart,
13
+ RUN_HEADER_SHARDED,
14
+ RUN_HEADER_UNSHARDED,
15
+ BRIEF_CLOSING,
16
+ PostSessionError,
17
+ runRepeats,
18
+ sessionOutcome,
19
+ transcriptBadLines,
20
+ transcriptReadPath,
21
+ } from '../probe-specialist.ts'
22
+
23
+ const FENCE = '```'
24
+
25
+ const SPECIALISTS_MD = `# Specialist prompts
26
+
27
+ Prose for the orchestrator.
28
+
29
+ ## Output Contract
30
+
31
+ Write findings to <RUN_DIR>/findings/<focus>.json.
32
+
33
+ ${FENCE}
34
+ { "id": string }
35
+ ${FENCE}
36
+
37
+ If you have no findings, write [].
38
+
39
+ ## Focus blocks
40
+
41
+ ### security
42
+
43
+ ${FENCE}magpie-specialist-security
44
+ You are a security reviewer.
45
+
46
+ ## What to look for
47
+
48
+ Injection.
49
+ ${FENCE}
50
+
51
+ ### bugs
52
+
53
+ ${FENCE}magpie-specialist-bugs
54
+ You are a bug hunter.
55
+ ${FENCE}
56
+ `
57
+
58
+ const IMPACT_PART = `
59
+ 6. The impact map:
60
+
61
+ ${FENCE}magpie-impact-part
62
+ ## Outside the diff
63
+
64
+ <top entries>
65
+
66
+ <the path above> has the full list.
67
+ ${FENCE}
68
+ `
69
+
70
+ const base = {
71
+ specialistsMd: SPECIALISTS_MD,
72
+ focus: 'security',
73
+ runDir: '/runs/r1',
74
+ worktree: '/runs/r1/worktree',
75
+ target: 'PR #70',
76
+ shard: null,
77
+ brief: null,
78
+ excludedFiles: false,
79
+ impact: null,
80
+ }
81
+
82
+ const SECURITY_BLOCK = 'You are a security reviewer.\n\n## What to look for\n\nInjection.'
83
+ const CONTRACT =
84
+ '## Output Contract\n\nWrite findings to /runs/r1/findings/security.json.\n\n' +
85
+ '```\n{ "id": string }\n```\n\nIf you have no findings, write [].'
86
+
87
+ test('an unsharded prompt is the focus block, the run header and the output contract', () => {
88
+ expect(assembleSpecialistPrompt(base)).toBe(
89
+ `${SECURITY_BLOCK}\n\n` +
90
+ 'You are reviewing PR #70.\nWorking directory: /runs/r1/worktree\nDiff: /runs/r1/diff.patch\n\n' +
91
+ CONTRACT,
92
+ )
93
+ })
94
+
95
+ test('the output contract names the focus id, not a placeholder the session fills in', () => {
96
+ const prompt = assembleSpecialistPrompt(base)
97
+ expect(prompt).toContain('Write findings to /runs/r1/findings/security.json.')
98
+ expect(prompt).not.toContain('<focus>')
99
+ })
100
+
101
+ test('a sharded prompt names the shard, its patch and the scope rule', () => {
102
+ expect(assembleSpecialistPrompt({ ...base, shard: { n: 2, total: 5 } })).toBe(
103
+ `${SECURITY_BLOCK}\n\n` +
104
+ 'You are reviewing PR #70.\nWorking directory: /runs/r1/worktree\nShard: 2 of 5\n' +
105
+ 'Diff: /runs/r1/shards/shard-2.patch\n\n' +
106
+ 'This shard is your review scope. The other shards belong to other agents working\n' +
107
+ 'in parallel: do not review files outside your shard, and do not report findings\n' +
108
+ 'anchored to them. Reading any file in the worktree for context is expected and\n' +
109
+ 'encouraged.\n\n' +
110
+ CONTRACT,
111
+ )
112
+ })
113
+
114
+ test('the excluded-files block and the brief follow the contract in that order', () => {
115
+ const prompt = assembleSpecialistPrompt({
116
+ ...base,
117
+ excludedFiles: true,
118
+ brief: '## What this PR is for\n\nLocks the namespace.',
119
+ })
120
+ expect(prompt).toBe(
121
+ `${SECURITY_BLOCK}\n\n` +
122
+ 'You are reviewing PR #70.\nWorking directory: /runs/r1/worktree\nDiff: /runs/r1/diff.patch\n\n' +
123
+ `${CONTRACT}\n\n` +
124
+ 'Files excluded by the path filter are absent from your diff but present in the\n' +
125
+ 'worktree, with their full patches in /runs/r1/diff.full.patch and the exclusion\n' +
126
+ 'list in /runs/r1/excluded-files.json. When a change in your scope implies a change\n' +
127
+ 'in an excluded file (a migration implies a model snapshot, a schema change implies\n' +
128
+ 'generated types), open it and cross-check rather than treating it as out of scope.\n\n' +
129
+ '## What this PR is for\n\nLocks the namespace.',
130
+ )
131
+ })
132
+
133
+ test('an impact prompt puts the outside-diff line under Diff and the impact part last', () => {
134
+ const prompt = assembleSpecialistPrompt({
135
+ ...base,
136
+ specialistsMd: SPECIALISTS_MD + IMPACT_PART,
137
+ shard: { n: 2, total: 5 },
138
+ brief: '## What this PR is for\n\nLocks the namespace.',
139
+ impact: {
140
+ path: '/runs/r1/impact.shard-2.md',
141
+ inline: '### Sibling routes\n- `/a` (diff: a.ts:3)',
142
+ },
143
+ })
144
+ expect(prompt).toBe(
145
+ `${SECURITY_BLOCK}\n\n` +
146
+ 'You are reviewing PR #70.\nWorking directory: /runs/r1/worktree\nShard: 2 of 5\n' +
147
+ 'Diff: /runs/r1/shards/shard-2.patch\nOutside the diff: /runs/r1/impact.shard-2.md\n\n' +
148
+ 'This shard is your review scope. The other shards belong to other agents working\n' +
149
+ 'in parallel: do not review files outside your shard, and do not report findings\n' +
150
+ 'anchored to them. Reading any file in the worktree for context is expected and\n' +
151
+ 'encouraged.\n\n' +
152
+ `${CONTRACT}\n\n` +
153
+ '## What this PR is for\n\nLocks the namespace.\n\n' +
154
+ '## Outside the diff\n\n### Sibling routes\n- `/a` (diff: a.ts:3)\n\n' +
155
+ '/runs/r1/impact.shard-2.md has the full list.',
156
+ )
157
+ })
158
+
159
+ test('dollar patterns in the inline entries are pasted as written', () => {
160
+ const prompt = assembleSpecialistPrompt({
161
+ ...base,
162
+ specialistsMd: SPECIALISTS_MD + IMPACT_PART,
163
+ impact: { path: '/runs/r1/impact.md', inline: " - a.ts:3 `s.replace(x, '$&$1')` `$'` `$$`" },
164
+ })
165
+ expect(prompt).toEndWith(
166
+ "## Outside the diff\n\n - a.ts:3 `s.replace(x, '$&$1')` `$'` `$$`\n\n" +
167
+ '/runs/r1/impact.md has the full list.',
168
+ )
169
+ })
170
+
171
+ test('dollar patterns in the impact path are pasted as written', () => {
172
+ const prompt = assembleSpecialistPrompt({
173
+ ...base,
174
+ specialistsMd: SPECIALISTS_MD + IMPACT_PART,
175
+ impact: { path: "/runs/$&$'/impact.md", inline: 'top' },
176
+ })
177
+ expect(prompt).toEndWith("top\n\n/runs/$&$'/impact.md has the full list.")
178
+ expect(prompt).toContain("Diff: /runs/r1/diff.patch\nOutside the diff: /runs/$&$'/impact.md\n")
179
+ })
180
+
181
+ test('an unsharded impact prompt adds the outside-diff line as the header’s last line', () => {
182
+ const prompt = assembleSpecialistPrompt({
183
+ ...base,
184
+ specialistsMd: SPECIALISTS_MD + IMPACT_PART,
185
+ impact: { path: '/runs/r1/impact.md', inline: 'top' },
186
+ })
187
+ expect(prompt).toContain(
188
+ 'Diff: /runs/r1/diff.patch\nOutside the diff: /runs/r1/impact.md\n\n## Output Contract',
189
+ )
190
+ })
191
+
192
+ test('an impact prompt without the impact part in specialists.md is refused, naming the block', () => {
193
+ expect(() =>
194
+ assembleSpecialistPrompt({ ...base, impact: { path: '/runs/r1/impact.md', inline: 'top' } }),
195
+ ).toThrow('magpie-impact-part')
196
+ })
197
+
198
+ test('a focus with no block in specialists.md is refused, naming the focus', () => {
199
+ expect(() => assembleSpecialistPrompt({ ...base, focus: 'performance' })).toThrow(
200
+ 'magpie-specialist-performance',
201
+ )
202
+ })
203
+
204
+ test('the live specialists.md carries the run header, excluded-files and brief text verbatim', async () => {
205
+ const live = await readFile(join(import.meta.dir, '../../../references/specialists.md'), 'utf8')
206
+ for (const template of [
207
+ RUN_HEADER_UNSHARDED,
208
+ RUN_HEADER_SHARDED,
209
+ EXCLUDED_FILES_BLOCK,
210
+ BRIEF_CLOSING,
211
+ ]) {
212
+ expect(live).toContain(template)
213
+ }
214
+ })
215
+
216
+ test('a full brief renders every heading in the part-5 order with the closing paragraph', () => {
217
+ expect(
218
+ renderBriefPart({
219
+ purpose: 'Lock the UUID namespace.',
220
+ changes: ['Adds a namespace constant', 'Verifies reports'],
221
+ watchItems: ['Old reports'],
222
+ unclear: ['Is the namespace public?'],
223
+ reviewRules: [{ rule: 'No raw SQL', source: 'CONTRIBUTING.md' }],
224
+ }),
225
+ ).toBe(
226
+ '## What this PR is for\n\nLock the UUID namespace.\n\n' +
227
+ 'What it does:\n- Adds a namespace constant\n- Verifies reports\n\n' +
228
+ 'Watch items:\n- Old reports\n\n' +
229
+ 'Open questions the scout could not resolve:\n- Is the namespace public?\n\n' +
230
+ 'Repository review rules:\n- No raw SQL (CONTRIBUTING.md)\n\n' +
231
+ "This brief is the author's claim as understood by a reader who has not reviewed the\n" +
232
+ 'code. It is not ground truth. Where the diff contradicts it, that is a finding in\n' +
233
+ 'your domain, not a correction to the brief. A watch item is a pointer, not a verdict:\n' +
234
+ 'escalate it into a finding with your own risk fields, or leave it alone. A repository\n' +
235
+ 'review rule is a convention the maintainers wrote down: a change that breaks one is a\n' +
236
+ "finding in your domain when the rule bears on it, citing the rule's source.",
237
+ )
238
+ })
239
+
240
+ test('a brief with no watch items, open questions or rules omits those headings', () => {
241
+ expect(
242
+ renderBriefPart({
243
+ purpose: 'Lock it.',
244
+ changes: ['Adds a constant'],
245
+ watchItems: [],
246
+ unclear: [],
247
+ reviewRules: [],
248
+ }),
249
+ ).toBe(
250
+ '## What this PR is for\n\nLock it.\n\nWhat it does:\n- Adds a constant\n\n' +
251
+ "This brief is the author's claim as understood by a reader who has not reviewed the\n" +
252
+ 'code. It is not ground truth. Where the diff contradicts it, that is a finding in\n' +
253
+ 'your domain, not a correction to the brief. A watch item is a pointer, not a verdict:\n' +
254
+ 'escalate it into a finding with your own risk fields, or leave it alone. A repository\n' +
255
+ 'review rule is a convention the maintainers wrote down: a change that breaks one is a\n' +
256
+ "finding in your domain when the rule bears on it, citing the rule's source.",
257
+ )
258
+ })
259
+
260
+ function assistantToolUse(name: string, input: Record<string, unknown>): string {
261
+ return JSON.stringify({
262
+ type: 'assistant',
263
+ message: {
264
+ content: [
265
+ { type: 'text', text: 'looking' },
266
+ { type: 'tool_use', name, input },
267
+ ],
268
+ },
269
+ })
270
+ }
271
+
272
+ const STREAM_HEAD = '{"type":"system","subtype":"init"}\n'
273
+
274
+ test('a transcript that Reads the impact file counts as reading it', () => {
275
+ const stream = `${STREAM_HEAD}${assistantToolUse('Read', { file_path: '/runs/r1/impact.md' })}\n`
276
+ expect(transcriptReadPath(stream, '/runs/r1/impact.md')).toBe(true)
277
+ })
278
+
279
+ test('a transcript that only cats the impact file through Bash does not count', () => {
280
+ const stream = `${STREAM_HEAD}${assistantToolUse('Bash', { command: 'cat /runs/r1/impact.md' })}\n`
281
+ expect(transcriptReadPath(stream, '/runs/r1/impact.md')).toBe(false)
282
+ })
283
+
284
+ test('a transcript that Reads other files does not count as reading the impact file', () => {
285
+ const stream =
286
+ `${STREAM_HEAD}${assistantToolUse('Read', { file_path: '/runs/r1/impact.shard-2.md' })}\n` +
287
+ `${assistantToolUse('Read', { file_path: '/runs/r1/diff.patch' })}\n`
288
+ expect(transcriptReadPath(stream, '/runs/r1/impact.md')).toBe(false)
289
+ })
290
+
291
+ const READ_IMPACT = assistantToolUse('Read', { file_path: '/runs/r1/impact.md' })
292
+
293
+ test('a transcript cut off mid-line still shows the Read made before the cut', () => {
294
+ const stream = `${STREAM_HEAD}${READ_IMPACT}\n{"type":"assistant","message":{"con`
295
+ expect(transcriptReadPath(stream, '/runs/r1/impact.md')).toBe(true)
296
+ })
297
+
298
+ test('a transcript cut off mid-line counts the cut line as bad', () => {
299
+ const stream = `${STREAM_HEAD}${READ_IMPACT}\n{"type":"assistant","message":{"con`
300
+ expect(transcriptBadLines(stream)).toBe(1)
301
+ })
302
+
303
+ test('a whole transcript has no bad lines', () => {
304
+ expect(transcriptBadLines(`${STREAM_HEAD}${READ_IMPACT}\n`)).toBe(0)
305
+ })
306
+
307
+ test('a successful session reports its cost and no error', () => {
308
+ const stream = `${STREAM_HEAD}{"type":"result","subtype":"success","is_error":false,"total_cost_usd":1.25}\n`
309
+ expect(sessionOutcome(stream, 0, '')).toEqual({ costUsd: 1.25, error: null })
310
+ })
311
+
312
+ test('a result event with no cost reports the cost as null', () => {
313
+ expect(
314
+ sessionOutcome(`${STREAM_HEAD}{"type":"result","subtype":"success","is_error":false}\n`, 0, ''),
315
+ ).toEqual({ costUsd: null, error: null })
316
+ })
317
+
318
+ test('a session that ended in error keeps its cost and records the error', () => {
319
+ const stream =
320
+ `${STREAM_HEAD}{"type":"result","subtype":"error_max_budget_usd","is_error":true,` +
321
+ '"total_cost_usd":3.02,"result":"budget spent"}\n'
322
+ expect(sessionOutcome(stream, 1, 'boom')).toEqual({
323
+ costUsd: 3.02,
324
+ error: 'session ended in error (error_max_budget_usd): budget spent; exit 1: boom',
325
+ })
326
+ })
327
+
328
+ test('a session with no result event records that instead of throwing', () => {
329
+ expect(sessionOutcome(STREAM_HEAD, 0, '')).toEqual({
330
+ costUsd: null,
331
+ error: 'stream-json has no result event',
332
+ })
333
+ })
334
+
335
+ test('a killed session whose transcript is cut off records no result event and the exit', () => {
336
+ expect(sessionOutcome(`${STREAM_HEAD}{"type":"res`, 137, 'killed')).toEqual({
337
+ costUsd: null,
338
+ error: 'stream-json has no result event; exit 137: killed',
339
+ })
340
+ })
341
+
342
+ function finding(id: string, file: string, description: string): ReviewFinding {
343
+ return {
344
+ id,
345
+ file,
346
+ line: 1,
347
+ severity: 'medium',
348
+ risk: { impact: 'medium', likelihood: 'possible', confidence: 'medium', action: 'should-fix' },
349
+ title: 't',
350
+ description,
351
+ domain: 'security',
352
+ }
353
+ }
354
+
355
+ const IMPACT_MD = [
356
+ '### Other writers of the same data',
357
+ '- `reports` (diff: src/api/lock.ts:10)',
358
+ ' - src/db/writer.ts:44 `insert into reports`',
359
+ ' - src/api/lock.ts:80 `update reports`',
360
+ ].join('\n')
361
+
362
+ test('a finding citing a map location outside the shard counts as an outside citation', () => {
363
+ const findings = [
364
+ finding('a', 'src/api/lock.ts', 'Observation: src/db/writer.ts:44 also writes reports.'),
365
+ ]
366
+ expect(countOutsideCitations(findings, IMPACT_MD, ['src/api/lock.ts'])).toBe(1)
367
+ })
368
+
369
+ test('a map location inside the shard, or a location not in the map, is not an outside citation', () => {
370
+ const findings = [
371
+ finding('a', 'src/api/lock.ts', 'Observation: src/api/lock.ts:80 updates reports.'),
372
+ finding('b', 'src/api/lock.ts', 'Observation: src/db/other.ts:12 reads reports.'),
373
+ finding('c', 'src/api/lock.ts', 'Observation: src/db/writer.ts:45 is near the writer.'),
374
+ ]
375
+ expect(countOutsideCitations(findings, IMPACT_MD, ['src/api/lock.ts'])).toBe(0)
376
+ })
377
+
378
+ test('a finding citing several outside locations counts once', () => {
379
+ const impact = `${IMPACT_MD}\n - src/db/audit.ts:7 \`insert into reports\``
380
+ const findings = [
381
+ finding('a', 'src/api/lock.ts', 'See src/db/writer.ts:44 and src/db/audit.ts:7.'),
382
+ ]
383
+ expect(countOutsideCitations(findings, impact, ['src/api/lock.ts'])).toBe(1)
384
+ })
385
+
386
+ const RISK = { impact: 'high', likelihood: 'likely', confidence: 'high', action: 'must-fix' }
387
+
388
+ test('findings without an id are numbered by focus and shard from 1', () => {
389
+ const findings = numberProbeFindings(
390
+ [
391
+ { file: 'a.ts', line: 1, risk: RISK, title: 'x', description: 'd' },
392
+ { id: 'security-7', file: 'b.ts', line: 2, risk: RISK, title: 'y', description: 'd' },
393
+ { file: 'c.ts', line: 3, risk: RISK, title: 'z', description: 'd' },
394
+ ],
395
+ 'security',
396
+ 3,
397
+ 'findings/security.shard-3.json',
398
+ )
399
+ expect(findings.map((f) => f.id)).toEqual(['security-s3-1', 'security-7', 'security-s3-3'])
400
+ })
401
+
402
+ test('a findings file that is not an array is rejected, naming it', () => {
403
+ expect(() => numberProbeFindings({}, 'security', 1, 'findings/security.json')).toThrow(
404
+ 'findings/security.json',
405
+ )
406
+ })
407
+
408
+ async function runDirWith(files: Record<string, string>): Promise<string> {
409
+ const dir = await mkdtemp(join(tmpdir(), 'magpie-probe-impact-'))
410
+ for (const [name, text] of Object.entries(files)) await writeFile(join(dir, name), text)
411
+ return dir
412
+ }
413
+
414
+ test('a shard with no impact file is refused, naming the shard', async () => {
415
+ const dir = await runDirWith({ 'impact.md': 'whole run\n', 'impact.brief.md': 'top\n' })
416
+ await expect(readImpactPart(dir, { n: 2, total: 5 })).rejects.toThrow('shard 2')
417
+ })
418
+
419
+ test('a sharded run reads its shard’s impact file and brief', async () => {
420
+ const dir = await runDirWith({
421
+ 'impact.shard-2.md': 'full\n',
422
+ 'impact.shard-2.brief.md': 'top entries\n',
423
+ })
424
+ expect(await readImpactPart(dir, { n: 2, total: 5 })).toEqual({
425
+ path: join(dir, 'impact.shard-2.md'),
426
+ inline: 'top entries',
427
+ })
428
+ })
429
+
430
+ test('an unsharded run with no impact.md is refused', async () => {
431
+ const dir = await runDirWith({})
432
+ await expect(readImpactPart(dir, null)).rejects.toThrow('impact.md')
433
+ })
434
+
435
+ const LIVE_MD = join(import.meta.dir, '../../../references/specialists.md')
436
+
437
+ test('a base prompt from the live specialists.md carries the focus block and the whole contract', async () => {
438
+ const prompt = assembleSpecialistPrompt({
439
+ ...base,
440
+ specialistsMd: await readFile(LIVE_MD, 'utf8'),
441
+ focus: 'bugs',
442
+ })
443
+ expect(prompt).toStartWith(
444
+ 'You are a senior software engineer specialized in finding bugs through code review.\n',
445
+ )
446
+ expect(prompt).toContain(
447
+ 'If you have no findings, write []. Return as your final tool result a single line: ' +
448
+ '`bugs: <N> findings (<critical>/<high>/<medium>/<low> by risk.impact)`. ' +
449
+ 'Do not include other prose.',
450
+ )
451
+ })
452
+
453
+ test('the live specialists.md names the part-5 headings renderBriefPart writes', async () => {
454
+ const live = await readFile(LIVE_MD, 'utf8')
455
+ for (const heading of [
456
+ '## What this PR is for',
457
+ 'What it does:',
458
+ 'Watch items:',
459
+ 'Open questions the scout could not resolve:',
460
+ 'Repository review rules:',
461
+ ]) {
462
+ expect(live).toContain(`\n${heading}\n`)
463
+ }
464
+ })
465
+
466
+ test('a repeat that fails after its session is reported and the next repeat still runs', async () => {
467
+ const ran: number[] = []
468
+ const warnings: string[] = []
469
+ const failed = await runRepeats(
470
+ 3,
471
+ async (repeat) => {
472
+ ran.push(repeat)
473
+ if (repeat === 2) throw new PostSessionError('judge failed (probe result written to /r.json)')
474
+ },
475
+ (line) => warnings.push(line),
476
+ )
477
+ expect({ ran, failed, warnings }).toEqual({
478
+ ran: [1, 2, 3],
479
+ failed: 1,
480
+ warnings: [
481
+ 'repeat 2 failed after its session: judge failed (probe result written to /r.json)\n',
482
+ ],
483
+ })
484
+ })
485
+
486
+ test('a repeat that fails before its session stops the remaining repeats', async () => {
487
+ const ran: number[] = []
488
+ const run = runRepeats(
489
+ 3,
490
+ async (repeat) => {
491
+ ran.push(repeat)
492
+ if (repeat === 1) throw new Error('worktree add failed')
493
+ },
494
+ () => {},
495
+ )
496
+ await expect(run).rejects.toThrow('worktree add failed')
497
+ expect(ran).toEqual([1])
498
+ })
@@ -114,6 +114,7 @@ test('diagnostics carry the log, manifest, findings files, deduped count and cri
114
114
  criticDropped: [{ id: 'b', reason: 'speculative' }],
115
115
  evidenceDropped: null,
116
116
  thresholdDropped: null,
117
+ impactBytes: null,
117
118
  })
118
119
  })
119
120
 
@@ -129,6 +130,7 @@ test('diagnostics report absent manifest, deduped and drop files as null', async
129
130
  criticDropped: null,
130
131
  evidenceDropped: null,
131
132
  thresholdDropped: null,
133
+ impactBytes: null,
132
134
  })
133
135
  })
134
136
 
@@ -162,3 +164,10 @@ test('a deduped findings file that is not an array is rejected', async () => {
162
164
  await writeFile(join(dir, 'findings.deduped.json'), '{"id":"a"}')
163
165
  await expect(replayDiagnostics(dir)).rejects.toThrow('findings.deduped.json')
164
166
  })
167
+
168
+ test('diagnostics report the impact map size when the run wrote impact.md', async () => {
169
+ const dir = await scratchRunDir()
170
+ await writeFile(join(dir, 'log.jsonl'), '{"stage":"setup","status":"done"}\n')
171
+ await writeFile(join(dir, 'impact.md'), '### Sibling routes\n- `/é`\n')
172
+ expect((await replayDiagnostics(dir)).impactBytes).toBe(27)
173
+ })