@iceinvein/agent-skills 0.1.40 → 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.
Files changed (139) hide show
  1. package/README.md +18 -2
  2. package/dist/cli/index.js +105 -28
  3. package/package.json +1 -1
  4. package/skills/index.json +14 -2
  5. package/skills/magpie/SKILL.md +118 -40
  6. package/skills/magpie/bin/magpie.ts +43 -0
  7. package/skills/magpie/fixtures/fake-gh-nodiff.sh +38 -0
  8. package/skills/magpie/package.json +1 -1
  9. package/skills/magpie/references/peer-review.md +7 -2
  10. package/skills/magpie/references/specialists.md +38 -7
  11. package/skills/magpie/scripts/__tests__/cli.test.ts +101 -1
  12. package/skills/magpie/scripts/__tests__/dedupe-cmd.test.ts +187 -0
  13. package/skills/magpie/scripts/__tests__/diff-chunks.test.ts +51 -0
  14. package/skills/magpie/scripts/__tests__/filter-diff-preservation.test.ts +54 -0
  15. package/skills/magpie/scripts/__tests__/findings-files.test.ts +35 -0
  16. package/skills/magpie/scripts/__tests__/gh.test.ts +69 -0
  17. package/skills/magpie/scripts/__tests__/git-diff.test.ts +83 -0
  18. package/skills/magpie/scripts/__tests__/helpers/git-fixture.ts +47 -0
  19. package/skills/magpie/scripts/__tests__/path-filter.test.ts +27 -0
  20. package/skills/magpie/scripts/__tests__/render-cmd.test.ts +95 -0
  21. package/skills/magpie/scripts/__tests__/render-findings.test.ts +33 -0
  22. package/skills/magpie/scripts/__tests__/render-progress.test.ts +42 -0
  23. package/skills/magpie/scripts/__tests__/setup-cmd.test.ts +83 -1
  24. package/skills/magpie/scripts/__tests__/shard.test.ts +165 -0
  25. package/skills/magpie/scripts/__tests__/skill-lint.test.ts +96 -1
  26. package/skills/magpie/scripts/dedupe-cmd.ts +58 -3
  27. package/skills/magpie/scripts/diff-chunks.ts +28 -0
  28. package/skills/magpie/scripts/findings-files.ts +32 -0
  29. package/skills/magpie/scripts/gh.ts +64 -13
  30. package/skills/magpie/scripts/git-diff.ts +111 -0
  31. package/skills/magpie/scripts/path-filter.ts +9 -5
  32. package/skills/magpie/scripts/refresh.ts +8 -0
  33. package/skills/magpie/scripts/render-cmd.ts +28 -9
  34. package/skills/magpie/scripts/render-findings.ts +11 -1
  35. package/skills/magpie/scripts/render-progress.ts +6 -1
  36. package/skills/magpie/scripts/setup-cmd.ts +38 -1
  37. package/skills/magpie/scripts/shard.ts +171 -0
  38. package/skills/magpie/scripts/status-cmd.ts +4 -1
  39. package/skills/magpie/skill.json +2 -2
  40. package/skills/magpie/templates/styles.css +5 -0
  41. package/skills/migrate/README.md +194 -0
  42. package/skills/migrate/SKILL.md +197 -0
  43. package/skills/migrate/bin/migrate +15 -0
  44. package/skills/migrate/bin/migrate.ts +309 -0
  45. package/skills/migrate/biome.json +35 -0
  46. package/skills/migrate/bun.lock +24 -0
  47. package/skills/migrate/docs/architecture.md +294 -0
  48. package/skills/migrate/docs/reference.md +590 -0
  49. package/skills/migrate/fixtures/tiny-express/GROUND-TRUTH.md +39 -0
  50. package/skills/migrate/fixtures/tiny-express/app.js +29 -0
  51. package/skills/migrate/fixtures/tiny-express/cron.js +6 -0
  52. package/skills/migrate/fixtures/tiny-express/reports/daily-users.json +6 -0
  53. package/skills/migrate/fixtures/tiny-express/schema.sql +12 -0
  54. package/skills/migrate/fixtures/tiny-express/settings.json +4 -0
  55. package/skills/migrate/fixtures/tiny-express/views/users.html +9 -0
  56. package/skills/migrate/fixtures/tiny-webforms/Controllers/UsersController.cs +68 -0
  57. package/skills/migrate/fixtures/tiny-webforms/Default.aspx +7 -0
  58. package/skills/migrate/fixtures/tiny-webforms/Default.aspx.cs +14 -0
  59. package/skills/migrate/fixtures/tiny-webforms/GROUND-TRUTH.md +50 -0
  60. package/skills/migrate/fixtures/tiny-webforms/Integrations/BillingClient.cs +16 -0
  61. package/skills/migrate/fixtures/tiny-webforms/Jobs/NightlyDigestJob.cs +33 -0
  62. package/skills/migrate/fixtures/tiny-webforms/Reports/DailyUsers.rdl +11 -0
  63. package/skills/migrate/fixtures/tiny-webforms/Schema.sql +12 -0
  64. package/skills/migrate/fixtures/tiny-webforms/Site.master +16 -0
  65. package/skills/migrate/fixtures/tiny-webforms/Users.aspx +8 -0
  66. package/skills/migrate/fixtures/tiny-webforms/Users.aspx.cs +14 -0
  67. package/skills/migrate/fixtures/tiny-webforms/web.config +10 -0
  68. package/skills/migrate/install.sh +68 -0
  69. package/skills/migrate/package.json +17 -0
  70. package/skills/migrate/references/phases/enumerate.md +291 -0
  71. package/skills/migrate/references/phases/extract.md +652 -0
  72. package/skills/migrate/references/phases/parity.md +275 -0
  73. package/skills/migrate/references/phases/probe.md +135 -0
  74. package/skills/migrate/references/phases/queue.md +242 -0
  75. package/skills/migrate/references/phases/seam.md +416 -0
  76. package/skills/migrate/references/recipes/README.md +116 -0
  77. package/skills/migrate/references/recipes/aspnet.md +287 -0
  78. package/skills/migrate/references/run-ops.md +280 -0
  79. package/skills/migrate/scripts/__tests__/census.test.ts +775 -0
  80. package/skills/migrate/scripts/__tests__/check.test.ts +458 -0
  81. package/skills/migrate/scripts/__tests__/citations.test.ts +156 -0
  82. package/skills/migrate/scripts/__tests__/cli.test.ts +183 -0
  83. package/skills/migrate/scripts/__tests__/concurrency.test.ts +164 -0
  84. package/skills/migrate/scripts/__tests__/config.test.ts +112 -0
  85. package/skills/migrate/scripts/__tests__/e2e-express.test.ts +1093 -0
  86. package/skills/migrate/scripts/__tests__/e2e-webforms.test.ts +1276 -0
  87. package/skills/migrate/scripts/__tests__/e2e.test.ts +320 -0
  88. package/skills/migrate/scripts/__tests__/ids.test.ts +38 -0
  89. package/skills/migrate/scripts/__tests__/import.test.ts +155 -0
  90. package/skills/migrate/scripts/__tests__/init.test.ts +192 -0
  91. package/skills/migrate/scripts/__tests__/leaks.test.ts +176 -0
  92. package/skills/migrate/scripts/__tests__/lock.test.ts +183 -0
  93. package/skills/migrate/scripts/__tests__/paths.test.ts +129 -0
  94. package/skills/migrate/scripts/__tests__/phase-cmd.test.ts +151 -0
  95. package/skills/migrate/scripts/__tests__/phases.test.ts +70 -0
  96. package/skills/migrate/scripts/__tests__/queue.test.ts +475 -0
  97. package/skills/migrate/scripts/__tests__/report.test.ts +150 -0
  98. package/skills/migrate/scripts/__tests__/run-state.test.ts +136 -0
  99. package/skills/migrate/scripts/__tests__/status-reset.test.ts +318 -0
  100. package/skills/migrate/scripts/__tests__/store.test.ts +132 -0
  101. package/skills/migrate/scripts/__tests__/validate.test.ts +54 -0
  102. package/skills/migrate/scripts/census-cmd.ts +109 -0
  103. package/skills/migrate/scripts/census.ts +342 -0
  104. package/skills/migrate/scripts/check-cmd.ts +24 -0
  105. package/skills/migrate/scripts/check.ts +376 -0
  106. package/skills/migrate/scripts/citations.ts +92 -0
  107. package/skills/migrate/scripts/config.ts +237 -0
  108. package/skills/migrate/scripts/ids.ts +31 -0
  109. package/skills/migrate/scripts/import-cmd.ts +141 -0
  110. package/skills/migrate/scripts/init-cmd.ts +118 -0
  111. package/skills/migrate/scripts/leaks.ts +184 -0
  112. package/skills/migrate/scripts/lock.ts +188 -0
  113. package/skills/migrate/scripts/paths.ts +103 -0
  114. package/skills/migrate/scripts/phase-cmd.ts +63 -0
  115. package/skills/migrate/scripts/phases.ts +113 -0
  116. package/skills/migrate/scripts/queue-cmd.ts +98 -0
  117. package/skills/migrate/scripts/queue.ts +258 -0
  118. package/skills/migrate/scripts/report-cmd.ts +47 -0
  119. package/skills/migrate/scripts/report.ts +131 -0
  120. package/skills/migrate/scripts/reset-cmd.ts +120 -0
  121. package/skills/migrate/scripts/status-cmd.ts +52 -0
  122. package/skills/migrate/scripts/store.ts +159 -0
  123. package/skills/migrate/scripts/types.ts +137 -0
  124. package/skills/migrate/scripts/validate.ts +221 -0
  125. package/skills/migrate/skill.json +33 -0
  126. package/skills/migrate/templates/config.toml +27 -0
  127. package/skills/migrate/templates/queue-item.md +17 -0
  128. package/skills/migrate/tsconfig.json +18 -0
  129. package/skills/migrate/uninstall.sh +31 -0
  130. package/skills/sluice/SKILL.md +82 -0
  131. package/skills/sluice/references/deep-channel.md +94 -0
  132. package/skills/sluice/references/finish.md +35 -0
  133. package/skills/sluice/references/intent.md +29 -0
  134. package/skills/sluice/references/review.md +42 -0
  135. package/skills/sluice/references/root-cause.md +38 -0
  136. package/skills/sluice/references/show-or-say.md +36 -0
  137. package/skills/sluice/references/test-first.md +35 -0
  138. package/skills/sluice/references/verify.md +26 -0
  139. package/skills/sluice/skill.json +32 -0
