@iceinvein/agent-skills 0.2.0 → 0.4.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 (68) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/index.js +14 -10
  3. package/package.json +1 -1
  4. package/skills/index.json +4 -4
  5. package/skills/migrate/README.md +35 -23
  6. package/skills/migrate/SKILL.md +75 -15
  7. package/skills/migrate/bin/migrate.ts +90 -0
  8. package/skills/migrate/docs/architecture.md +61 -26
  9. package/skills/migrate/docs/reference.md +53 -8
  10. package/skills/migrate/fixtures/fake-gh.ts +113 -0
  11. package/skills/migrate/fixtures/flow-target/docs/WORK.md +12 -0
  12. package/skills/migrate/fixtures/flow-target/docs/modernisation/capability-map/.gitkeep +0 -0
  13. package/skills/migrate/fixtures/flow-target/tools/flow/src/cli.ts +156 -0
  14. package/skills/migrate/package.json +1 -1
  15. package/skills/migrate/references/phases/adjudicate.md +161 -0
  16. package/skills/migrate/references/phases/handoff.md +220 -0
  17. package/skills/migrate/references/phases/probe.md +2 -2
  18. package/skills/migrate/references/phases/queue.md +21 -14
  19. package/skills/migrate/references/run-ops.md +17 -13
  20. package/skills/migrate/scripts/__tests__/adapter-flow.test.ts +290 -0
  21. package/skills/migrate/scripts/__tests__/adapter-github.test.ts +232 -0
  22. package/skills/migrate/scripts/__tests__/adapter-markdown.test.ts +183 -0
  23. package/skills/migrate/scripts/__tests__/adjudicate.test.ts +332 -0
  24. package/skills/migrate/scripts/__tests__/assumptions.test.ts +179 -0
  25. package/skills/migrate/scripts/__tests__/coverage.test.ts +192 -0
  26. package/skills/migrate/scripts/__tests__/e2e-express.test.ts +167 -7
  27. package/skills/migrate/scripts/__tests__/e2e-webforms.test.ts +9 -4
  28. package/skills/migrate/scripts/__tests__/forecast.test.ts +280 -0
  29. package/skills/migrate/scripts/__tests__/gates-handoff.test.ts +309 -0
  30. package/skills/migrate/scripts/__tests__/handoff-cmd.test.ts +308 -0
  31. package/skills/migrate/scripts/__tests__/handoff-order.test.ts +156 -0
  32. package/skills/migrate/scripts/adapters/flow.ts +280 -0
  33. package/skills/migrate/scripts/adapters/github.ts +260 -0
  34. package/skills/migrate/scripts/adapters/markdown.ts +175 -0
  35. package/skills/migrate/scripts/adjudicate-cmd.ts +243 -0
  36. package/skills/migrate/scripts/assumptions.ts +188 -0
  37. package/skills/migrate/scripts/check.ts +119 -320
  38. package/skills/migrate/scripts/coverage-cmd.ts +86 -0
  39. package/skills/migrate/scripts/coverage.ts +151 -0
  40. package/skills/migrate/scripts/dates.ts +17 -0
  41. package/skills/migrate/scripts/forecast-cmd.ts +124 -0
  42. package/skills/migrate/scripts/forecast.ts +264 -0
  43. package/skills/migrate/scripts/gates/adjudication.ts +30 -0
  44. package/skills/migrate/scripts/gates/census.ts +107 -0
  45. package/skills/migrate/scripts/gates/citations.ts +11 -0
  46. package/skills/migrate/scripts/gates/context.ts +76 -0
  47. package/skills/migrate/scripts/gates/coverage.ts +22 -0
  48. package/skills/migrate/scripts/gates/deltas.ts +15 -0
  49. package/skills/migrate/scripts/gates/handoff.ts +145 -0
  50. package/skills/migrate/scripts/gates/leaks.ts +11 -0
  51. package/skills/migrate/scripts/gates/parity.ts +15 -0
  52. package/skills/migrate/scripts/gates/queue.ts +9 -0
  53. package/skills/migrate/scripts/gates/refs.ts +97 -0
  54. package/skills/migrate/scripts/gates/run-state.ts +67 -0
  55. package/skills/migrate/scripts/gates/source.ts +28 -0
  56. package/skills/migrate/scripts/handoff-cmd.ts +186 -0
  57. package/skills/migrate/scripts/handoff.ts +330 -0
  58. package/skills/migrate/scripts/paths.ts +4 -0
  59. package/skills/migrate/scripts/types.ts +43 -0
  60. package/skills/migrate/scripts/validate.ts +12 -0
  61. package/skills/migrate/skill.json +2 -2
  62. package/skills/migrate/templates/forecast-assumptions.md +59 -0
  63. package/skills/sluice/SKILL.md +20 -7
  64. package/skills/sluice/references/deep-channel.md +20 -0
  65. package/skills/sluice/references/finish.md +4 -2
  66. package/skills/sluice/references/meter.md +38 -0
  67. package/skills/sluice/scripts/run-stats.sh +236 -0
  68. package/skills/sluice/skill.json +4 -3
