@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.
- package/README.md +2 -2
- package/dist/cli/index.js +14 -10
- package/package.json +1 -1
- package/skills/index.json +4 -4
- package/skills/migrate/README.md +35 -23
- package/skills/migrate/SKILL.md +75 -15
- package/skills/migrate/bin/migrate.ts +90 -0
- package/skills/migrate/docs/architecture.md +61 -26
- package/skills/migrate/docs/reference.md +53 -8
- package/skills/migrate/fixtures/fake-gh.ts +113 -0
- package/skills/migrate/fixtures/flow-target/docs/WORK.md +12 -0
- package/skills/migrate/fixtures/flow-target/docs/modernisation/capability-map/.gitkeep +0 -0
- package/skills/migrate/fixtures/flow-target/tools/flow/src/cli.ts +156 -0
- package/skills/migrate/package.json +1 -1
- package/skills/migrate/references/phases/adjudicate.md +161 -0
- package/skills/migrate/references/phases/handoff.md +220 -0
- package/skills/migrate/references/phases/probe.md +2 -2
- package/skills/migrate/references/phases/queue.md +21 -14
- package/skills/migrate/references/run-ops.md +17 -13
- package/skills/migrate/scripts/__tests__/adapter-flow.test.ts +290 -0
- package/skills/migrate/scripts/__tests__/adapter-github.test.ts +232 -0
- package/skills/migrate/scripts/__tests__/adapter-markdown.test.ts +183 -0
- package/skills/migrate/scripts/__tests__/adjudicate.test.ts +332 -0
- package/skills/migrate/scripts/__tests__/assumptions.test.ts +179 -0
- package/skills/migrate/scripts/__tests__/coverage.test.ts +192 -0
- package/skills/migrate/scripts/__tests__/e2e-express.test.ts +167 -7
- package/skills/migrate/scripts/__tests__/e2e-webforms.test.ts +9 -4
- package/skills/migrate/scripts/__tests__/forecast.test.ts +280 -0
- package/skills/migrate/scripts/__tests__/gates-handoff.test.ts +309 -0
- package/skills/migrate/scripts/__tests__/handoff-cmd.test.ts +308 -0
- package/skills/migrate/scripts/__tests__/handoff-order.test.ts +156 -0
- package/skills/migrate/scripts/adapters/flow.ts +280 -0
- package/skills/migrate/scripts/adapters/github.ts +260 -0
- package/skills/migrate/scripts/adapters/markdown.ts +175 -0
- package/skills/migrate/scripts/adjudicate-cmd.ts +243 -0
- package/skills/migrate/scripts/assumptions.ts +188 -0
- package/skills/migrate/scripts/check.ts +119 -320
- package/skills/migrate/scripts/coverage-cmd.ts +86 -0
- package/skills/migrate/scripts/coverage.ts +151 -0
- package/skills/migrate/scripts/dates.ts +17 -0
- package/skills/migrate/scripts/forecast-cmd.ts +124 -0
- package/skills/migrate/scripts/forecast.ts +264 -0
- package/skills/migrate/scripts/gates/adjudication.ts +30 -0
- package/skills/migrate/scripts/gates/census.ts +107 -0
- package/skills/migrate/scripts/gates/citations.ts +11 -0
- package/skills/migrate/scripts/gates/context.ts +76 -0
- package/skills/migrate/scripts/gates/coverage.ts +22 -0
- package/skills/migrate/scripts/gates/deltas.ts +15 -0
- package/skills/migrate/scripts/gates/handoff.ts +145 -0
- package/skills/migrate/scripts/gates/leaks.ts +11 -0
- package/skills/migrate/scripts/gates/parity.ts +15 -0
- package/skills/migrate/scripts/gates/queue.ts +9 -0
- package/skills/migrate/scripts/gates/refs.ts +97 -0
- package/skills/migrate/scripts/gates/run-state.ts +67 -0
- package/skills/migrate/scripts/gates/source.ts +28 -0
- package/skills/migrate/scripts/handoff-cmd.ts +186 -0
- package/skills/migrate/scripts/handoff.ts +330 -0
- package/skills/migrate/scripts/paths.ts +4 -0
- package/skills/migrate/scripts/types.ts +43 -0
- package/skills/migrate/scripts/validate.ts +12 -0
- package/skills/migrate/skill.json +2 -2
- package/skills/migrate/templates/forecast-assumptions.md +59 -0
- package/skills/sluice/SKILL.md +20 -7
- package/skills/sluice/references/deep-channel.md +20 -0
- package/skills/sluice/references/finish.md +4 -2
- package/skills/sluice/references/meter.md +38 -0
- package/skills/sluice/scripts/run-stats.sh +236 -0
- 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 "
|
|
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
|
|
11
|
-
adjudicates an item, so a healthy run through
|
|
12
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|
217
|
-
`
|
|
218
|
-
hands off to phase 6: `5 open
|
|
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 `
|
|
122
|
-
whole store (`.migrate/.lock`) for the length of their
|
|
123
|
-
|
|
124
|
-
|
|
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:
|
|
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`
|
|
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
|
|
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 `
|
|
217
|
-
Verified against a scratch store holding a dead holder's lock:
|
|
218
|
-
exits `3` without the flag and `0` with it
|
|
219
|
-
|
|
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
|
|
284
|
+
not a CLI verb; `migrate` has no benchmark command.
|