@@ -0,0 +1,275 @@
1
+ # Phase 4: Parity
2
+
3
+ ## Purpose
4
+
5
+ Assign an oracle to every requirement whose confidence is not `queued`, and
6
+ maintain the sanctioned-difference catalog for whatever legitimately cannot
7
+ match. Exit condition: no unsigned delta in `deltas.jsonl`, every non-queued
8
+ requirement carries a parity value, and `migrate phase parity --status
9
+ done` has run.
10
+
11
+ ## Inputs
12
+
13
+ - `config.toml`: `target.parity_test_path`, the template every parity `ref`
14
+ must be built from by hand. Its shipped default is
15
+ `tests/parity/{capability}/{fr_slug}.test.ts`; probe.md is where an
16
+ operator would have hand-edited it to something else, so read it, never
17
+ assume the default.
18
+ - `.migrate/parity-basis.md`: hand-written prose from probe, carrying
19
+ whether the source is `runnable` or `source-only` and the detection
20
+ evidence behind that call. This phase does not redetect it; it reads what
21
+ probe already decided.
22
+ - The store: `requirements.jsonl` (every non-queued row needs a plan),
23
+ `deltas.jsonl` (existing sanctioned differences, checked before writing a
24
+ new rubric or a new delta rather than after).
25
+
26
+ ## Procedure
27
+
28
+ **The three parity kinds, stated once, before anything is assigned.**
29
+ `parity.kind` is `golden-master`, `differential`, or `rubric`.
30
+
31
+ - **`golden-master`.** Capture the legacy system's actual output for a
32
+ fixed input once, and assert the target reproduces it exactly. Fits a
33
+ deterministic, replayable behavior: the same request into the same state
34
+ gets the same response every time, so one captured snapshot is a
35
+ reusable oracle.
36
+ - **`differential`.** Run both systems side by side on the same input and
37
+ diff the two live results, rather than trusting one frozen capture. Fits
38
+ a behavior whose *exact* output legitimately varies (a token, a
39
+ timestamp, a generated id) while the comparison that actually matters
40
+ (did both systems accept, reject, and decide the same way) still
41
+ automates cleanly.
42
+ - **`rubric`, with a `level` of `high`, `moderate`, `low`, or `unknown`.**
43
+ For when no automatable oracle exists at all: a `source-only` basis with
44
+ nothing to run, or a behavior that crosses a boundary neither capture nor
45
+ live diffing can reach (an external mail send, a third-party callback).
46
+ **Only `level: high` needs no queue id; `moderate`, `low`, and `unknown`
47
+ each require one**, because a rubric below `high` is itself a claim that
48
+ something is not fully known, and that claim needs an owner's eyes, not a
49
+ guess standing in for one.
50
+
51
+ A worked example, run against a real store, assigning all three kinds
52
+ across the requirements extract.md mined:
53
+
54
+ ```json
55
+ { "id": "UM-001", "parity": { "kind": "differential", "ref": "tests/parity/user-management/login.test.ts" } }
56
+ { "id": "UM-002", "parity": { "kind": "golden-master", "ref": "tests/parity/user-management/list-users.test.ts" } }
57
+ { "id": "UM-003", "parity": { "kind": "rubric", "level": "moderate", "queue": "q-parity-um-003-reset-flow" } }
58
+ ```
59
+
60
+ (each shown here trimmed to its `id` and `parity` field; the real batch
61
+ carries every other required field for each row unchanged). UM-001 gets
62
+ `differential`: both systems get the same credentials, and while the issued
63
+ token differs, whether the attempt succeeds and what it rejects must match.
64
+ UM-002 gets `golden-master`: a `GET` with no side effects and no
65
+ input-dependent branching is exactly the deterministic case golden-master
66
+ fits. UM-003 gets `rubric:moderate`: its confidence was already `inferred`
67
+ in extract.md, since nothing in the source shows what happens when a reset
68
+ token is actually submitted, so no automated oracle has anything to run
69
+ against, and the `moderate` level needs the queue id it names.
70
+
71
+ `migrate import reqs batch.json` accepts this and prints `import reqs: 0
72
+ added, 3 updated, batch b-reqs-parity-001`: this is the write-back
73
+ `SKILL.md` calls out as load-bearing, in the same shape as extract's
74
+ disposition write-back. This import is the only writer of a *resolved*
75
+ `parity` value; the phase-status flip at the end of this phase does not
76
+ touch it, and neither does anything else short of `migrate reset --phase
77
+ parity`, which clears it back to `null` rather than resolving it.
78
+
79
+ **A sub-high rubric's queue id is checked by the refs gate, so file it in
80
+ the same pass, before any check that would otherwise name it dangling.**
81
+ UM-003's `moderate` level named `q-parity-um-003-reset-flow`; file it now:
82
+
83
+ ```markdown
84
+ ---
85
+ id: q-parity-um-003-reset-flow
86
+ severity: moderate
87
+ status: open
88
+ ---
89
+
90
+ ## Evidence
91
+
92
+ `UM-003` (password reset) has `confidence: inferred`: the source shows a
93
+ link is emailed and that account existence is not leaked, but nothing in
94
+ `AuthController.cs` shows what the token looks like, how long it lives, or
95
+ what happens when it is submitted. There is no fixture that can play back a
96
+ real reset end-to-end, so neither `golden-master` nor `differential` has
97
+ anything to run against.
98
+
99
+ ## Options
100
+
101
+ (a) Ship a `rubric:low` plan now and revisit once the token-verification
102
+ question resolves. (b) Block parity on this FR until that question
103
+ resolves. (c) Ship `rubric:moderate`: enough is observable (email is sent,
104
+ no account-existence leak) to check by hand, but not enough for an
105
+ executable oracle.
106
+
107
+ ## Recommendation
108
+
109
+ Recommend (c); `rubric:moderate` matches what is actually known today.
110
+ ```
111
+
112
+ `migrate queue add q-parity-um-003-reset-flow.md` accepts this and prints
113
+ `queue add: q-parity-um-003-reset-flow [moderate]`.
114
+
115
+ **Show the substitution, because nothing else will.** `{capability}` is the
116
+ capability's own `slug` from `capabilities.jsonl`, already known.
117
+ `{fr_slug}` has no deriving code anywhere in this CLI: it is a short,
118
+ kebab-case name you choose by hand for what the requirement actually is
119
+ (`login`, `list-users`), not the arbitrary FR id (`UM-001` tells a reader of
120
+ the test tree nothing). This is hand work the same way writing
121
+ `capabilities.jsonl` itself is hand work in seam.md: nothing imports a
122
+ parity plan's `ref` against the template, checks that it resolves to a real
123
+ file, or even checks that it looks like the template at all. Verified on a
124
+ disposable copy of the store, not the running example (overwriting UM-001's
125
+ real plan here just to prove this point would only recreate the exact kind
126
+ of drift this manual exists to prevent): a `golden-master` row with
127
+ `"ref": "this/path/does/not/exist/anywhere.test.ts"`, matching neither the
128
+ template nor any real file, imports cleanly and passes `migrate check`
129
+ without a single violation. The convention is entirely this manual's
130
+ discipline; get the substitution right by hand, because no gate is behind
131
+ you if you do not.
132
+
133
+ **Deltas exist to record sanctioned differences, never to silence a real
134
+ failure.** State this before writing one, not after: a delta is a *reason*
135
+ a difference is acceptable, backed by a rationale a reviewer can check, not
136
+ a lever for making an inconvenient test pass. If a parity test fails and
137
+ the honest cause is "the requirement was wrong" or "the target has a bug,"
138
+ the fix is to correct the requirement or the target, never to paper over
139
+ the failure with a delta whose rationale was written after the fact to fit.
140
+ A delta's `parity_exclusion` field says precisely what a parity test may
141
+ not assert on, not that the whole area is exempt from comparison.
142
+
143
+ A worked example, run against a real store. The target sends password-reset
144
+ emails through an async queue; the legacy system sent them synchronously in
145
+ the request, so response timing between the two systems now legitimately
146
+ differs for reasons that have nothing to do with correctness.
147
+
148
+ ```json
149
+ {
150
+ "id": "delta-async-email-delivery",
151
+ "scope": "Password reset email delivery timing (UM-003)",
152
+ "rationale": "The target sends reset emails through an async queue instead of synchronously in the request, so response timing legitimately differs from the legacy system.",
153
+ "parity_exclusion": "The UM-003 parity check must not assert on how soon the email was actually sent, only that a send was enqueued.",
154
+ "validation": "A separate async-delivery test in the greenfield-only suite confirms the queued job eventually sends the email; the parity suite does not re-prove it.",
155
+ "owner_signed": null
156
+ }
157
+ ```
158
+
159
+ `migrate import deltas batch.json` accepts this (`owner_signed: null` is a
160
+ valid value while a delta is proposed but not yet ratified) and prints
161
+ `import deltas: 1 added, 0 updated, batch b-deltas-001`. Unsigned, it fails
162
+ its own gate; `migrate check --phase parity`, run for real right now,
163
+ reports:
164
+
165
+ ```
166
+ deltas:
167
+ delta-async-email-delivery is not owner-signed
168
+ ```
169
+
170
+ Re-import the same id with `"owner_signed": "2026-08-07"` once an owner has
171
+ actually looked at it, and the gate clears; `deltas` never appears again in
172
+ the same store's `check` output.
173
+
174
+ ### The split-suite discipline
175
+
176
+ Three suites, kept apart on purpose:
177
+
178
+ - **parity.** Tests whose whole job is proving the target matches the
179
+ legacy system, one per requirement's `parity.ref`. This is the only suite
180
+ a delta's `parity_exclusion` ever narrows.
181
+ - **greenfield-only.** Tests for target-only behavior with no legacy
182
+ analog: the async email queue itself, from the delta above, is exactly
183
+ this. Nothing here compares against the legacy system, because there is
184
+ nothing on the legacy side to compare against.
185
+ - **legacy-only.** Behavior deliberately not carried forward. An
186
+ `out-of-scope` element (`route-get-legacy-admin-tool`, from extract.md)
187
+ gets no parity test at all; there is no requirement to assign one to, and
188
+ writing one would imply a comparison this run explicitly decided not to
189
+ make.
190
+
191
+ ### Parity coverage
192
+
193
+ **Every requirement whose confidence is not `queued` must carry a parity
194
+ value; a `queued` requirement is exempt.** The exemption exists because a
195
+ queued requirement's entire content, not just its oracle, is still
196
+ provisional: assigning it a parity plan before an owner has even confirmed
197
+ the requirement is real would be planning a test for something that might
198
+ not exist. Verified on a disposable copy of the store, using an extra
199
+ requirement never added to the running example: a requirement with
200
+ `confidence: {kind: queued, ...}` and `parity: null` produces no `parity`
201
+ gate violation; the same row with `confidence: confirmed` and
202
+ `parity: null` produces exactly one, naming the requirement's id. The
203
+ running example already shows this in the other direction, without needing
204
+ a separate copy: extract.md's own "What closes it" transcript names
205
+ `UM-001`, `UM-002`, and `UM-003` under `parity`, one line each, at the point
206
+ where all three are `confirmed` or `inferred` (never `queued`) and none yet
207
+ has a plan.
208
+
209
+ **The honest limit: a parity plan on record is a commitment, not a proof.**
210
+ `check`'s parity gate is satisfied once `parity` is a well-formed value; it
211
+ never runs `target.commands.test`, never opens the file the `ref` names,
212
+ and never confirms the test that file describes actually exists or passes.
213
+ Writing `{"kind": "golden-master", "ref": "..."}` and later writing the test
214
+ file at that path are two separate acts, and only the manual's own
215
+ discipline connects them.
216
+
217
+ ## What closes it
218
+
219
+ `migrate check --phase parity` mid-run reads the same way extract's did:
220
+ noisy on the surfaces this scratch run never enumerated, and quiet on
221
+ everything parity itself owns once every non-queued requirement has a plan,
222
+ `q-parity-um-003-reset-flow` is filed (above), and every delta is signed.
223
+ Skip filing that queue item and `refs` reappears here, naming `UM-003` via
224
+ `parity.queue`, the same way `q-legacy-admin-tool` reappears in extract.md's
225
+ own check if that one is skipped. Run for real, right after the delta above
226
+ was signed:
227
+
228
+ ```
229
+ 4/5 mapped, 1 out-of-scope, 0 unaccounted
230
+
231
+ Violations (7):
232
+ census:
233
+ declared surface jobs has no lens census record; the lens did not run or did not close
234
+ declared surface reports has no lens census record; the lens did not run or did not close
235
+ declared surface screens has no lens census record; the lens did not run or did not close
236
+ declared surface integrations has no lens census record; the lens did not run or did not close
237
+ declared surface workflows has no lens census record; the lens did not run or did not close
238
+ declared surface settings has no lens census record; the lens did not run or did not close
239
+ run-state:
240
+ phase parity is running; every phase through parity must be done
241
+ ```
242
+
243
+ Neither `deltas` nor `parity` appears: both are already clean at this
244
+ point. Flip the phase:
245
+
246
+ ```
247
+ migrate phase parity --status done
248
+ ```
249
+
250
+ ## Degradation
251
+
252
+ - **`source.basis` is `source-only`.** No live legacy system to run a
253
+ `differential` against, and often nothing to capture a fresh
254
+ `golden-master` from either, unless an existing fixture or recorded
255
+ output in the source already plays that role. When neither is possible,
256
+ `rubric` is what remains; expect more `moderate`, `low`, and `unknown`
257
+ levels, and more queue items, on a `source-only` run than on a `runnable`
258
+ one.
259
+ - **The target's test command is still `init`'s placeholder.** A parity
260
+ plan can still be recorded (the gate only checks the value's shape); the
261
+ test itself has nowhere real to run yet. This is exactly the "commitment,
262
+ not proof" limit above, sharpest right after probe when `target.commands`
263
+ has not been wired up.
264
+ - **Genuinely unclear which rubric level applies.** Use `unknown` rather
265
+ than guessing a specific level to avoid a queue id; `unknown` still needs
266
+ one, so nothing is gained by picking a falsely specific level instead.
267
+
268
+ ## Commands
269
+
270
+ ```
271
+ migrate import deltas <batch.json>
272
+ migrate import reqs <batch.json>
273
+ migrate queue add <item.md>
274
+ migrate phase parity --status done
275
+ ```
@@ -0,0 +1,135 @@
1
+ # Phase 0: Probe
2
+
3
+ ## Purpose
4
+
5
+ Detect the source stack, detect whether the source is runnable, interview for
6
+ the target profile, and confirm the surface set this run will enumerate.
7
+ Write `.migrate/config.toml` and `.migrate/parity-basis.md`. Every later phase
8
+ reads `config.toml`; deciding the runtime basis here means the enumerate
9
+ phase never has to re-check it, and no phase after this one probes the
10
+ source's runnability again.
11
+
12
+ Exit condition: `config.toml` exists, `parity-basis.md` carries the
13
+ detection evidence as prose, and `migrate phase probe --status done` has
14
+ run.
15
+
16
+ ## Inputs
17
+
18
+ There is no `config.toml` and no store yet; this phase writes the first one.
19
+ `migrate init` refuses at exit 1 if `.migrate/config.toml` already exists, so
20
+ re-running probe on a live store means editing the file directly, not
21
+ re-running `init`.
22
+
23
+ What you read instead:
24
+
25
+ - The source checkout itself (read-only): manifest and build files,
26
+ dependency lockfiles, a `.git` directory or its absence, README and any
27
+ docs tree.
28
+ - The target repo: it must already be a git working copy (the store commits
29
+ inside it), and whatever `.gitignore` it already has.
30
+ - The operator: the target profile is an interview, not something detectable
31
+ from the source.
32
+
33
+ ## Procedure
34
+
35
+ 1. **Detect the source stack.** Read the manifest and build files. If
36
+ nothing in the checkout names a stack you can commit to, record
37
+ `unknown` rather than guessing. `unknown` is a valid value, not a failure:
38
+ it is what routes the enumerate phase into contract-only mode instead of
39
+ into the wrong stack's recipe, which is worse than no recipe at all.
40
+
41
+ 2. **Detect whether the source is runnable.** Try to install dependencies,
42
+ build, and start it, in whatever order the stack suggests. Write every
43
+ probe command you ran and its actual output to `.migrate/parity-basis.md`
44
+ as prose, along with any dependency gap or environmental blocker you hit.
45
+ This is prose, not a census record, because it is an argument for the
46
+ basis you are about to declare, not a count anything can balance. Decide
47
+ `runnable` or `source-only` from that evidence and pass it to `--basis`.
48
+
49
+ 3. **Interview for the target profile.** Ask for: a name, the target stack,
50
+ the layout (which directories hold which part of the target), the
51
+ commands that test, lint, and build it, and `parity_test_path` (the path
52
+ template later phases will write parity tests under, for example
53
+ `tests/parity/{capability}/{fr_slug}.test.ts`).
54
+
55
+ 4. **Confirm or replace the default surface set.** The default is
56
+ `["routes", "tables", "jobs", "reports", "screens", "integrations",
57
+ "workflows", "settings"]`. This is written by `migrate init` and is not
58
+ yours to change through a flag: `init` takes no surface-set argument at
59
+ all. If the default fits the source, leave it. If it does not, this is
60
+ the single largest source-genericity lever in the whole tool, and it
61
+ costs one config key. A COBOL source, for example, declares:
62
+
63
+ ```toml
64
+ [surfaces]
65
+ types = ["programs", "copybooks", "jcl-jobs", "bms-maps", "datasets"]
66
+ ```
67
+
68
+ Hand-edit `[surfaces].types` in `config.toml` to replace it. Every
69
+ downstream gate reads the declared set, not the default, so this one edit
70
+ is what makes the rest of the run track a non-.NET source honestly
71
+ instead of forcing it through a shape that does not fit.
72
+
73
+ Element ids derive from the surface name with a trailing `s` stripped
74
+ (`tables` -> `table-...`). If a declared surface is already singular but
75
+ ends in `s` anyway (`status`, stripped naively to `statu-...`), add an
76
+ entry to `[surfaces.singular]` to override it, for example
77
+ `status = "status"`. `enumerate.md`'s Inputs section reads this table
78
+ when it derives ids; probe is the only phase that ever writes
79
+ `config.toml`, so an override missed here has no later phase to catch it
80
+ in.
81
+
82
+ `[target.layout]` and `[target.commands]` have the same property:
83
+ `init` writes them as an empty table and three placeholder `echo`
84
+ commands respectively, and nothing else fills them in. Hand-edit the
85
+ interview answers from step 3 into both before enumerate starts.
86
+ `target.parity_test_path` is different: `init` already writes a real
87
+ default, `tests/parity/{capability}/{fr_slug}.test.ts`, not a
88
+ placeholder, so it needs no edit when the operator's answer matches it.
89
+ When it does not (a different path convention, a different test file
90
+ extension), hand-edit `target.parity_test_path` in `[target]` the same
91
+ way, since nothing else will. Neither `SKILL.md` nor `docs/reference.md`
92
+ names this field, so this paragraph is its only documented home; the
93
+ parity phase is what reads it back.
94
+
95
+ ## What closes it
96
+
97
+ There is no census kind for probe; it is not a lens, an attribute, a
98
+ rule-sweep, or a closer, so nothing balances here. The phase closes on the
99
+ artifacts existing and the status flip:
100
+
101
+ ```
102
+ migrate init --source /abs/path/to/legacy --scope "user management module" \
103
+ --name nexus-workforce --source-stack aspnet-webforms \
104
+ --target-stack "dotnet-10 + vue3" --basis runnable
105
+ migrate phase probe --status done
106
+ ```
107
+
108
+ `init` exits 1 if `config.toml` already exists, and 2 if `--source` is
109
+ missing, is not a directory, or `--basis` is not `runnable` or
110
+ `source-only`. Confirm the write with `migrate status`, which prints the
111
+ detected stack and basis on its first line.
112
+
113
+ Running `migrate check --phase probe` here will not come back clean: the
114
+ census gate reads the whole store regardless of `--phase`, so it reports
115
+ every declared surface's lens record and every declared closer's record as
116
+ missing, correctly, because none of them exist yet. That is not a probe
117
+ defect; it is the same "other nine gates read the whole store" behavior
118
+ `SKILL.md` describes, and it is why probe's own close is the status flip
119
+ above, not a clean `check`.
120
+
121
+ ## Degradation
122
+
123
+ | Absent | Record |
124
+ |---|---|
125
+ | No VCS in the source | `vcs = "none"`. `init` detects this from the absence of `.git` and writes it for you; nothing to hand-edit. |
126
+ | No runnable environment | `basis = "source-only"`, plus the blocking evidence (missing SDK, a dependency that will not install, an environment nothing here can reach) written to `parity-basis.md`. This is the fact the runtime lens and the parity phase both read later: getting it right once here is the point of moving basis detection to phase 0 at all. |
127
+ | No documentation | Note it in `parity-basis.md` now (no `docs/` tree, no wiki export, nothing beyond a generated README). This does not close anything by itself, but it means the enumerate phase's docs lens can open with `not-applicable:no-documentation` immediately instead of re-discovering the same absence. |
128
+
129
+ ## Commands
130
+
131
+ ```
132
+ migrate init --source <path> --scope "<text>" --name <target> \
133
+ [--source-stack <s>] [--target-stack <s>] [--basis <runnable|source-only>]
134
+ migrate phase probe --status done
135
+ ```
@@ -0,0 +1,242 @@
1
+ # Phase 5: Queue
2
+
3
+ ## Purpose
4
+
5
+ Carry forward, for an owner to adjudicate, everything any phase could not
6
+ resolve on its own: evidence, the real options, and a recommendation.
7
+ Exit condition: every item filed anywhere in the run so far is
8
+ grammatically valid, every id the referential-integrity gate actually
9
+ checks resolves to a real queue file, and `migrate phase queue --status
10
+ done` has run. It is not "the queue is empty": nothing in this milestone
11
+ adjudicates an item, so a healthy run through this phase still ends with
12
+ open items, deliberately.
13
+
14
+ ## Inputs
15
+
16
+ - The store: `.migrate/queue/`, already holding whatever `migrate queue
17
+ add` has filed from any earlier phase. This phase reads what already
18
+ exists; it does not start a new file of its own the way `elements.jsonl`
19
+ or `requirements.jsonl` does.
20
+ - `templates/queue-item.md`: the skeleton every filed item should start
21
+ from. It carries the same three fields and three headings this manual's
22
+ grammar section states below, no more and no fewer.
23
+
24
+ ## Procedure
25
+
26
+ **The queue is cross-cutting, populated from any phase, not a phase that
27
+ runs once.** Every earlier manual in this set files items directly:
28
+ enumerate.md's zero-modularity escalation, extract.md's attribute and
29
+ rule-sweep and closer findings, parity.md's sub-high rubrics. This phase's
30
+ job is not to invent new items; it is to make sure everything already filed
31
+ is well-formed and everything the gate can check actually resolves, and
32
+ then to close.
33
+
34
+ **The grammar, stated once, before any example.** A queue item is a
35
+ markdown file whose stem matches its own `id`. Frontmatter carries `id`
36
+ (`q-` plus a lowercase kebab-case slug), `severity` (`critical`,
37
+ `moderate`, or `minor`), and `status` (`open`, or `adjudicated` with a
38
+ `ruling`). The body carries exactly three level-two headings, in any
39
+ order, case-sensitive and line-anchored (`## Evidence`, `## Options`,
40
+ `## Recommendation`), and **all three sections must be non-empty**. Missing
41
+ and empty are reported as distinguishable errors, not folded into one
42
+ generic complaint, so the fix is obvious from the message alone.
43
+
44
+ A worked example, the same file extract.md filed (built, in turn, on
45
+ `templates/queue-item.md`), run against a real store:
46
+
47
+ ```markdown
48
+ ---
49
+ id: q-reset-token-verify-missing
50
+ severity: critical
51
+ status: open
52
+ ---
53
+
54
+ ## Evidence
55
+
56
+ `read-write-symmetry` checked every write path against a matching read
57
+ path. `ResetPassword` writes a reset token via `GenerateResetToken` and
58
+ emails it, but no controller in the source reads or verifies a submitted
59
+ token: there is no `POST /api/password-reset/confirm` or equivalent. Either
60
+ the verification endpoint exists somewhere this pass did not look, or reset
61
+ tokens are issued and never checked.
62
+
63
+ ## Options
64
+
65
+ (a) Widen the search (other controllers, an area folder, a separate
66
+ service) before concluding it is missing. (b) Treat it as a real gap and
67
+ flag it for the target to fix, not replicate. (c) Ask the operator directly
68
+ whether reset ever worked end-to-end in production.
69
+
70
+ ## Recommendation
71
+
72
+ Recommend (c); a write with no matching read is exactly what this closer
73
+ exists to catch, and only the operator can say whether it is a real defect
74
+ or evidence this pass has not looked far enough yet.
75
+ ```
76
+
77
+ `migrate queue add q-reset-token-verify-missing.md` accepts this and prints
78
+ `queue add: q-reset-token-verify-missing [critical]`.
79
+
80
+ ### What the grammar rejects
81
+
82
+ Each of these, run against a real store, is refused before the file is ever
83
+ copied into `.migrate/queue/`; a rejected `queue add` never leaves a
84
+ half-written file behind.
85
+
86
+ - **Filename does not match `id`.** A file named `q-bad-mismatch.md` whose
87
+ frontmatter says `id: q-something-else`:
88
+ `filename q-bad-mismatch does not match id q-something-else`, exit 1.
89
+ - **A section is missing entirely.** No `## Options` heading anywhere in
90
+ the body: `missing ## Options section (a line reading exactly "##
91
+ Options", case-sensitive)`, exit 1.
92
+ - **A section is present but empty.** A `## Options` heading with nothing
93
+ before the next heading: `## Options section is empty`, exit 1.
94
+ - **No frontmatter block at all.** A file that never opens with `---\n`:
95
+ `missing --- frontmatter block`, exit **2**, not 1. This is the one
96
+ grammar failure that is a usage error rather than a content failure: the
97
+ file never resolved to a queue item in the first place, the same class as
98
+ a missing file or an unreadable one.
99
+ - **An invalid severity.** `severity: urgent`: `severity must be one of
100
+ critical, moderate, minor, got urgent`, exit 1.
101
+
102
+ Two more exist that are just as real but need a longer setup to trigger
103
+ faithfully rather than trust secondhand: an `id` that is not `q-` plus a
104
+ lowercase kebab-case slug, and a duplicate `## Evidence` heading (reported
105
+ as a duplicate, never silently taking the first and dropping the rest).
106
+ Both are enforced the same way, by name, before the file is copied.
107
+
108
+ ### Severity, and why the list is ordered
109
+
110
+ **`queue list` sorts by severity first (`critical`, `moderate`, `minor`, in
111
+ that order), then by id.** This is not cosmetic: the queue is meant to be
112
+ adjudicated top to bottom in one sitting, and an owner working that way
113
+ should see the item that most needs a decision first, every time, not
114
+ whatever happened to be filed most recently. Write each item short enough
115
+ that one pass through the whole list is actually plausible; a queue item
116
+ that takes a page to explain a one-line decision has failed its own point
117
+ just as much as one with no evidence at all.
118
+
119
+ A worked example: `migrate queue list`, run against a real store with five
120
+ items filed across extract.md and parity.md's examples, prints:
121
+
122
+ ```
123
+ q-reset-token-verify-missing critical open
124
+ q-account-lockout-scope moderate open
125
+ q-parity-um-003-reset-flow moderate open
126
+ q-users-islocked-semantics moderate open
127
+ q-legacy-admin-tool minor open
128
+ 5 item(s)
129
+ ```
130
+
131
+ The three `moderate` items sort by id alone (`account-lockout-scope` before
132
+ `parity-um-003-reset-flow` before `users-islocked-semantics`), since
133
+ severity does not separate them. When genuinely unsure which severity
134
+ fits, lean toward the one that puts the item in front of the owner sooner:
135
+ a real ambiguity mislabeled `minor` can sit unread far longer than the same
136
+ ambiguity mislabeled one tier too high ever costs.
137
+
138
+ ### Referential integrity
139
+
140
+ **The gate checks exactly three fields against real queue files, by name,
141
+ and no others: `confidence.queue` on a requirement whose `confidence.kind`
142
+ is `queued`; `disposition.queue` on an element whose `disposition.kind` is
143
+ `out-of-scope`; and `parity.queue` on a requirement whose `parity.kind` is
144
+ `rubric` at any level below `high`.** State this before relying on it for
145
+ anything else, because the obvious-sounding generalization is wrong: a
146
+ census record's own `queued` array (on a `lens`, `attribute`, `rule-sweep`,
147
+ or `closer` record) is never cross-checked against a real queue file by any
148
+ gate. Verified on a disposable copy of the store, taken before extract.md's
149
+ own queue items were filed: a census record whose `queued` array names an
150
+ id with no file behind it still passes `migrate check` with zero `refs`
151
+ violations for that id, on that copy. Filing the file anyway is still this
152
+ manual's discipline, exactly as extract.md says, even though nothing
153
+ downstream will ever catch you if you skip it there.
154
+
155
+ A worked example of what the gate does check, run on a disposable copy so
156
+ the extra requirement and queue item below never enter the running example
157
+ (which by this point already has all five of its own items filed and would
158
+ otherwise read as six): a requirement with `confidence: {"kind": "queued",
159
+ "queue": "q-bulk-import-scope"}` and no such file on disk yet.
160
+
161
+ ```
162
+ refs:
163
+ UM-005 references queue item q-bulk-import-scope via confidence.queue, which does not exist
164
+ ```
165
+
166
+ `migrate queue add q-bulk-import-scope.md`, on that same disposable copy,
167
+ files the missing item; the very next `migrate check` no longer names it,
168
+ with no other change to that copy. The message names which field the
169
+ reference came from (`disposition.queue`, `confidence.queue`, or
170
+ `parity.queue`) precisely so that one requirement dangling from two
171
+ different fields at once reads as two separate things to fix, not one
172
+ ambiguous-looking duplicate.
173
+
174
+ ## What closes it
175
+
176
+ There is no verb that empties the queue in this milestone; closing this
177
+ phase means every item filed so far is well-formed and every reference the
178
+ gate checks resolves, not that adjudication has happened. Run for real:
179
+
180
+ ```
181
+ migrate phase queue --status done
182
+ phase: queue is now done
183
+
184
+ migrate check --phase queue
185
+ 4/5 mapped, 1 out-of-scope, 0 unaccounted
186
+
187
+ Violations (6):
188
+ census:
189
+ declared surface jobs has no lens census record; the lens did not run or did not close
190
+ declared surface reports has no lens census record; the lens did not run or did not close
191
+ declared surface screens has no lens census record; the lens did not run or did not close
192
+ declared surface integrations has no lens census record; the lens did not run or did not close
193
+ declared surface workflows has no lens census record; the lens did not run or did not close
194
+ declared surface settings has no lens census record; the lens did not run or did not close
195
+ ```
196
+
197
+ The six remaining lines are the same census noise every earlier manual in
198
+ this set already explains, not a queue defect: those six surfaces were
199
+ never enumerated in this scratch run. Closed for real, on the same store
200
+ with a zero-finding lens record recorded for each: `migrate check --phase
201
+ queue` exits 0 with no violations at all, confirming this phase's own
202
+ gates (`queue`, and the three `refs` fields above) were clean the whole
203
+ time and only the unrelated census gap was ever holding exit 0 back.
204
+
205
+ Plain `migrate check`, with no `--phase`, still cannot reach exit 0 in this
206
+ version, exactly as `SKILL.md` says: `adjudicate` and `handoff` have no
207
+ verb yet, so their phases stay `pending` forever this milestone, and
208
+ `run-state` names both by hand:
209
+
210
+ ```
211
+ run-state:
212
+ phase adjudicate is pending; every phase through handoff must be done
213
+ phase handoff is pending; every phase through handoff must be done
214
+ ```
215
+
216
+ `migrate check --phase queue` is the real terminus this milestone offers;
217
+ `migrate status` afterward is the plainer read, and it is what actually
218
+ hands off to phase 6: `5 open queue item(s) of 5`, `resume: adjudicate, no
219
+ batches yet`.
220
+
221
+ ## Degradation
222
+
223
+ - **One malformed item among many well-formed ones.** `queue list`,
224
+ `queue show`, `check`, and `status` all keep going past it and report the
225
+ rest; one bad file never hides an entire directory's worth of good ones.
226
+ - **Genuinely unsure which severity to file under.** Covered above: lean
227
+ toward escalating rather than downgrading when truly unsure, since the
228
+ cost of a false escalation (an owner glances at it sooner than strictly
229
+ needed) is smaller than the cost of a false de-escalation (a real problem
230
+ waits at the bottom of the list).
231
+ - **A census record's own `queued` ids with no queue file behind them.**
232
+ Not caught by any gate, covered above; file them anyway, since a reviewer
233
+ reading this run against this manual will expect to find one.
234
+
235
+ ## Commands
236
+
237
+ ```
238
+ migrate queue add <item.md>
239
+ migrate queue list [--open]
240
+ migrate queue show <id>
241
+ migrate phase queue --status done
242
+ ```