@@ -0,0 +1,161 @@
1
+ # Phase 6: Adjudicate
2
+
3
+ ## Purpose
4
+
5
+ Get an owner's decision on every open queue item, in one pass, and record
6
+ each ruling where the gate reads it. Exit condition: every item in
7
+ `.migrate/queue/` has `status: adjudicated` with a non-empty `ruling`, every
8
+ downstream consequence of those rulings has been imported into the store,
9
+ and `migrate phase adjudicate --status done` has run.
10
+
11
+ This is the phase where the run stops being a mapping exercise. Nothing
12
+ before it needed a human to decide anything; nothing after it can proceed
13
+ until they have.
14
+
15
+ ## Inputs
16
+
17
+ - `.migrate/queue/*.md`, everything filed by any earlier phase. Phase 5
18
+ made sure they are well-formed; this phase gets them decided.
19
+ - The store, because most rulings have a consequence in it: an element's
20
+ `disposition`, a requirement's `confidence`, a requirement's `parity`.
21
+ - The owner. This is the one phase whose input is not in the repository.
22
+
23
+ ## Procedure
24
+
25
+ **Read the whole list before ruling on anything.** `migrate adjudicate` with
26
+ no arguments prints the review sheet: every item, severity first, with the
27
+ first line of its recommendation alongside. That last part is what makes one
28
+ sitting possible, and it is the reason to start here rather than opening
29
+ files:
30
+
31
+ ```
32
+ migrate adjudicate
33
+
34
+ q-mailer-unobservable [moderate] open - Recommend (b): nothing in the checkout shows this ever delivered mail.
35
+
36
+ 1 open
37
+ ```
38
+
39
+ Items are ordered `critical`, `moderate`, `minor`, then by id, the same
40
+ order `queue list` uses. Work down it.
41
+
42
+ **Draft a ruling for each, then present them together.** The agent's job is
43
+ to draft, not to decide. A drafted ruling that the owner accepts unchanged
44
+ is a good outcome; a drafted ruling nobody read is the failure this phase
45
+ exists to prevent. Present the whole set at once so the owner sees the
46
+ shape of what they are agreeing to, rather than being walked through four
47
+ separate decisions with no view of how they interact.
48
+
49
+ **Record each approved ruling.** One command per item:
50
+
51
+ ```
52
+ migrate adjudicate q-mailer-unobservable \
53
+ --ruling "out of scope until an operator confirms delivery ever worked"
54
+
55
+ adjudicate: q-mailer-unobservable
56
+ status open -> adjudicated
57
+ ruling recorded
58
+ next: apply the consequence with `migrate import`
59
+ ```
60
+
61
+ The file's frontmatter afterwards, with the body untouched:
62
+
63
+ ```
64
+ ---
65
+ id: q-mailer-unobservable
66
+ severity: moderate
67
+ status: adjudicated
68
+ ruling: out of scope until an operator confirms delivery ever worked
69
+ adjudicated: 2026-08-13
70
+ ---
71
+ ```
72
+
73
+ Three things about that write are worth knowing before you rely on them.
74
+ Keys the command does not own keep their position, so an item carrying its
75
+ own extra frontmatter is not reordered. The body round-trips byte for byte,
76
+ because it is the audit record of *why* the ruling was made and a rewrite
77
+ that reflows it destroys the thing being audited. And the ruling is a
78
+ single frontmatter line, so a ruling containing a newline is refused at
79
+ exit 2 rather than written into a block it would corrupt. Keep rulings to
80
+ one sentence; the reasoning belongs in the body, which already has it.
81
+
82
+ **The verb does not touch the row files.** This is the part most likely to
83
+ catch you out. `adjudicate` writes queue frontmatter and nothing else. A
84
+ ruling that puts an element out of scope, or settles a requirement's
85
+ confidence, or fixes a parity plan, is applied by `migrate import`, exactly
86
+ as in phases 3 and 4. That is why the command prints `next: apply the
87
+ consequence with 'migrate import'` on every success, and why this phase is
88
+ not finished when the last item flips to `adjudicated`.
89
+
90
+ Worked through: the ruling above says the mailer requirement is out of
91
+ scope until someone confirms delivery. `UD-003` currently carries
92
+ `confidence: {"kind": "queued", "queue": "q-mailer-unobservable"}`. The
93
+ ruling does not change that by itself. If the decision is to leave the
94
+ requirement recorded but unconfirmed, nothing further is needed and it
95
+ simply stays outside the confirmed denominator that `migrate coverage`
96
+ divides by. If the decision is that it is now settled, re-import the row
97
+ with the confidence the ruling gives it.
98
+
99
+ **Re-ruling refuses.** An item that already carries a ruling is not
100
+ silently overwritten:
101
+
102
+ ```
103
+ migrate adjudicate q-mailer-unobservable --ruling "changed my mind"
104
+
105
+ adjudicate: q-mailer-unobservable is already adjudicated: out of scope until an operator confirms delivery ever worked
106
+ adjudicate: pass --force to replace it
107
+ ```
108
+
109
+ Exit 1, and the existing ruling is printed so you can see what `--force`
110
+ would have replaced. An owner's recorded decision is not something a re-run
111
+ that meant no harm should be able to discard.
112
+
113
+ ## The gate
114
+
115
+ Gate 11, `adjudication`, checks exactly one thing: every queue item is
116
+ `adjudicated` and carries a non-empty ruling. It names each item that is
117
+ not, with its severity.
118
+
119
+ The gate is phase-scoped: it does not fire when the checked terminus is below
120
+ `adjudicate`, so `migrate check --phase queue` stays clean for a run that has
121
+ not reached this phase yet. From `migrate check --phase adjudicate` onward it
122
+ applies, and it also applies at any terminus once `phases.json` marks the
123
+ adjudicate phase done, so a store cannot claim the phase and then be checked
124
+ below it.
125
+
126
+ An open item on a store that is otherwise complete reads:
127
+
128
+ ```
129
+ adjudication:
130
+ q-mailer-unobservable [moderate] is still open; every queue item needs a ruling before handoff
131
+ ```
132
+
133
+ ## Degradation
134
+
135
+ - **The owner is unavailable.** Do not rule on their behalf and do not mark
136
+ the phase done. There is no partial-credit state here: an unruled item
137
+ blocks handoff, which is the correct outcome, because handing a build team
138
+ work whose open questions nobody answered is what this gate exists to
139
+ stop. Leave the phase `running` and say so in the handoff notes.
140
+ - **An item turns out to need no decision.** It still needs a ruling. Record
141
+ the reason it needed nothing (`no action: the endpoint was removed before
142
+ this run started`); the gate wants a decision recorded, not a change made.
143
+ - **A ruling that changes an earlier phase's output.** Apply it with the
144
+ same `migrate import` that phase used, then re-run `migrate check --phase
145
+ adjudicate`. Re-importing is an upsert by id, so it updates rather than
146
+ duplicating.
147
+ - **A queue file that will not parse.** `adjudicate <id>` on it exits 1 and
148
+ names the grammar failure; fix the file with phase 5's grammar and try
149
+ again. An id with no file at all is exit 2, a different class: the request
150
+ never resolved to an item.
151
+
152
+ ## Commands
153
+
154
+ ```
155
+ migrate adjudicate
156
+ migrate adjudicate <id> --ruling "<text>"
157
+ migrate adjudicate <id> --ruling "<text>" --force
158
+ migrate import <elements|reqs|deltas> <batch.json>
159
+ migrate check --phase adjudicate
160
+ migrate phase adjudicate --status done
161
+ ```
@@ -0,0 +1,220 @@
1
+ # Phase 7: Handoff
2
+
3
+ ## Purpose
4
+
5
+ Emit the mapped requirements into whatever a delivery team actually works
6
+ from, in dependency order, and record what was emitted so progress can be
7
+ read back. Exit condition: `.migrate/handoff.json` exists and accounts for
8
+ every requirement, the adapter's artifacts are in the target, and `migrate
9
+ phase handoff --status done` has run. After that, plain `migrate check`,
10
+ with no `--phase`, exits 0, and that is what "the migration is mapped"
11
+ means.
12
+
13
+ This phase does not deliver anything. It hands over.
14
+
15
+ ## Inputs
16
+
17
+ - `.migrate/requirements.jsonl` and `capabilities.jsonl`: what to emit and
18
+ how to group it.
19
+ - `.migrate/config.toml`, `[handoff] adapter`: which medium. Overridable
20
+ per run with `--adapter`.
21
+ - Whatever the chosen adapter reaches: the target repo for `markdown`,
22
+ `gh` for `github`, a flow target's own tree for `flow`.
23
+
24
+ ## Procedure
25
+
26
+ **Choose the adapter once, in config, not per invocation.** `--adapter`
27
+ exists for trying one out and for reading coverage back through a different
28
+ medium than the one that emitted. Switching the configured adapter mid-run
29
+ does not migrate anything that was already emitted; it emits again,
30
+ somewhere else.
31
+
32
+ | Adapter | Emits | Reads progress from |
33
+ |---|---|---|
34
+ | `markdown` | `docs/migrate/roadmap.md` plus one file per capability | ticked checkboxes in the roadmap, dated in the file |
35
+ | `github` | a milestone per capability, an issue per requirement | closed issues, dated from `closedAt` |
36
+ | `flow` | `docs/modernisation/capability-map/<slug>.md`, and a fenced block under `## Proposed` in `docs/WORK.md` when the target has one | `flow parity --json` in the target, undated |
37
+
38
+ **Dry-run first.** `--dry-run` runs every refusal check and prints the plan
39
+ without writing anything at all, `handoff.json` included:
40
+
41
+ ```
42
+ migrate handoff --dry-run
43
+
44
+ plan:
45
+ user-directory (3 requirement(s))
46
+ handoff: dry run, 1 work item(s), nothing written
47
+ ```
48
+
49
+ A capability appears after every capability it cites, which is what
50
+ "dependency order" means here: capability A depends on B when a requirement
51
+ in A carries a ledger citation to an element the seam assigned to B. If the
52
+ graph has a cycle, its members are emitted in slug order and anything merely
53
+ blocked by that cycle still sorts normally behind it. The cycle is broken rather
54
+ than reported: `dependencyOrder` returns which capabilities were in one, but no
55
+ caller prints it today, so a cyclic seam is resolved silently.
56
+
57
+ **The refusals, and what each means.** Handoff will not emit while anything
58
+ is unresolved, and it names every blocker at once rather than one per run:
59
+
60
+ ```
61
+ migrate handoff
62
+
63
+ handoff: [run-state] phase adjudicate is pending; every phase through adjudicate must be done
64
+ handoff: [adjudication] q-mailer-unobservable [moderate] is still open; every queue item needs a ruling before handoff
65
+ handoff: UD-003 blocked by q-mailer-unobservable
66
+ handoff: refusing to emit with 3 blocker(s)
67
+ ```
68
+
69
+ The gate run behind this has citations and leaks both switched on, because
70
+ both are mandatory before handoff, and is bounded at `adjudicate` so that
71
+ gate 12 (which wants the `handoff.json` this command has not written yet)
72
+ cannot refuse the very run that would satisfy it.
73
+
74
+ **"Blocked" is measured against open items, not against the confidence
75
+ field.** A requirement blocks handoff when its `confidence` is `queued`, or
76
+ its `parity` is a `rubric` below `high`, **and** the queue item it points at
77
+ is still open. Once that item is adjudicated the decision is settled and the
78
+ requirement stops blocking, even though its confidence still reads `queued`.
79
+ This matters in practice: it means a ruling of "leave this one unconfirmed"
80
+ does not oblige you to re-import the row before you can hand over.
81
+
82
+ **Emit.**
83
+
84
+ ```
85
+ migrate handoff
86
+
87
+ handoff: adapter markdown, 1 work item(s), 3 requirement(s)
88
+ created 1
89
+ updated 0
90
+ unchanged 0
91
+ next: mark the phase done with `migrate phase handoff --status done`, then read progress back with `migrate coverage`
92
+ ```
93
+
94
+ Running it again over an unchanged store reports everything `unchanged` and
95
+ writes nothing new. Every adapter is idempotent, and each achieves it
96
+ differently: `markdown` compares rendered content, `github` finds its own
97
+ issues by a `<!-- migrate:fr=... -->` marker at the start of the body, `flow`
98
+ compares rendered content and then validates the result with the target's own
99
+ `flow map --check`. A run that rewrote a shared file (the roadmap, `WORK.md`)
100
+ reports its items `updated` rather than `unchanged`, so the status always
101
+ reflects whether anything in the target moved.
102
+
103
+ Each adapter owns only what it wrote. `github` regenerates the block above a
104
+ closing fence in an issue body and leaves anything you add beneath it alone;
105
+ `flow` rewrites only its own fenced block under `## Proposed` and never touches
106
+ the rest of `WORK.md`. Editing a file somebody else writes in is the reason
107
+ both boundaries are explicit rather than inferred from shape.
108
+
109
+ **`handoff.json` is the record, and it carries no timestamps.**
110
+
111
+ ```jsonc
112
+ {
113
+ "version": 1,
114
+ "adapter": "markdown",
115
+ "items": [
116
+ { "key": "user-directory", "title": "User Directory",
117
+ "frs": ["UD-001", "UD-002", "UD-003"], "dependsOn": [], "weight": 3 }
118
+ ],
119
+ "refs": { "user-directory": "docs/migrate/capabilities/user-directory.md" },
120
+ "basis": { "confirmed": 2, "emitted": 3, "order": ["user-directory"] }
121
+ }
122
+ ```
123
+
124
+ `emitted` is every requirement that reached a work item; `confirmed` is the
125
+ denominator `migrate coverage` divides by, and the two differ by exactly the
126
+ requirements handoff emits but parity does not hold the build to. The
127
+ absence of dates is deliberate: every date this tool reports is read at read
128
+ time from the adapter's medium, which is what lets two runs over one store
129
+ produce identical bytes.
130
+
131
+ **Using the `flow` adapter.** One constraint will catch you before anything
132
+ else does. The flow target derives a requirement-id pattern from each
133
+ capability's declared `ns` and rejects anything that does not match, so
134
+ `plan()` refuses first, by name:
135
+
136
+ ```
137
+ handoff: flow: 1 requirement id(s) do not match their capability's namespace pattern <ns>-NNN, which the flow target requires:
138
+ login-001 (capability user-management, ns UM)
139
+ ```
140
+
141
+ Nothing is written when that fires. Fix the ids in the store (re-import with
142
+ the corrected ids) rather than working around it: the alternative is a
143
+ capability file the target cannot parse, in a repo this tool does not own.
144
+ The adapter also translates on the way out, since the two vocabularies
145
+ differ: `accidental-candidate` becomes `poss-accidental`, and the three
146
+ confidence kinds become `Confirmed`, `Inferred` and `Speculative`.
147
+
148
+ ## The gate
149
+
150
+ Gate 12, `handoff`, asks whether the requirements actually reached the
151
+ emitted work. It checks that `handoff.json` exists and is well formed, that
152
+ every requirement appears in exactly one work item, that every `frs` entry
153
+ resolves to a requirement, that every `dependsOn` resolves to another work item
154
+ and is not the item itself, that no work-item key repeats, that every item has
155
+ a `refs` entry recording where it went, that `basis.order` and the work items
156
+ name the same set, and that the basis counts match the store.
157
+
158
+ Every requirement, not only the confirmed ones: an inferred requirement is
159
+ something the build team must see and decide about, so handoff emits it.
160
+ Confidence starts mattering at the coverage denominator, not here.
161
+
162
+ Like gate 11 it is phase-scoped, so it does not fire below `--phase handoff`
163
+ **unless `phases.json` already claims the phase is done**. A store whose own
164
+ state file says it reached handoff cannot hide an unemitted handoff by being
165
+ checked at an earlier terminus. Its honest limit is the same in kind as the run-state gate's: it
166
+ proves the emitted work covers the store's requirements. It cannot prove the
167
+ issues were read or the roadmap was believed.
168
+
169
+ ## Reading progress back
170
+
171
+ `migrate coverage` divides built by confirmed and names its evidence:
172
+
173
+ ```
174
+ built 2/2 confirmed requirements (100%)
175
+ evidence: markdown roadmap checkboxes, dated in file
176
+ excluded: 1 non-confirmed (user-directory 1)
177
+
178
+ user-directory 2/2 done
179
+ ```
180
+
181
+ `migrate forecast` needs an owner-attested `.migrate/forecast-assumptions.md`
182
+ (copy `templates/forecast-assumptions.md`) and refuses without one. It
183
+ projects from two measured velocities and labels every scenario as measured
184
+ or as an owner target, so an aspiration never reads as a fact. Both commands
185
+ are meant to be re-run as delivery proceeds; they are the only part of this
186
+ tool that keeps working after the mapping run ends.
187
+
188
+ ## Degradation
189
+
190
+ - **The adapter partially applied.** Re-run it. Every adapter is
191
+ idempotent, and finishing a partial apply by re-running is the designed
192
+ path. For `markdown` specifically, re-running preserves every ticked box
193
+ and its date, so picking up newly extracted requirements never costs the
194
+ owner their record of what was delivered.
195
+ - **No flow CLI in the target.** The `flow` adapter still emits, and says on
196
+ stderr that the capability files were not validated against the target's
197
+ own parser. An unvalidated emission must not read as a checked one.
198
+ - **`flow` coverage has no dates.** Expected. The flow target computes
199
+ covered from merged slices plus a baseline and keeps the dates in a slice
200
+ ledger this tool does not own, so every completion comes back undated.
201
+ Coverage still works; forecast's measured rows print `not projected` and
202
+ its target rows still project. Use `flow forecast` in the target for the
203
+ dated answer.
204
+ - **An adapter with no throughput at all.** `coverage` names it rather than
205
+ reporting zero built. "This adapter cannot tell you" and "nothing has been
206
+ delivered" are very different claims.
207
+ - **A completion naming a requirement the store does not have.** `coverage`
208
+ exits 1: the emitted work and the store have diverged, which is a real
209
+ problem rather than a degradation.
210
+
211
+ ## Commands
212
+
213
+ ```
214
+ migrate handoff --dry-run
215
+ migrate handoff [--adapter <markdown|github|flow>]
216
+ migrate phase handoff --status done
217
+ migrate check
218
+ migrate coverage
219
+ migrate forecast
220
+ ```
@@ -114,8 +114,8 @@ Running `migrate check --phase probe` here will not come back clean: the
114
114
  census gate reads the whole store regardless of `--phase`, so it reports
