@iceinvein/agent-skills 0.1.39 → 0.2.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 +18 -2
- package/dist/cli/index.js +105 -28
- package/package.json +1 -1
- package/skills/index.json +14 -2
- package/skills/magpie/README.md +2 -1
- package/skills/magpie/SKILL.md +138 -45
- package/skills/magpie/bin/magpie.ts +43 -0
- package/skills/magpie/fixtures/example-pr/brief.json +18 -0
- package/skills/magpie/fixtures/fake-gh-nodiff.sh +38 -0
- package/skills/magpie/fixtures/fake-gh.sh +14 -0
- package/skills/magpie/package.json +1 -1
- package/skills/magpie/references/peer-review.md +7 -2
- package/skills/magpie/references/scout.md +90 -0
- package/skills/magpie/references/specialists.md +117 -6
- package/skills/magpie/scripts/__tests__/cli.test.ts +101 -1
- package/skills/magpie/scripts/__tests__/dedupe-cmd.test.ts +187 -0
- package/skills/magpie/scripts/__tests__/diff-chunks.test.ts +51 -0
- package/skills/magpie/scripts/__tests__/filter-diff-preservation.test.ts +54 -0
- package/skills/magpie/scripts/__tests__/findings-files.test.ts +35 -0
- package/skills/magpie/scripts/__tests__/gh.test.ts +90 -0
- package/skills/magpie/scripts/__tests__/git-diff.test.ts +83 -0
- package/skills/magpie/scripts/__tests__/helpers/git-fixture.ts +47 -0
- package/skills/magpie/scripts/__tests__/path-filter.test.ts +27 -0
- package/skills/magpie/scripts/__tests__/preview-cmd.test.ts +16 -0
- package/skills/magpie/scripts/__tests__/refresh.test.ts +45 -0
- package/skills/magpie/scripts/__tests__/render-cmd.test.ts +173 -1
- package/skills/magpie/scripts/__tests__/render-findings.test.ts +151 -1
- package/skills/magpie/scripts/__tests__/render-progress.test.ts +42 -0
- package/skills/magpie/scripts/__tests__/setup-cmd.test.ts +83 -1
- package/skills/magpie/scripts/__tests__/shard.test.ts +165 -0
- package/skills/magpie/scripts/__tests__/skill-lint.test.ts +207 -7
- package/skills/magpie/scripts/__tests__/types.test.ts +47 -0
- package/skills/magpie/scripts/dedupe-cmd.ts +58 -3
- package/skills/magpie/scripts/diff-chunks.ts +28 -0
- package/skills/magpie/scripts/findings-files.ts +32 -0
- package/skills/magpie/scripts/gh.ts +68 -14
- package/skills/magpie/scripts/git-diff.ts +111 -0
- package/skills/magpie/scripts/path-filter.ts +9 -5
- package/skills/magpie/scripts/preview-cmd.ts +11 -1
- package/skills/magpie/scripts/refresh.ts +32 -3
- package/skills/magpie/scripts/render-cmd.ts +35 -11
- package/skills/magpie/scripts/render-findings.ts +78 -2
- package/skills/magpie/scripts/render-progress.ts +6 -1
- package/skills/magpie/scripts/setup-cmd.ts +38 -1
- package/skills/magpie/scripts/shard.ts +171 -0
- package/skills/magpie/scripts/status-cmd.ts +7 -4
- package/skills/magpie/scripts/types.ts +50 -0
- package/skills/magpie/skill.json +2 -2
- package/skills/magpie/templates/styles.css +66 -0
- package/skills/migrate/README.md +194 -0
- package/skills/migrate/SKILL.md +197 -0
- package/skills/migrate/bin/migrate +15 -0
- package/skills/migrate/bin/migrate.ts +309 -0
- package/skills/migrate/biome.json +35 -0
- package/skills/migrate/bun.lock +24 -0
- package/skills/migrate/docs/architecture.md +294 -0
- package/skills/migrate/docs/reference.md +590 -0
- package/skills/migrate/fixtures/tiny-express/GROUND-TRUTH.md +39 -0
- package/skills/migrate/fixtures/tiny-express/app.js +29 -0
- package/skills/migrate/fixtures/tiny-express/cron.js +6 -0
- package/skills/migrate/fixtures/tiny-express/reports/daily-users.json +6 -0
- package/skills/migrate/fixtures/tiny-express/schema.sql +12 -0
- package/skills/migrate/fixtures/tiny-express/settings.json +4 -0
- package/skills/migrate/fixtures/tiny-express/views/users.html +9 -0
- package/skills/migrate/fixtures/tiny-webforms/Controllers/UsersController.cs +68 -0
- package/skills/migrate/fixtures/tiny-webforms/Default.aspx +7 -0
- package/skills/migrate/fixtures/tiny-webforms/Default.aspx.cs +14 -0
- package/skills/migrate/fixtures/tiny-webforms/GROUND-TRUTH.md +50 -0
- package/skills/migrate/fixtures/tiny-webforms/Integrations/BillingClient.cs +16 -0
- package/skills/migrate/fixtures/tiny-webforms/Jobs/NightlyDigestJob.cs +33 -0
- package/skills/migrate/fixtures/tiny-webforms/Reports/DailyUsers.rdl +11 -0
- package/skills/migrate/fixtures/tiny-webforms/Schema.sql +12 -0
- package/skills/migrate/fixtures/tiny-webforms/Site.master +16 -0
- package/skills/migrate/fixtures/tiny-webforms/Users.aspx +8 -0
- package/skills/migrate/fixtures/tiny-webforms/Users.aspx.cs +14 -0
- package/skills/migrate/fixtures/tiny-webforms/web.config +10 -0
- package/skills/migrate/install.sh +68 -0
- package/skills/migrate/package.json +17 -0
- package/skills/migrate/references/phases/enumerate.md +291 -0
- package/skills/migrate/references/phases/extract.md +652 -0
- package/skills/migrate/references/phases/parity.md +275 -0
- package/skills/migrate/references/phases/probe.md +135 -0
- package/skills/migrate/references/phases/queue.md +242 -0
- package/skills/migrate/references/phases/seam.md +416 -0
- package/skills/migrate/references/recipes/README.md +116 -0
- package/skills/migrate/references/recipes/aspnet.md +287 -0
- package/skills/migrate/references/run-ops.md +280 -0
- package/skills/migrate/scripts/__tests__/census.test.ts +775 -0
- package/skills/migrate/scripts/__tests__/check.test.ts +458 -0
- package/skills/migrate/scripts/__tests__/citations.test.ts +156 -0
- package/skills/migrate/scripts/__tests__/cli.test.ts +183 -0
- package/skills/migrate/scripts/__tests__/concurrency.test.ts +164 -0
- package/skills/migrate/scripts/__tests__/config.test.ts +112 -0
- package/skills/migrate/scripts/__tests__/e2e-express.test.ts +1093 -0
- package/skills/migrate/scripts/__tests__/e2e-webforms.test.ts +1276 -0
- package/skills/migrate/scripts/__tests__/e2e.test.ts +320 -0
- package/skills/migrate/scripts/__tests__/ids.test.ts +38 -0
- package/skills/migrate/scripts/__tests__/import.test.ts +155 -0
- package/skills/migrate/scripts/__tests__/init.test.ts +192 -0
- package/skills/migrate/scripts/__tests__/leaks.test.ts +176 -0
- package/skills/migrate/scripts/__tests__/lock.test.ts +183 -0
- package/skills/migrate/scripts/__tests__/paths.test.ts +129 -0
- package/skills/migrate/scripts/__tests__/phase-cmd.test.ts +151 -0
- package/skills/migrate/scripts/__tests__/phases.test.ts +70 -0
- package/skills/migrate/scripts/__tests__/queue.test.ts +475 -0
- package/skills/migrate/scripts/__tests__/report.test.ts +150 -0
- package/skills/migrate/scripts/__tests__/run-state.test.ts +136 -0
- package/skills/migrate/scripts/__tests__/status-reset.test.ts +318 -0
- package/skills/migrate/scripts/__tests__/store.test.ts +132 -0
- package/skills/migrate/scripts/__tests__/validate.test.ts +54 -0
- package/skills/migrate/scripts/census-cmd.ts +109 -0
- package/skills/migrate/scripts/census.ts +342 -0
- package/skills/migrate/scripts/check-cmd.ts +24 -0
- package/skills/migrate/scripts/check.ts +376 -0
- package/skills/migrate/scripts/citations.ts +92 -0
- package/skills/migrate/scripts/config.ts +237 -0
- package/skills/migrate/scripts/ids.ts +31 -0
- package/skills/migrate/scripts/import-cmd.ts +141 -0
- package/skills/migrate/scripts/init-cmd.ts +118 -0
- package/skills/migrate/scripts/leaks.ts +184 -0
- package/skills/migrate/scripts/lock.ts +188 -0
- package/skills/migrate/scripts/paths.ts +103 -0
- package/skills/migrate/scripts/phase-cmd.ts +63 -0
- package/skills/migrate/scripts/phases.ts +113 -0
- package/skills/migrate/scripts/queue-cmd.ts +98 -0
- package/skills/migrate/scripts/queue.ts +258 -0
- package/skills/migrate/scripts/report-cmd.ts +47 -0
- package/skills/migrate/scripts/report.ts +131 -0
- package/skills/migrate/scripts/reset-cmd.ts +120 -0
- package/skills/migrate/scripts/status-cmd.ts +52 -0
- package/skills/migrate/scripts/store.ts +159 -0
- package/skills/migrate/scripts/types.ts +137 -0
- package/skills/migrate/scripts/validate.ts +221 -0
- package/skills/migrate/skill.json +33 -0
- package/skills/migrate/templates/config.toml +27 -0
- package/skills/migrate/templates/queue-item.md +17 -0
- package/skills/migrate/tsconfig.json +18 -0
- package/skills/migrate/uninstall.sh +31 -0
- package/skills/sluice/SKILL.md +82 -0
- package/skills/sluice/references/deep-channel.md +94 -0
- package/skills/sluice/references/finish.md +35 -0
- package/skills/sluice/references/intent.md +29 -0
- package/skills/sluice/references/review.md +42 -0
- package/skills/sluice/references/root-cause.md +38 -0
- package/skills/sluice/references/show-or-say.md +36 -0
- package/skills/sluice/references/test-first.md +35 -0
- package/skills/sluice/references/verify.md +26 -0
- package/skills/sluice/skill.json +32 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { mkdir, readdir, rm, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { type FileChunk, splitFileChunks } from './diff-chunks.ts'
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_SHARD_BUDGET = 6000
|
|
6
|
+
export const DEFAULT_SHARD_MAX_FILES = 80
|
|
7
|
+
|
|
8
|
+
export type ShardEntry = { id: number; path: string; files: string[]; lines: number }
|
|
9
|
+
export type ShardManifest = {
|
|
10
|
+
budget: number
|
|
11
|
+
maxFiles: number
|
|
12
|
+
totalFiles: number
|
|
13
|
+
totalLines: number
|
|
14
|
+
shards: ShardEntry[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The directory a file is grouped under, capped at two segments. Cohesion is
|
|
19
|
+
* not cosmetic: the architecture and code-smells focuses look for duplication,
|
|
20
|
+
* boundary violations, and shotgun surgery, and splitting a module across
|
|
21
|
+
* shards hides exactly those.
|
|
22
|
+
*/
|
|
23
|
+
export function groupKey(path: string): string {
|
|
24
|
+
const parts = path.split('/')
|
|
25
|
+
if (parts.length <= 1) return '.'
|
|
26
|
+
return parts.slice(0, Math.min(2, parts.length - 1)).join('/')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function planShards(chunks: FileChunk[], budget: number, maxFiles: number): FileChunk[][] {
|
|
30
|
+
const groups = new Map<string, FileChunk[]>()
|
|
31
|
+
for (const c of chunks) {
|
|
32
|
+
const key = groupKey(c.path)
|
|
33
|
+
const existing = groups.get(key)
|
|
34
|
+
if (existing) existing.push(c)
|
|
35
|
+
else groups.set(key, [c])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const shards: FileChunk[][] = []
|
|
39
|
+
let current: FileChunk[] = []
|
|
40
|
+
let currentLines = 0
|
|
41
|
+
const flush = () => {
|
|
42
|
+
if (current.length > 0) {
|
|
43
|
+
shards.push(current)
|
|
44
|
+
current = []
|
|
45
|
+
currentLines = 0
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const members of groups.values()) {
|
|
50
|
+
const groupLines = members.reduce((n, c) => n + c.lines, 0)
|
|
51
|
+
// Start a fresh shard rather than straddle a group across a boundary, when
|
|
52
|
+
// the whole group could fit on its own.
|
|
53
|
+
if (
|
|
54
|
+
current.length > 0 &&
|
|
55
|
+
(currentLines + groupLines > budget || current.length + members.length > maxFiles)
|
|
56
|
+
) {
|
|
57
|
+
flush()
|
|
58
|
+
}
|
|
59
|
+
for (const c of members) {
|
|
60
|
+
if (
|
|
61
|
+
current.length > 0 &&
|
|
62
|
+
(currentLines + c.lines > budget || current.length + 1 > maxFiles)
|
|
63
|
+
) {
|
|
64
|
+
flush()
|
|
65
|
+
}
|
|
66
|
+
current.push(c)
|
|
67
|
+
currentLines += c.lines
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
flush()
|
|
71
|
+
return shards
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Read `$RUN_DIR/diff.patch`, tolerating only its absence. A missing
|
|
75
|
+
* diff.patch means "nothing to shard"; anything else (permission error,
|
|
76
|
+
* disk error, EISDIR, ...) is a genuine failure and must propagate rather
|
|
77
|
+
* than silently turn into an empty manifest. */
|
|
78
|
+
async function readDiffPatch(runDir: string): Promise<string> {
|
|
79
|
+
try {
|
|
80
|
+
return await Bun.file(join(runDir, 'diff.patch')).text()
|
|
81
|
+
} catch (err) {
|
|
82
|
+
const code = err && typeof err === 'object' && 'code' in err ? err.code : undefined
|
|
83
|
+
if (code === 'ENOENT') return ''
|
|
84
|
+
throw err
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Delete `shard-<n>.patch` files left by an earlier split. Re-splitting 7 shards
|
|
89
|
+
* into 4 would otherwise leave `shard-5.patch` .. `shard-7.patch` on disk: the
|
|
90
|
+
* manifest is authoritative for dispatch, but a stale patch makes the directory
|
|
91
|
+
* claim coverage the manifest does not have, and a resume that reads shard ids
|
|
92
|
+
* off disk would review a file set that no longer matches its id. Only the shard
|
|
93
|
+
* patches are touched: `manifest.json` stays in place until it is overwritten at
|
|
94
|
+
* the end (so there is never a window with no manifest), and `diff.patch` lives
|
|
95
|
+
* outside this directory and is what a single-shard manifest points at. */
|
|
96
|
+
async function clearStaleShardPatches(shardsDir: string): Promise<void> {
|
|
97
|
+
let entries: string[]
|
|
98
|
+
try {
|
|
99
|
+
entries = await readdir(shardsDir)
|
|
100
|
+
} catch {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
for (const name of entries) {
|
|
104
|
+
if (/^shard-\d+\.patch$/.test(name)) await rm(join(shardsDir, name), { force: true })
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Split `$RUN_DIR/diff.patch` into budgeted shards. `diff.patch` is never
|
|
110
|
+
* modified: shards are views over it, which is what keeps the critic's diff
|
|
111
|
+
* excerpts, dedupe's evidence check, and the report's rendering working
|
|
112
|
+
* unchanged.
|
|
113
|
+
*
|
|
114
|
+
* Re-splitting an already-sharded run invalidates any
|
|
115
|
+
* `findings/<focus>.shard-<n>.json` from the previous split: the same shard id
|
|
116
|
+
* now names a different file set. Stage 4's resume rule keys on those files, so
|
|
117
|
+
* the caller must delete them before re-dispatching.
|
|
118
|
+
*/
|
|
119
|
+
export async function shardDiff(
|
|
120
|
+
runDir: string,
|
|
121
|
+
opts: { budget?: number; maxFiles?: number } = {},
|
|
122
|
+
): Promise<ShardManifest> {
|
|
123
|
+
const budget = opts.budget ?? DEFAULT_SHARD_BUDGET
|
|
124
|
+
const maxFiles = opts.maxFiles ?? DEFAULT_SHARD_MAX_FILES
|
|
125
|
+
const diff = await readDiffPatch(runDir)
|
|
126
|
+
const chunks = splitFileChunks(diff)
|
|
127
|
+
const totalLines = chunks.reduce((n, c) => n + c.lines, 0)
|
|
128
|
+
const shardsDir = join(runDir, 'shards')
|
|
129
|
+
await clearStaleShardPatches(shardsDir)
|
|
130
|
+
await mkdir(shardsDir, { recursive: true })
|
|
131
|
+
|
|
132
|
+
const planned = planShards(chunks, budget, maxFiles)
|
|
133
|
+
let shards: ShardEntry[]
|
|
134
|
+
if (planned.length === 0) {
|
|
135
|
+
shards = []
|
|
136
|
+
} else if (planned.length === 1) {
|
|
137
|
+
// Single-shard passthrough: stage 4 takes the same path it took on 0.9.0.
|
|
138
|
+
const only = planned[0] ?? []
|
|
139
|
+
shards = [
|
|
140
|
+
{
|
|
141
|
+
id: 1,
|
|
142
|
+
path: 'diff.patch',
|
|
143
|
+
files: only.map((c) => c.path),
|
|
144
|
+
lines: only.reduce((n, c) => n + c.lines, 0),
|
|
145
|
+
},
|
|
146
|
+
]
|
|
147
|
+
} else {
|
|
148
|
+
shards = []
|
|
149
|
+
for (const [i, members] of planned.entries()) {
|
|
150
|
+
const id = i + 1
|
|
151
|
+
const rel = `shards/shard-${id}.patch`
|
|
152
|
+
await writeFile(join(runDir, rel), members.map((c) => c.text).join(''))
|
|
153
|
+
shards.push({
|
|
154
|
+
id,
|
|
155
|
+
path: rel,
|
|
156
|
+
files: members.map((c) => c.path),
|
|
157
|
+
lines: members.reduce((n, c) => n + c.lines, 0),
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const manifest: ShardManifest = {
|
|
163
|
+
budget,
|
|
164
|
+
maxFiles,
|
|
165
|
+
totalFiles: chunks.length,
|
|
166
|
+
totalLines,
|
|
167
|
+
shards,
|
|
168
|
+
}
|
|
169
|
+
await writeFile(join(shardsDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`)
|
|
170
|
+
return manifest
|
|
171
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/** The resume ladder. Exported so `skill-lint` can assert that the diagnostic
|
|
5
|
+
* stage names SKILL.md logs (`filter`, `tests-check`, `shard-coverage`, ...) stay
|
|
6
|
+
* out of it: a diagnostic entry that lands on this list would move `next`. */
|
|
7
|
+
export const ORDER = [
|
|
5
8
|
'setup',
|
|
6
9
|
'context',
|
|
7
10
|
'specialists',
|
|
@@ -15,9 +18,9 @@ const ORDER = [
|
|
|
15
18
|
export type StatusResult = {
|
|
16
19
|
/**
|
|
17
20
|
* Highest stage the log says is behind us. A stage logged `skipped` counts:
|
|
18
|
-
* `context`
|
|
19
|
-
* treating it as unfinished would send a resume
|
|
20
|
-
*
|
|
21
|
+
* `context` logs `skipped` when the scout produced no brief, and the pipeline
|
|
22
|
+
* has nothing to go back for, so treating it as unfinished would send a resume
|
|
23
|
+
* to a stage with no remaining work.
|
|
21
24
|
*/
|
|
22
25
|
lastCompleted: (typeof ORDER)[number] | null
|
|
23
26
|
next: (typeof ORDER)[number] | 'cleanup'
|
|
@@ -332,3 +332,53 @@ export type PrFileEntry = {
|
|
|
332
332
|
export function isSuggestion(f: ReviewFinding): boolean {
|
|
333
333
|
return f.risk.action === 'consider' || f.risk.action === 'optional'
|
|
334
334
|
}
|
|
335
|
+
|
|
336
|
+
export type BriefSubsystem = {
|
|
337
|
+
name: string
|
|
338
|
+
role: string
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Scout-produced PR summary. Written to `$RUN_DIR/brief.json` by the context stage. */
|
|
342
|
+
export type PrBrief = {
|
|
343
|
+
purpose: string
|
|
344
|
+
changes: string[]
|
|
345
|
+
subsystems: BriefSubsystem[]
|
|
346
|
+
watchItems: string[]
|
|
347
|
+
unclear: string[]
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function briefStrings(raw: unknown): string[] {
|
|
351
|
+
if (!Array.isArray(raw)) return []
|
|
352
|
+
return raw
|
|
353
|
+
.filter((v): v is string => typeof v === 'string')
|
|
354
|
+
.map((v) => v.trim())
|
|
355
|
+
.filter((v) => v.length > 0)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Lenient by design, unlike `parseFinding`. A subagent-authored brief that came
|
|
360
|
+
* back malformed must degrade the report header to absent, not throw away an
|
|
361
|
+
* otherwise-complete review at render time.
|
|
362
|
+
*/
|
|
363
|
+
export function parseBrief(raw: unknown): PrBrief | null {
|
|
364
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null
|
|
365
|
+
const r = raw as Record<string, unknown>
|
|
366
|
+
const purpose = typeof r.purpose === 'string' ? r.purpose.trim() : ''
|
|
367
|
+
if (purpose.length === 0) return null
|
|
368
|
+
const subsystems: BriefSubsystem[] = Array.isArray(r.subsystems)
|
|
369
|
+
? (r.subsystems as unknown[]).flatMap((entry) => {
|
|
370
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return []
|
|
371
|
+
const e = entry as Record<string, unknown>
|
|
372
|
+
const name = typeof e.name === 'string' ? e.name.trim() : ''
|
|
373
|
+
if (name.length === 0) return []
|
|
374
|
+
return [{ name, role: typeof e.role === 'string' ? e.role.trim() : '' }]
|
|
375
|
+
})
|
|
376
|
+
: []
|
|
377
|
+
return {
|
|
378
|
+
purpose,
|
|
379
|
+
changes: briefStrings(r.changes),
|
|
380
|
+
subsystems,
|
|
381
|
+
watchItems: briefStrings(r.watchItems),
|
|
382
|
+
unclear: briefStrings(r.unclear),
|
|
383
|
+
}
|
|
384
|
+
}
|
package/skills/magpie/skill.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magpie",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Interactive PR review pipeline. Runs five parallel specialist subagents (security, bugs, performance, code-smells, architecture), dedupes findings, applies a critic rubric, peer-reviews via codex exec (falling back to a Claude second opinion when codex is unavailable), and serves an interactive HTML report for selecting findings to post via gh. Bundles a Bun CLI installed onto PATH via the skill's postinstall step. Use when the user asks to review a GitHub pull request.",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Interactive PR review pipeline. Runs five parallel specialist subagents (security, bugs, performance, code-smells, architecture), dedupes findings, applies a critic rubric, peer-reviews via codex exec (falling back to a Claude second opinion when codex is unavailable), and serves an interactive HTML report for selecting findings to post via gh. Bundles a Bun CLI installed onto PATH via the skill's postinstall step. Use when the user asks to review a GitHub pull request. Splits oversized diffs into budgeted shards and rebuilds the diff from the local clone when gh pr diff refuses it.",
|
|
5
5
|
"author": "iceinvein",
|
|
6
6
|
"type": "prompt",
|
|
7
7
|
"tools": [
|
|
@@ -183,6 +183,11 @@ button {
|
|
|
183
183
|
color: var(--ink);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
.diff-source {
|
|
187
|
+
font-size: 0.78rem;
|
|
188
|
+
opacity: 0.7;
|
|
189
|
+
}
|
|
190
|
+
|
|
186
191
|
.header-spacer {
|
|
187
192
|
flex: 1;
|
|
188
193
|
}
|
|
@@ -1638,3 +1643,64 @@ code.inline-code {
|
|
|
1638
1643
|
border-color: var(--line);
|
|
1639
1644
|
}
|
|
1640
1645
|
}
|
|
1646
|
+
|
|
1647
|
+
/* ---- PR brief header ---- */
|
|
1648
|
+
.pr-brief {
|
|
1649
|
+
margin: 0;
|
|
1650
|
+
padding: 10px 16px 12px;
|
|
1651
|
+
background: var(--card);
|
|
1652
|
+
border-bottom: 1px solid var(--line);
|
|
1653
|
+
color: var(--ink);
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
.brief-summary {
|
|
1657
|
+
cursor: pointer;
|
|
1658
|
+
font-size: 12px;
|
|
1659
|
+
font-weight: 600;
|
|
1660
|
+
letter-spacing: 0.02em;
|
|
1661
|
+
text-transform: uppercase;
|
|
1662
|
+
color: var(--ink-muted);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
.brief-body {
|
|
1666
|
+
padding-top: 8px;
|
|
1667
|
+
max-width: 92ch;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
.brief-purpose {
|
|
1671
|
+
margin: 0;
|
|
1672
|
+
font-size: 14px;
|
|
1673
|
+
line-height: 1.5;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
.brief-changes {
|
|
1677
|
+
margin: 8px 0 0;
|
|
1678
|
+
padding-left: 18px;
|
|
1679
|
+
font-size: 13px;
|
|
1680
|
+
line-height: 1.5;
|
|
1681
|
+
color: var(--ink-muted);
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
.brief-subsystems,
|
|
1685
|
+
.brief-issues {
|
|
1686
|
+
display: flex;
|
|
1687
|
+
flex-wrap: wrap;
|
|
1688
|
+
gap: 6px;
|
|
1689
|
+
margin-top: 10px;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
.brief-chip,
|
|
1693
|
+
.brief-issue {
|
|
1694
|
+
padding: 2px 8px;
|
|
1695
|
+
border: 1px solid var(--line);
|
|
1696
|
+
border-radius: var(--radius-sm);
|
|
1697
|
+
font-family: var(--font-mono);
|
|
1698
|
+
font-size: 11px;
|
|
1699
|
+
color: var(--ink-muted);
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
.brief-issue {
|
|
1703
|
+
border-color: var(--accent);
|
|
1704
|
+
color: var(--accent);
|
|
1705
|
+
text-decoration: none;
|
|
1706
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# migrate
|
|
2
|
+
|
|
3
|
+
Source-agnostic legacy migration mapping. `migrate` walks a legacy codebase
|
|
4
|
+
through probe, enumerate, seam, extract, parity, and queue, building an
|
|
5
|
+
auditable requirements ledger with mandatory citations instead of a
|
|
6
|
+
self-reported one. It enumerates the legacy surface from two independent
|
|
7
|
+
directions per lens, derives a capability seam empirically rather than by
|
|
8
|
+
guesswork, extracts cited functional requirements, plans parity against the
|
|
9
|
+
source, and routes every ambiguity to a batch decision queue for a human to
|
|
10
|
+
adjudicate. The coverage arithmetic, citation resolution, and phase ordering
|
|
11
|
+
are enforced by a bundled Bun CLI instead of being self-reported. Two more
|
|
12
|
+
phases, adjudicate and handoff, complete the walkthrough but ship no CLI verb
|
|
13
|
+
yet; see the phases table below.
|
|
14
|
+
|
|
15
|
+
## Using it
|
|
16
|
+
|
|
17
|
+
Invoke `/migrate` in a target repo that is a git working copy, pointed at a
|
|
18
|
+
read-only checkout of the legacy source. The skill (`SKILL.md`) is the
|
|
19
|
+
walkthrough: it names, phase by phase, what to read, what to dispatch, and
|
|
20
|
+
what `migrate` command closes that phase out. Each phase's manual under
|
|
21
|
+
`references/phases/` carries the actual judgment calls, loaded only when
|
|
22
|
+
that phase is current so a run never pays for prose it does not need yet.
|
|
23
|
+
|
|
24
|
+
### The phases
|
|
25
|
+
|
|
26
|
+
| # | Phase | Manual | What it produces |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| 0 | Probe | `references/phases/probe.md` | `.migrate/config.toml` (detected source stack, basis, target profile) and `.migrate/parity-basis.md` (the detection evidence, hand-written) |
|
|
29
|
+
| 1 | Enumerate | `references/phases/enumerate.md` | `elements.jsonl` and a lens census record per surface |
|
|
30
|
+
| 2 | Seam | `references/phases/seam.md` | `capabilities.jsonl`, `seam.json`, `seam.md`: the capability partition and its evidence |
|
|
31
|
+
| 3 | Extract | `references/phases/extract.md` | `requirements.jsonl`, attribute/rule-sweep/closer census records, terminal element dispositions |
|
|
32
|
+
| 4 | Parity | `references/phases/parity.md` | `deltas.jsonl` and a parity plan on every non-queued requirement |
|
|
33
|
+
| 5 | Queue | `references/phases/queue.md` | Queue items carrying evidence, options and a recommendation for anything ambiguous |
|
|
34
|
+
| 6 | Adjudicate | none yet | No verb ships in this version; `migrate status` and `migrate queue list` are the terminus |
|
|
35
|
+
| 7 | Handoff | none yet | Same as adjudicate: no verb yet |
|
|
36
|
+
|
|
37
|
+
A run in this version stops at the queue. `adjudicate` and `handoff` have no
|
|
38
|
+
CLI verbs to complete them, so `migrate check --phase queue` is the
|
|
39
|
+
practical terminus: its exit 0 is what "done, for now" means. Plain `migrate
|
|
40
|
+
check` gates every phase through `handoff` and cannot pass yet for the same
|
|
41
|
+
reason. `references/run-ops.md` covers what applies across every phase
|
|
42
|
+
rather than any one of them: subagent dispatch, the batch-checkpoint
|
|
43
|
+
discipline, and what happens when two agents contend for the store lock.
|
|
44
|
+
|
|
45
|
+
### Recipes
|
|
46
|
+
|
|
47
|
+
Enumerate reads the source stack `probe` detected and looks for a matching
|
|
48
|
+
file in `references/recipes/`, one file per stack family
|
|
49
|
+
(`references/recipes/aspnet.md` covers `aspnet-webforms`, `aspnet-mvc`, and
|
|
50
|
+
`aspnet-webapi`). A recipe answers one narrow question: for each declared
|
|
51
|
+
surface type, at least two independent directions for enumerating it and the
|
|
52
|
+
probe command that realises each one. Nothing else; the lens contract itself
|
|
53
|
+
lives once in `enumerate.md`, and a recipe does not restate it, carry
|
|
54
|
+
classification rules, or gate anything.
|
|
55
|
+
|
|
56
|
+
If no file matches the detected stack, that is contract-only mode: a
|
|
57
|
+
supported path, not a degraded one. The enumerating agent derives its own two
|
|
58
|
+
directions per surface, and the census gates them exactly as it would a
|
|
59
|
+
recipe's.
|
|
60
|
+
|
|
61
|
+
**Adding a stack is one new file in `references/recipes/` and no edit
|
|
62
|
+
anywhere else.** `SKILL.md`, the phase manuals, and the CLI never name an
|
|
63
|
+
individual stack; they only read `[source].stack` and look in that
|
|
64
|
+
directory. See `references/recipes/README.md` for the exact file shape.
|
|
65
|
+
|
|
66
|
+
## Checking as you go
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
migrate check --phase <current-phase>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
bounds the run-state gate at that phase; the other nine gates always read
|
|
73
|
+
the whole store, so a coverage or census gap past your current phase still
|
|
74
|
+
fails on its own gate regardless of `--phase`. Citations are checked by
|
|
75
|
+
default; pass `--no-citations` to skip that gate.
|
|
76
|
+
|
|
77
|
+
## Fixtures
|
|
78
|
+
|
|
79
|
+
Two fixtures, each with a committed `GROUND-TRUTH.md`, drive the skill end to
|
|
80
|
+
end:
|
|
81
|
+
|
|
82
|
+
- **`fixtures/tiny-express/`**: a small Express/Node app, twelve elements
|
|
83
|
+
across all eight default surfaces. Its stack (`express`) matches no file
|
|
84
|
+
in `references/recipes/`, so it proves the contract-only path: enumerate
|
|
85
|
+
deriving its own two directions per surface with no recipe to lean on, and
|
|
86
|
+
the census gating them exactly the same as it would a recipe's.
|
|
87
|
+
- **`fixtures/tiny-webforms/`**: a small ASP.NET Web Forms app, sixteen
|
|
88
|
+
elements across the same eight surfaces. Its stack (`aspnet-webforms`)
|
|
89
|
+
matches `references/recipes/aspnet.md`, so it is that recipe's first run
|
|
90
|
+
against committed code, not just the throwaway trees it was written
|
|
91
|
+
against.
|
|
92
|
+
|
|
93
|
+
`scripts/__tests__/e2e-express.test.ts` and
|
|
94
|
+
`scripts/__tests__/e2e-webforms.test.ts` copy the respective fixture to a
|
|
95
|
+
temp directory and drive the real CLI as a subprocess, probe through queue,
|
|
96
|
+
through `init`, `import`, `census`, `phase`, `queue add`, `queue list`, and
|
|
97
|
+
`check`, reconciling every row against the fixture's ground truth. Both end at
|
|
98
|
+
`migrate check --phase queue` on exit 0, and then show plain `migrate check`
|
|
99
|
+
failing on exactly `adjudicate` and `handoff`, the two phases with no verb in
|
|
100
|
+
this version.
|
|
101
|
+
|
|
102
|
+
Both show gates in both directions. Failing before they pass: the mid-run check
|
|
103
|
+
after enumerate names the three closer records extract has not written yet, and
|
|
104
|
+
the `deltas` gate names the sanctioned difference each run files unsigned
|
|
105
|
+
before an owner signs it. Failing after they pass: each run closes green, then
|
|
106
|
+
mutates the store (nulling every parity plan, then removing an element row) and
|
|
107
|
+
asserts the gate that should catch it does.
|
|
108
|
+
|
|
109
|
+
## Documentation
|
|
110
|
+
|
|
111
|
+
- **[docs/reference.md](docs/reference.md)** is what you need to drive the CLI:
|
|
112
|
+
the batch-file and census formats with worked examples, the row schemas and
|
|
113
|
+
their grammars, what each of the ten gates enforces, and the exit-code
|
|
114
|
+
convention. Ships with the installed skill.
|
|
115
|
+
- **[docs/architecture.md](docs/architecture.md)** is for working on the skill
|
|
116
|
+
itself: the module map, the rule that decides what belongs in the CLI rather
|
|
117
|
+
than the prompt, how to add a gate or a surface type, the testing
|
|
118
|
+
conventions, and the known limits.
|
|
119
|
+
|
|
120
|
+
## Store layout
|
|
121
|
+
|
|
122
|
+
The store lives at `.migrate/` in the target repo and is committed.
|
|
123
|
+
|
|
124
|
+
| Path | Shape | Holds |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| `.migrate/config.toml` | declarative | source pointer and scope, detected source stack, target profile, surface-type set, closer set, handoff adapter |
|
|
127
|
+
| `.migrate/elements.jsonl` | rows | surface ledger |
|
|
128
|
+
| `.migrate/requirements.jsonl` | rows | functional requirements and their dispositions |
|
|
129
|
+
| `.migrate/capabilities.jsonl` | rows | the seam partition |
|
|
130
|
+
| `.migrate/seam.json` | object | run-level seam metadata: validators run, modularity, status |
|
|
131
|
+
| `.migrate/deltas.jsonl` | rows | sanctioned delta catalog |
|
|
132
|
+
| `.migrate/census.jsonl` | rows | one accounting record per lens run |
|
|
133
|
+
| `.migrate/phases.json` | object | per-phase status, batches, resume pointers |
|
|
134
|
+
| `.migrate/seam.md` | prose | validator scripts and their raw output |
|
|
135
|
+
| `.migrate/parity-basis.md` | prose | runnable-versus-source-only detection evidence |
|
|
136
|
+
| `.migrate/queue/q-<slug>.md` | prose | evidence, options, recommendation |
|
|
137
|
+
| `.migrate/.env` | secrets | runtime-lens credentials, gitignored |
|
|
138
|
+
| `docs/migrate/*.md` | generated | human-readable views, written by `migrate report` |
|
|
139
|
+
|
|
140
|
+
## CLI
|
|
141
|
+
|
|
142
|
+
| Command | Does |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `migrate init --source <path> --scope <text> --name <target>` | Writes `.migrate/config.toml` |
|
|
145
|
+
| `migrate import <elements\|reqs\|deltas> <batch.json>` | Validated bulk append to the store |
|
|
146
|
+
| `migrate census <record.json>` | Records a lens accounting record |
|
|
147
|
+
| `migrate phase [<name>] [--status <s>]` | Prints phase state, or sets one phase's status to any value you name |
|
|
148
|
+
| `migrate queue add <file.md>` | Adds a queue item |
|
|
149
|
+
| `migrate queue list [--open]` | Lists queue items, severity first |
|
|
150
|
+
| `migrate queue show <id>` | Prints one queue item |
|
|
151
|
+
| `migrate check [--phase <p>] [--no-citations] [--leaks]` | Runs the gates |
|
|
152
|
+
| `migrate status` | Phase state, counts, resume pointer |
|
|
153
|
+
| `migrate reset --phase <phase>` | Clears one phase's derived rows and returns it to `pending` |
|
|
154
|
+
| `migrate report [--out <dir>]` | Renders markdown views |
|
|
155
|
+
|
|
156
|
+
Run `migrate --help` for the same list from the CLI itself.
|
|
157
|
+
|
|
158
|
+
**Four commands write a phase's status, each to a different extent.** `phase
|
|
159
|
+
--status <s>` is the only one that writes any value you ask for, and the only
|
|
160
|
+
one whose whole purpose is that write. `reset --phase <p>` also writes it
|
|
161
|
+
directly, but only ever to `pending`, and it empties that phase's `batches`
|
|
162
|
+
list at the same time. `import` and `census` touch `phases.json` incidentally,
|
|
163
|
+
each moving a phase to `running` (unless it is already `done`) when they record
|
|
164
|
+
a batch. Nothing else writes it at all.
|
|
165
|
+
|
|
166
|
+
A lock failure on `import`, `census`, `phase --status`, or `reset` exits `3`;
|
|
167
|
+
pass `--force-unlock` once you have confirmed no other agent is actually
|
|
168
|
+
writing.
|
|
169
|
+
|
|
170
|
+
## Install
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
bunx @iceinvein/agent-skills install migrate -g
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
This skill ships in two parts: the prompt (`SKILL.md`) and a companion Bun
|
|
177
|
+
CLI (`bin` + `scripts`). The agent-skills installer writes both into
|
|
178
|
+
`~/.claude/skills/migrate/` and then runs the bundled `install.sh` as a
|
|
179
|
+
postinstall step, which symlinks `bin/migrate` onto your PATH (preferring
|
|
180
|
+
`/usr/local/bin`, falling back to `~/.local/bin`). Removing the skill with
|
|
181
|
+
`agent-skills remove migrate -g` runs `uninstall.sh` first to undo the PATH
|
|
182
|
+
symlink.
|
|
183
|
+
|
|
184
|
+
If you cloned this repo and want to run from source, you can also invoke
|
|
185
|
+
`./install.sh` directly: it does the PATH-link step against the local source
|
|
186
|
+
tree.
|
|
187
|
+
|
|
188
|
+
## Development
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
bun test # Run all tests
|
|
192
|
+
bun run lint # Biome check
|
|
193
|
+
bun run typecheck # tsc --noEmit
|
|
194
|
+
```
|