@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,365 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import type { ImpactMap } from '../impact.ts'
3
+ import {
4
+ IMPACT_BRIEF_CAP_BYTES,
5
+ IMPACT_BRIEF_MAX_CHILDREN,
6
+ renderImpactMarkdown,
7
+ } from '../impact-render.ts'
8
+
9
+ // Entries are deliberately out of render order so the tests pin the sort.
10
+ function sampleMap(): ImpactMap {
11
+ return {
12
+ version: 1,
13
+ symbols: [
14
+ {
15
+ name: 'formatTotal',
16
+ kind: 'added',
17
+ definedAt: [{ file: 'src/total.ts', line: 3 }],
18
+ shard: 1,
19
+ hits: 1,
20
+ references: [{ file: 'src/cart.ts', line: 7, text: 'formatTotal(cart)' }],
21
+ },
22
+ {
23
+ name: 'render',
24
+ kind: 'changed',
25
+ definedAt: [{ file: 'src/view.ts', line: 1 }],
26
+ shard: 1,
27
+ hits: 230,
28
+ references: [],
29
+ },
30
+ {
31
+ name: 'loadUser',
32
+ kind: 'changed',
33
+ definedAt: [{ file: 'src/user.ts', line: 5 }],
34
+ shard: 0,
35
+ hits: 30,
36
+ references: [
37
+ { file: 'src/api.ts', line: 2, text: 'loadUser(id)' },
38
+ { file: 'src/page.ts', line: 8, text: 'await loadUser(params.id)' },
39
+ ],
40
+ },
41
+ {
42
+ name: 'parseRow',
43
+ kind: 'removed',
44
+ definedAt: [{ file: 'src/a.ts', line: 2 }],
45
+ shard: 1,
46
+ hits: 1,
47
+ references: [{ file: 'src/b.ts', line: 9, text: 'parseRow(r)' }],
48
+ },
49
+ ],
50
+ uses: [
51
+ {
52
+ name: 'getConfig',
53
+ usedIn: { file: 'src/user.ts', line: 12 },
54
+ shard: 0,
55
+ definedAt: [{ file: 'lib/config.ts', line: 1, text: 'export function getConfig() {' }],
56
+ },
57
+ ],
58
+ stores: [
59
+ {
60
+ name: 'orders',
61
+ kind: 'table',
62
+ usedIn: [{ file: 'src/db.ts', line: 10 }],
63
+ shard: 0,
64
+ writers: [{ file: 'jobs/sync.ts', line: 4, text: 'INSERT INTO orders (id) VALUES (1)' }],
65
+ },
66
+ ],
67
+ routes: [
68
+ {
69
+ path: '/api/users/:id',
70
+ usedIn: { file: 'src/routes.ts', line: 20 },
71
+ shard: 1,
72
+ siblings: [{ file: 'src/admin.ts', line: 3, text: "router.get('/api/users', list)" }],
73
+ },
74
+ ],
75
+ docs: [
76
+ {
77
+ file: 'docs/user.md',
78
+ line: 4,
79
+ text: 'Call loadUser before render.',
80
+ mentions: ['loadUser', 'render'],
81
+ shards: [1],
82
+ },
83
+ ],
84
+ }
85
+ }
86
+
87
+ function emptyMap(): ImpactMap {
88
+ return { version: 1, symbols: [], uses: [], stores: [], routes: [], docs: [] }
89
+ }
90
+
91
+ const FULL = `### Other writers of the same data
92
+ - \`orders\` (diff: src/db.ts:10)
93
+ - jobs/sync.ts:4 \`INSERT INTO orders (id) VALUES (1)\`
94
+
95
+ ### References to changed symbols
96
+ - \`parseRow\` (diff: src/a.ts:2)
97
+ - src/b.ts:9 \`parseRow(r)\`
98
+ - \`loadUser\` (diff: src/user.ts:5)
99
+ - src/api.ts:2 \`loadUser(id)\`
100
+ - src/page.ts:8 \`await loadUser(params.id)\`
101
+ - 2 of 30 shown
102
+ - \`render\`: 230 hits, too common to list
103
+
104
+ ### References to added symbols
105
+ - \`formatTotal\` (diff: src/total.ts:3)
106
+ - src/cart.ts:7 \`formatTotal(cart)\`
107
+
108
+ ### Definitions this change relies on
109
+ - \`getConfig\` (diff: src/user.ts:12)
110
+ - lib/config.ts:1 \`export function getConfig() {\`
111
+
112
+ ### Sibling routes
113
+ - \`/api/users/:id\` (diff: src/routes.ts:20)
114
+ - src/admin.ts:3 \`router.get('/api/users', list)\`
115
+
116
+ ### Docs naming what changed
117
+ - docs/user.md:4 \`Call loadUser before render.\` (names: loadUser, render)`
118
+
119
+ const SHARD_0 = `### Other writers of the same data
120
+ - \`orders\` (diff: src/db.ts:10)
121
+ - jobs/sync.ts:4 \`INSERT INTO orders (id) VALUES (1)\`
122
+
123
+ ### References to changed symbols
124
+ - \`loadUser\` (diff: src/user.ts:5)
125
+ - src/api.ts:2 \`loadUser(id)\`
126
+ - src/page.ts:8 \`await loadUser(params.id)\`
127
+ - 2 of 30 shown
128
+
129
+ ### Definitions this change relies on
130
+ - \`getConfig\` (diff: src/user.ts:12)
131
+ - lib/config.ts:1 \`export function getConfig() {\``
132
+
133
+ const SHARD_0_CUT_AFTER_TWO = `### Other writers of the same data
134
+ - \`orders\` (diff: src/db.ts:10)
135
+ - jobs/sync.ts:4 \`INSERT INTO orders (id) VALUES (1)\`
136
+
137
+ ### References to changed symbols
138
+ - \`loadUser\` (diff: src/user.ts:5)
139
+ - src/api.ts:2 \`loadUser(id)\`
140
+ - src/page.ts:8 \`await loadUser(params.id)\`
141
+ - 2 of 30 shown
142
+
143
+ 1 more entries in impact.json`
144
+
145
+ const bytes = (s: string): number => Buffer.byteLength(s, 'utf8')
146
+
147
+ describe('renderImpactMarkdown', () => {
148
+ test('renders every entry in section order with shard null', () => {
149
+ expect(renderImpactMarkdown(sampleMap(), { shard: null, capBytes: 100_000 })).toBe(FULL)
150
+ })
151
+
152
+ test('renders only the entries of the requested shard', () => {
153
+ expect(renderImpactMarkdown(sampleMap(), { shard: 0, capBytes: 100_000 })).toBe(SHARD_0)
154
+ })
155
+
156
+ test('returns an empty string when no entry matches the shard', () => {
157
+ expect(renderImpactMarkdown(sampleMap(), { shard: 5, capBytes: 100_000 })).toBe('')
158
+ })
159
+
160
+ test('returns an empty string for an empty map', () => {
161
+ expect(renderImpactMarkdown(emptyMap(), { shard: null, capBytes: 100_000 })).toBe('')
162
+ })
163
+
164
+ test('keeps whole entries that fit the cap and counts the rest', () => {
165
+ const capBytes = bytes(SHARD_0_CUT_AFTER_TWO)
166
+ expect(renderImpactMarkdown(sampleMap(), { shard: 0, capBytes })).toBe(SHARD_0_CUT_AFTER_TWO)
167
+ })
168
+
169
+ test('skips an entry too big for the cap and keeps a later one that fits', () => {
170
+ const map = emptyMap()
171
+ for (const [name, text] of [
172
+ ['aaaa', 'write(aaaa)'],
173
+ ['bbbb', 'y'.repeat(300)],
174
+ ['cccc', 'write(cccc)'],
175
+ ] as const) {
176
+ map.stores.push({
177
+ name,
178
+ kind: 'file',
179
+ usedIn: [{ file: `${name}.ts`, line: 1 }],
180
+ shard: null,
181
+ writers: [{ file: 'w.ts', line: 1, text }],
182
+ })
183
+ }
184
+ expect(renderImpactMarkdown(map, { shard: null, capBytes: 200 })).toBe(
185
+ `### Other writers of the same data
186
+ - \`aaaa\` (diff: aaaa.ts:1)
187
+ - w.ts:1 \`write(aaaa)\`
188
+ - \`cccc\` (diff: cccc.ts:1)
189
+ - w.ts:1 \`write(cccc)\`
190
+
191
+ 1 more entries in impact.json`,
192
+ )
193
+ })
194
+
195
+ test('limits each entry to maxChildren and says how many were shown', () => {
196
+ const refText = (i: number): string => `${'z'.repeat(116)}(${String(i).padStart(2, '0')})`
197
+ const twelveRefs = (file: string) =>
198
+ Array.from({ length: 12 }, (_, i) => ({ file, line: i + 1, text: refText(i + 1) }))
199
+ const map = emptyMap()
200
+ map.symbols.push(
201
+ {
202
+ name: 'alpha',
203
+ kind: 'changed',
204
+ definedAt: [{ file: 'src/a.ts', line: 1 }],
205
+ shard: null,
206
+ hits: 12,
207
+ references: twelveRefs('src/ra.ts'),
208
+ },
209
+ {
210
+ name: 'beta',
211
+ kind: 'changed',
212
+ definedAt: [{ file: 'src/b.ts', line: 1 }],
213
+ shard: null,
214
+ hits: 12,
215
+ references: twelveRefs('src/rb.ts'),
216
+ },
217
+ {
218
+ name: 'gamma',
219
+ kind: 'changed',
220
+ definedAt: [{ file: 'src/c.ts', line: 1 }],
221
+ shard: null,
222
+ hits: 1,
223
+ references: [{ file: 'src/g.ts', line: 5, text: 'gamma()' }],
224
+ },
225
+ )
226
+ const z = 'z'.repeat(116)
227
+ expect(
228
+ renderImpactMarkdown(map, {
229
+ shard: null,
230
+ capBytes: IMPACT_BRIEF_CAP_BYTES,
231
+ maxChildren: IMPACT_BRIEF_MAX_CHILDREN,
232
+ }),
233
+ ).toBe(`### References to changed symbols
234
+ - \`alpha\` (diff: src/a.ts:1)
235
+ - src/ra.ts:1 \`${z}(01)\`
236
+ - src/ra.ts:2 \`${z}(02)\`
237
+ - src/ra.ts:3 \`${z}(03)\`
238
+ - 3 of 12 shown
239
+ - \`beta\` (diff: src/b.ts:1)
240
+ - src/rb.ts:1 \`${z}(01)\`
241
+ - src/rb.ts:2 \`${z}(02)\`
242
+ - src/rb.ts:3 \`${z}(03)\`
243
+ - 3 of 12 shown
244
+ - \`gamma\` (diff: src/c.ts:1)
245
+ - src/g.ts:5 \`gamma()\``)
246
+ })
247
+
248
+ test('counts children of a non-symbol entry against their own number', () => {
249
+ const map = emptyMap()
250
+ map.routes.push({
251
+ path: '/api/x',
252
+ usedIn: { file: 'r.ts', line: 1 },
253
+ shard: null,
254
+ siblings: [
255
+ { file: 's.ts', line: 1, text: 'a' },
256
+ { file: 's.ts', line: 2, text: 'b' },
257
+ ],
258
+ })
259
+ expect(renderImpactMarkdown(map, { shard: null, capBytes: 1000, maxChildren: 1 })).toBe(
260
+ `### Sibling routes
261
+ - \`/api/x\` (diff: r.ts:1)
262
+ - s.ts:1 \`a\`
263
+ - 1 of 2 shown`,
264
+ )
265
+ })
266
+
267
+ test('throws naming the cap when the cap cannot hold the count line', () => {
268
+ expect(() => renderImpactMarkdown(sampleMap(), { shard: null, capBytes: 10 })).toThrow('10')
269
+ })
270
+
271
+ test('wraps text containing a backtick in a double-backtick span', () => {
272
+ const map = emptyMap()
273
+ map.routes.push({
274
+ path: '/api/:id',
275
+ usedIn: { file: 'r.ts', line: 1 },
276
+ shard: null,
277
+ siblings: [{ file: 's.ts', line: 4, text: `const u = \`/api/\${id}\`` }],
278
+ })
279
+ expect(renderImpactMarkdown(map, { shard: null, capBytes: 1000 })).toBe(
280
+ `### Sibling routes
281
+ - \`/api/:id\` (diff: r.ts:1)
282
+ - s.ts:4 \`\` const u = \`/api/\${id}\` \`\``,
283
+ )
284
+ })
285
+
286
+ test('leaves out entries with no facts outside the diff, without counting them', () => {
287
+ const map = emptyMap()
288
+ map.stores.push({
289
+ name: 'orders',
290
+ kind: 'table',
291
+ usedIn: [{ file: 'a.ts', line: 1 }],
292
+ shard: null,
293
+ writers: [],
294
+ })
295
+ map.routes.push({ path: '/x', usedIn: { file: 'a.ts', line: 2 }, shard: null, siblings: [] })
296
+ map.uses.push({
297
+ name: 'getUser',
298
+ usedIn: { file: 'a.ts', line: 3 },
299
+ shard: null,
300
+ definedAt: [],
301
+ })
302
+ map.symbols.push(
303
+ {
304
+ name: 'lonely',
305
+ kind: 'added',
306
+ definedAt: [{ file: 'a.ts', line: 4 }],
307
+ shard: null,
308
+ hits: 0,
309
+ references: [],
310
+ },
311
+ {
312
+ name: 'render',
313
+ kind: 'changed',
314
+ definedAt: [{ file: 'a.ts', line: 5 }],
315
+ shard: null,
316
+ hits: 300,
317
+ references: [],
318
+ },
319
+ )
320
+ map.docs.push({
321
+ file: 'README.md',
322
+ line: 1,
323
+ text: 'see render',
324
+ mentions: ['render'],
325
+ shards: [],
326
+ })
327
+ expect(renderImpactMarkdown(map, { shard: null, capBytes: 1000 })).toBe(
328
+ `### References to changed symbols
329
+ - \`render\`: 300 hits, too common to list
330
+
331
+ ### Docs naming what changed
332
+ - README.md:1 \`see render\` (names: render)`,
333
+ )
334
+ })
335
+
336
+ test('renders a doc in each shard it names', () => {
337
+ const map = emptyMap()
338
+ map.docs.push({ file: 'd.md', line: 2, text: 'see jobs', mentions: ['jobs'], shards: [2, 4] })
339
+ expect(renderImpactMarkdown(map, { shard: 4, capBytes: 1000 })).toBe(
340
+ '### Docs naming what changed\n- d.md:2 `see jobs` (names: jobs)',
341
+ )
342
+ })
343
+
344
+ test('leaves a doc out of a shard it does not name', () => {
345
+ const map = emptyMap()
346
+ map.docs.push({ file: 'd.md', line: 2, text: 'see jobs', mentions: ['jobs'], shards: [2, 4] })
347
+ expect(renderImpactMarkdown(map, { shard: 3, capBytes: 1000 })).toBe('')
348
+ })
349
+
350
+ test('counts the cap in UTF-8 bytes, not characters', () => {
351
+ const map = emptyMap()
352
+ map.stores.push({
353
+ name: 'cafés',
354
+ kind: 'file',
355
+ usedIn: [{ file: 'a.ts', line: 1 }],
356
+ shard: null,
357
+ writers: [{ file: 'b.ts', line: 2, text: "write('cafés')" }],
358
+ })
359
+ const whole =
360
+ "### Other writers of the same data\n- `cafés` (diff: a.ts:1)\n - b.ts:2 `write('cafés')`"
361
+ expect(renderImpactMarkdown(map, { shard: null, capBytes: whole.length })).toBe(
362
+ '1 more entries in impact.json',
363
+ )
364
+ })
365
+ })