115
115
  every declared surface's lens record and every declared closer's record as
116
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
117
+ defect; it is the same "gates that are neither run-state nor phase-scoped
118
+ read the whole store" behavior `SKILL.md` describes, and it is why probe's own close is the status flip
119
119
  above, not a clean `check`.
120
120
 
121
121
  ## Degradation
@@ -7,9 +7,9 @@ resolve on its own: evidence, the real options, and a recommendation.
7
7
  Exit condition: every item filed anywhere in the run so far is
8
8
  grammatically valid, every id the referential-integrity gate actually
9
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.
10
+ done` has run. It is not "the queue is empty": nothing in *this* phase
11
+ adjudicates an item, so a healthy run through phase 5 still ends with open
12
+ items, deliberately. Phase 6 is where they get decided.
13
13
 
14
14
  ## Inputs
15
15
 
@@ -173,9 +173,12 @@ ambiguous-looking duplicate.
173
173
 
174
174
  ## What closes it
175
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:
176
+ No verb empties the queue here; closing this phase means every item filed so
177
+ far is well-formed and every reference the gate checks resolves, not that
178
+ adjudication has happened. `migrate adjudicate` is phase 6's verb, and the
179
+ `adjudication` gate that requires a ruling on every item is phase-scoped, so
180
+ it stays silent until the checked terminus reaches `adjudicate`. Run for
181
+ real:
179
182
 
180
183
  ```
181
184
  migrate phase queue --status done
@@ -202,21 +205,25 @@ queue` exits 0 with no violations at all, confirming this phase's own
202
205
  gates (`queue`, and the three `refs` fields above) were clean the whole
203
206
  time and only the unrelated census gap was ever holding exit 0 back.
204
207
 
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:
208
+ Plain `migrate check`, with no `--phase`, still fails here, and should:
209
+ phases 6 and 7 have not run, so `run-state` names both, the `adjudication`
210
+ gate names every item nobody has ruled on, and the `handoff` gate reports
211
+ that nothing has been emitted.
209
212
 
210
213
  ```
211
214
  run-state:
212
215
  phase adjudicate is pending; every phase through handoff must be done
213
216
  phase handoff is pending; every phase through handoff must be done
217
+ adjudication:
218
+ q-reset-token-verify-missing [critical] is still open; every queue item needs a ruling before handoff
219
+ handoff:
220
+ no handoff.json in the store; handoff has not run, so nothing has reached a delivery medium
214
221
  ```
215
222
 
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`.
223
+ `migrate check --phase queue` is this phase's terminus; plain `migrate
224
+ check` becomes reachable once phase 7 closes. `migrate status` afterward is
225
+ the plainer read, and it is what actually hands off to phase 6: `5 open
226
+ queue item(s) of 5`, `resume: adjudicate, no batches yet`.
220
227
 
221
228
  ## Degradation
222
229
 
@@ -118,10 +118,12 @@ resuming a crashed run a read rather than a recollection.
118
118
 
119
119
  Enforced. Every claim in this section was run.
120
120
 
121
- `import`, `census`, `phase --status`, and `reset` each take one lock over the
122
- whole store (`.migrate/.lock`) for the length of their read-modify-write. Those
123
- four are the whole set; every other command either only reads the store, or
124
- does not touch it. Without
121
+ `import`, `census`, `phase --status`, `reset`, `adjudicate` and `handoff` each
122
+ take one lock over the whole store (`.migrate/.lock`) for the length of their
123
+ read-modify-write. Those six are the whole set for the read-modify-write over a
124
+ whole store file. Every other command either only reads the store, or writes a
125
+ file nothing else contends for: `init` creates `config.toml`, `queue add` copies
126
+ one item in. Without
125
127
  it, two agents importing at once each read the same base file, and whichever
126
128
  one rewrites last silently discards the other's rows. The default wait is 30
127
129
  seconds, polling with backoff (25ms, growing by roughly 1.5x each attempt, up
@@ -135,7 +137,7 @@ and the answer is no) and `2` (a malformed request: a bad flag, a missing
135
137
  file). A lock failure says the request itself was fine; there is nothing
136
138
  about your `batch.json`, or about the `--phase` you named, to go back and
137
139
  inspect, regardless of which of the three ways below the lock failed. (Three
138
- ways the lock can fail, not three commands that take it: four commands do,
140
+ ways the lock can fail, not three commands that take it: six commands do,
139
141
  listed above.)
140
142
 
141
143
  That said, "retry" resolves the three underlying causes differently, and only
@@ -162,8 +164,8 @@ message text is exact):
162
164
  import: waiting for store lock (held by pid 51234 since 2026-08-07T09:14:02.001Z)
163
165
  ```
164
166
 
165
- `phase --status` is the one that does not print this line. It waits on the
166
- same lock with the same backoff, silently: run against a scratch store with a
167
+ `phase --status`, `adjudicate` and `handoff` do not print this line. All three
168
+ wait on the same lock with the same backoff, silently: run against a scratch store with a
167
169
  live holder released two seconds in, `migrate phase enumerate --status running`
168
170
  printed nothing at all until it succeeded, 1.85 seconds later. `reset` does
169
171
  print it, verified the same way against a live holder released two seconds in:
@@ -172,7 +174,7 @@ print it, verified the same way against a live holder released two seconds in:
172
174
  reset: waiting for store lock (held by pid 74987 since 2026-08-08T00:00:00.000Z)
173
175
  ```
174
176
 
175
- If the wait for any of the four ends in a timeout instead, all four report it
177
+ If the wait ends in a timeout instead, every lock-taker reports it
176
178
  the same way, with their own command name prefixed:
177
179
 
178
180
  ```
@@ -213,10 +215,12 @@ what you read here matches what you'll see on screen exactly.
213
215
 
214
216
  **`--force-unlock` is only appropriate after confirming the named pid is not
215
217
  running and no other agent is mid-write.** It exists on `import`, `census`,
216
- `phase`, and `reset`, the same four commands that take the lock at all.
217
- Verified against a scratch store holding a dead holder's lock: each of the four
218
- exits `3` without the flag and `0` with it. `reset` matters most here of the
219
- four, since it is the one whose whole job is deleting rows. What it actually
218
+ `phase`, `reset`, `adjudicate` and `handoff`, the same commands that take the
219
+ lock at all. Verified against a scratch store holding a dead holder's lock:
220
+ each of the original four exits `3` without the flag and `0` with it, and
221
+ `adjudicate` and `handoff` were brought onto the same exit code afterwards, so
222
+ all six behave alike. `reset`
223
+ matters most of them, since it is the one whose whole job is deleting rows. What it actually
220
224
  does is blunt: it unlinks the lock file
221
225
  unconditionally, before this process even checks who, if anyone, holds it.
222
226
  The CLI does not verify staleness for you when you pass this flag; the
@@ -277,4 +281,4 @@ itself is being validated against a benchmark rather than run against a live
277
281
  migration, the agent producing the mapping and the agent scoring it against
278
282
  ground truth must not be the same agent, and must not share context that
279
283
  would let one see the other's answer. This is a v1 documented discipline,
280
- not a CLI verb; `migrate` has no benchmark command in this milestone.
284
+ not a CLI verb; `migrate` has no benchmark command.