@iceinvein/agent-skills 0.1.39 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -2
- package/dist/cli/index.js +105 -28
- package/package.json +1 -1
- package/skills/index.json +14 -2
- package/skills/magpie/README.md +2 -1
- package/skills/magpie/SKILL.md +138 -45
- package/skills/magpie/bin/magpie.ts +43 -0
- package/skills/magpie/fixtures/example-pr/brief.json +18 -0
- package/skills/magpie/fixtures/fake-gh-nodiff.sh +38 -0
- package/skills/magpie/fixtures/fake-gh.sh +14 -0
- package/skills/magpie/package.json +1 -1
- package/skills/magpie/references/peer-review.md +7 -2
- package/skills/magpie/references/scout.md +90 -0
- package/skills/magpie/references/specialists.md +117 -6
- package/skills/magpie/scripts/__tests__/cli.test.ts +101 -1
- package/skills/magpie/scripts/__tests__/dedupe-cmd.test.ts +187 -0
- package/skills/magpie/scripts/__tests__/diff-chunks.test.ts +51 -0
- package/skills/magpie/scripts/__tests__/filter-diff-preservation.test.ts +54 -0
- package/skills/magpie/scripts/__tests__/findings-files.test.ts +35 -0
- package/skills/magpie/scripts/__tests__/gh.test.ts +90 -0
- package/skills/magpie/scripts/__tests__/git-diff.test.ts +83 -0
- package/skills/magpie/scripts/__tests__/helpers/git-fixture.ts +47 -0
- package/skills/magpie/scripts/__tests__/path-filter.test.ts +27 -0
- package/skills/magpie/scripts/__tests__/preview-cmd.test.ts +16 -0
- package/skills/magpie/scripts/__tests__/refresh.test.ts +45 -0
- package/skills/magpie/scripts/__tests__/render-cmd.test.ts +173 -1
- package/skills/magpie/scripts/__tests__/render-findings.test.ts +151 -1
- package/skills/magpie/scripts/__tests__/render-progress.test.ts +42 -0
- package/skills/magpie/scripts/__tests__/setup-cmd.test.ts +83 -1
- package/skills/magpie/scripts/__tests__/shard.test.ts +165 -0
- package/skills/magpie/scripts/__tests__/skill-lint.test.ts +207 -7
- package/skills/magpie/scripts/__tests__/types.test.ts +47 -0
- package/skills/magpie/scripts/dedupe-cmd.ts +58 -3
- package/skills/magpie/scripts/diff-chunks.ts +28 -0
- package/skills/magpie/scripts/findings-files.ts +32 -0
- package/skills/magpie/scripts/gh.ts +68 -14
- package/skills/magpie/scripts/git-diff.ts +111 -0
- package/skills/magpie/scripts/path-filter.ts +9 -5
- package/skills/magpie/scripts/preview-cmd.ts +11 -1
- package/skills/magpie/scripts/refresh.ts +32 -3
- package/skills/magpie/scripts/render-cmd.ts +35 -11
- package/skills/magpie/scripts/render-findings.ts +78 -2
- package/skills/magpie/scripts/render-progress.ts +6 -1
- package/skills/magpie/scripts/setup-cmd.ts +38 -1
- package/skills/magpie/scripts/shard.ts +171 -0
- package/skills/magpie/scripts/status-cmd.ts +7 -4
- package/skills/magpie/scripts/types.ts +50 -0
- package/skills/magpie/skill.json +2 -2
- package/skills/magpie/templates/styles.css +66 -0
- package/skills/migrate/README.md +194 -0
- package/skills/migrate/SKILL.md +197 -0
- package/skills/migrate/bin/migrate +15 -0
- package/skills/migrate/bin/migrate.ts +309 -0
- package/skills/migrate/biome.json +35 -0
- package/skills/migrate/bun.lock +24 -0
- package/skills/migrate/docs/architecture.md +294 -0
- package/skills/migrate/docs/reference.md +590 -0
- package/skills/migrate/fixtures/tiny-express/GROUND-TRUTH.md +39 -0
- package/skills/migrate/fixtures/tiny-express/app.js +29 -0
- package/skills/migrate/fixtures/tiny-express/cron.js +6 -0
- package/skills/migrate/fixtures/tiny-express/reports/daily-users.json +6 -0
- package/skills/migrate/fixtures/tiny-express/schema.sql +12 -0
- package/skills/migrate/fixtures/tiny-express/settings.json +4 -0
- package/skills/migrate/fixtures/tiny-express/views/users.html +9 -0
- package/skills/migrate/fixtures/tiny-webforms/Controllers/UsersController.cs +68 -0
- package/skills/migrate/fixtures/tiny-webforms/Default.aspx +7 -0
- package/skills/migrate/fixtures/tiny-webforms/Default.aspx.cs +14 -0
- package/skills/migrate/fixtures/tiny-webforms/GROUND-TRUTH.md +50 -0
- package/skills/migrate/fixtures/tiny-webforms/Integrations/BillingClient.cs +16 -0
- package/skills/migrate/fixtures/tiny-webforms/Jobs/NightlyDigestJob.cs +33 -0
- package/skills/migrate/fixtures/tiny-webforms/Reports/DailyUsers.rdl +11 -0
- package/skills/migrate/fixtures/tiny-webforms/Schema.sql +12 -0
- package/skills/migrate/fixtures/tiny-webforms/Site.master +16 -0
- package/skills/migrate/fixtures/tiny-webforms/Users.aspx +8 -0
- package/skills/migrate/fixtures/tiny-webforms/Users.aspx.cs +14 -0
- package/skills/migrate/fixtures/tiny-webforms/web.config +10 -0
- package/skills/migrate/install.sh +68 -0
- package/skills/migrate/package.json +17 -0
- package/skills/migrate/references/phases/enumerate.md +291 -0
- package/skills/migrate/references/phases/extract.md +652 -0
- package/skills/migrate/references/phases/parity.md +275 -0
- package/skills/migrate/references/phases/probe.md +135 -0
- package/skills/migrate/references/phases/queue.md +242 -0
- package/skills/migrate/references/phases/seam.md +416 -0
- package/skills/migrate/references/recipes/README.md +116 -0
- package/skills/migrate/references/recipes/aspnet.md +287 -0
- package/skills/migrate/references/run-ops.md +280 -0
- package/skills/migrate/scripts/__tests__/census.test.ts +775 -0
- package/skills/migrate/scripts/__tests__/check.test.ts +458 -0
- package/skills/migrate/scripts/__tests__/citations.test.ts +156 -0
- package/skills/migrate/scripts/__tests__/cli.test.ts +183 -0
- package/skills/migrate/scripts/__tests__/concurrency.test.ts +164 -0
- package/skills/migrate/scripts/__tests__/config.test.ts +112 -0
- package/skills/migrate/scripts/__tests__/e2e-express.test.ts +1093 -0
- package/skills/migrate/scripts/__tests__/e2e-webforms.test.ts +1276 -0
- package/skills/migrate/scripts/__tests__/e2e.test.ts +320 -0
- package/skills/migrate/scripts/__tests__/ids.test.ts +38 -0
- package/skills/migrate/scripts/__tests__/import.test.ts +155 -0
- package/skills/migrate/scripts/__tests__/init.test.ts +192 -0
- package/skills/migrate/scripts/__tests__/leaks.test.ts +176 -0
- package/skills/migrate/scripts/__tests__/lock.test.ts +183 -0
- package/skills/migrate/scripts/__tests__/paths.test.ts +129 -0
- package/skills/migrate/scripts/__tests__/phase-cmd.test.ts +151 -0
- package/skills/migrate/scripts/__tests__/phases.test.ts +70 -0
- package/skills/migrate/scripts/__tests__/queue.test.ts +475 -0
- package/skills/migrate/scripts/__tests__/report.test.ts +150 -0
- package/skills/migrate/scripts/__tests__/run-state.test.ts +136 -0
- package/skills/migrate/scripts/__tests__/status-reset.test.ts +318 -0
- package/skills/migrate/scripts/__tests__/store.test.ts +132 -0
- package/skills/migrate/scripts/__tests__/validate.test.ts +54 -0
- package/skills/migrate/scripts/census-cmd.ts +109 -0
- package/skills/migrate/scripts/census.ts +342 -0
- package/skills/migrate/scripts/check-cmd.ts +24 -0
- package/skills/migrate/scripts/check.ts +376 -0
- package/skills/migrate/scripts/citations.ts +92 -0
- package/skills/migrate/scripts/config.ts +237 -0
- package/skills/migrate/scripts/ids.ts +31 -0
- package/skills/migrate/scripts/import-cmd.ts +141 -0
- package/skills/migrate/scripts/init-cmd.ts +118 -0
- package/skills/migrate/scripts/leaks.ts +184 -0
- package/skills/migrate/scripts/lock.ts +188 -0
- package/skills/migrate/scripts/paths.ts +103 -0
- package/skills/migrate/scripts/phase-cmd.ts +63 -0
- package/skills/migrate/scripts/phases.ts +113 -0
- package/skills/migrate/scripts/queue-cmd.ts +98 -0
- package/skills/migrate/scripts/queue.ts +258 -0
- package/skills/migrate/scripts/report-cmd.ts +47 -0
- package/skills/migrate/scripts/report.ts +131 -0
- package/skills/migrate/scripts/reset-cmd.ts +120 -0
- package/skills/migrate/scripts/status-cmd.ts +52 -0
- package/skills/migrate/scripts/store.ts +159 -0
- package/skills/migrate/scripts/types.ts +137 -0
- package/skills/migrate/scripts/validate.ts +221 -0
- package/skills/migrate/skill.json +33 -0
- package/skills/migrate/templates/config.toml +27 -0
- package/skills/migrate/templates/queue-item.md +17 -0
- package/skills/migrate/tsconfig.json +18 -0
- package/skills/migrate/uninstall.sh +31 -0
- package/skills/sluice/SKILL.md +82 -0
- package/skills/sluice/references/deep-channel.md +94 -0
- package/skills/sluice/references/finish.md +35 -0
- package/skills/sluice/references/intent.md +29 -0
- package/skills/sluice/references/review.md +42 -0
- package/skills/sluice/references/root-cause.md +38 -0
- package/skills/sluice/references/show-or-say.md +36 -0
- package/skills/sluice/references/test-first.md +35 -0
- package/skills/sluice/references/verify.md +26 -0
- package/skills/sluice/skill.json +32 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: migrate
|
|
3
|
+
description: Source-agnostic legacy migration mapping. Walks a legacy codebase through probe, enumerate, seam, extract, parity, and queue, building an auditable requirements ledger with mandatory citations and a `migrate check` gate in place of self-reported completeness. Use when the user asks to migrate, re-specify, replatform, or map a legacy system onto a new stack, or to resume, check, or report on a mapping run already under way.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# migrate
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
- `migrate` on `PATH`, put there by this skill's `install.sh`.
|
|
11
|
+
- A read-only checkout of the legacy source. `migrate check`'s `citations` and
|
|
12
|
+
`source` gates read it; nothing here writes to it, and every writer in the CLI
|
|
13
|
+
refuses a path that resolves inside it.
|
|
14
|
+
- A target repo that is a git working copy. The store lives inside it and
|
|
15
|
+
commits alongside your own work; there is no separate run directory.
|
|
16
|
+
|
|
17
|
+
## Phase walkthrough
|
|
18
|
+
|
|
19
|
+
Work phases 0 through 7 in order. Do not skip ahead: the run-state gate fails a
|
|
20
|
+
phase marked `done` while its predecessor is still `pending`, so working out of
|
|
21
|
+
order just produces a violation you undo later.
|
|
22
|
+
|
|
23
|
+
### 0. Probe
|
|
24
|
+
|
|
25
|
+
Produces `.migrate/config.toml` (detected source stack, `runnable` or
|
|
26
|
+
`source-only` basis, the target profile), written by `migrate init`, plus
|
|
27
|
+
`.migrate/parity-basis.md`: hand-written prose carrying the detection
|
|
28
|
+
evidence, since no command writes it either.
|
|
29
|
+
|
|
30
|
+
Read `references/phases/probe.md` before dispatching anything.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
migrate init --source <path> --scope "<text>" --name <target> \
|
|
34
|
+
[--source-stack <s>] [--target-stack <s>] [--basis <runnable|source-only>]
|
|
35
|
+
migrate phase probe --status done
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 1. Enumerate
|
|
39
|
+
|
|
40
|
+
Produces `elements.jsonl`, every row `unaccounted`, and one `lens` census
|
|
41
|
+
record per declared surface type.
|
|
42
|
+
|
|
43
|
+
Read `references/phases/enumerate.md` before dispatching anything.
|
|
44
|
+
|
|
45
|
+
Fanout unit: one agent per (surface, lens) pair.
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
migrate import elements <batch.json>
|
|
49
|
+
migrate census <lens-record.json>
|
|
50
|
+
migrate phase enumerate --status done
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Seam
|
|
54
|
+
|
|
55
|
+
Produces `capabilities.jsonl` (the seam partition), `seam.json` (run-level
|
|
56
|
+
seam metadata), and `seam.md` (the validators' raw evidence). All three are
|
|
57
|
+
hand-written: there is no `seam` verb, so nothing in the CLI authors their
|
|
58
|
+
content. (`migrate reset --phase seam` does write to these paths, clearing
|
|
59
|
+
`capabilities.jsonl` and deleting the other two, but that undoes the phase
|
|
60
|
+
rather than authoring it.)
|
|
61
|
+
|
|
62
|
+
Read `references/phases/seam.md` before dispatching anything.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
migrate phase seam --status done
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. Extract
|
|
69
|
+
|
|
70
|
+
Produces `requirements.jsonl`, the attribute/rule-sweep/closer census records,
|
|
71
|
+
and a terminal disposition on every element.
|
|
72
|
+
|
|
73
|
+
Read `references/phases/extract.md` before dispatching anything.
|
|
74
|
+
|
|
75
|
+
Fanout unit: one agent per capability.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
migrate import reqs <batch.json>
|
|
79
|
+
migrate import elements <batch.json>
|
|
80
|
+
migrate census <record.json>
|
|
81
|
+
migrate queue add <item.md>
|
|
82
|
+
migrate phase extract --status done
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`queue add` is not optional here. An `out-of-scope` disposition's queue id
|
|
86
|
+
and a `queued` confidence's queue id are both checked by the `refs` gate, and
|
|
87
|
+
a queue id with no file behind it is a violation the moment anything checks,
|
|
88
|
+
not a future one. File each item in the same pass that names it.
|
|
89
|
+
|
|
90
|
+
The second import carries the resolved `disposition` (`mapped` or
|
|
91
|
+
`out-of-scope`); it is the only writer of a *resolved* value there, so this
|
|
92
|
+
line is the ledger write-back itself, not something the phase-status flip
|
|
93
|
+
does for you. `migrate reset --phase extract` also writes this field, but
|
|
94
|
+
only back to `unaccounted`; it clears, it does not resolve.
|
|
95
|
+
|
|
96
|
+
### 4. Parity
|
|
97
|
+
|
|
98
|
+
Produces `deltas.jsonl` and a parity plan on every requirement whose
|
|
99
|
+
confidence is not `queued`.
|
|
100
|
+
|
|
101
|
+
Read `references/phases/parity.md` before dispatching anything.
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
migrate import deltas <batch.json>
|
|
105
|
+
migrate import reqs <batch.json>
|
|
106
|
+
migrate queue add <item.md>
|
|
107
|
+
migrate phase parity --status done
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Same rule as phase 3: a `rubric` plan below `high` must carry a queue id, and
|
|
111
|
+
the `refs` gate checks it resolves, so file the item in this pass.
|
|
112
|
+
|
|
113
|
+
The second import carries the resolved `parity` value; as in extract, it is
|
|
114
|
+
the only writer of a *resolved* value, and this line is the write-back
|
|
115
|
+
itself. `migrate reset --phase parity` also writes this field, but only
|
|
116
|
+
back to `null`; it clears, it does not resolve.
|
|
117
|
+
|
|
118
|
+
### 5. Queue
|
|
119
|
+
|
|
120
|
+
Produces the queue items carrying forward anything ambiguous: evidence,
|
|
121
|
+
options, and a recommendation, filed for an owner to adjudicate.
|
|
122
|
+
|
|
123
|
+
Read `references/phases/queue.md` before dispatching anything.
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
migrate queue add <item.md>
|
|
127
|
+
migrate phase queue --status done
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 6. Adjudicate
|
|
131
|
+
|
|
132
|
+
A run stops at the queue in this version of the tool: `adjudicate` has no verb
|
|
133
|
+
yet, so nothing here can move a queue item's status past `open`. `migrate
|
|
134
|
+
status` and `migrate queue list` are the terminus; adjudication arrives with
|
|
135
|
+
its verb in the next milestone.
|
|
136
|
+
|
|
137
|
+
### 7. Handoff
|
|
138
|
+
|
|
139
|
+
`handoff` has no verb yet either, for the same reason. `migrate status` and
|
|
140
|
+
`migrate queue list` remain the terminus; handoff arrives with its verb in the
|
|
141
|
+
next milestone.
|
|
142
|
+
|
|
143
|
+
## Checking as you go
|
|
144
|
+
|
|
145
|
+
Run `migrate check --phase <current>` after every batch. It bounds the
|
|
146
|
+
run-state gate at that phase; the other nine gates always read the whole
|
|
147
|
+
store, so a coverage or census gap past your current phase still fails on its
|
|
148
|
+
own gate regardless of `--phase`.
|
|
149
|
+
|
|
150
|
+
Run plain `migrate check` only when claiming the whole migration is complete:
|
|
151
|
+
with no `--phase`, it gates every phase through `handoff`. In this version
|
|
152
|
+
that cannot pass, because `adjudicate` and `handoff` have no verbs to complete
|
|
153
|
+
them. `migrate check --phase queue` is the practical terminus for this
|
|
154
|
+
milestone; its exit 0 is what "done, for now" means.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
migrate check --phase queue
|
|
158
|
+
migrate check
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Resuming a crashed run
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
migrate status
|
|
165
|
+
migrate phase
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`migrate status` prints the last committed batch and the outstanding work;
|
|
169
|
+
`migrate phase` (no name) prints every phase's status and batch count, one
|
|
170
|
+
line each, so you can see exactly where the run stopped. To re-enter one
|
|
171
|
+
phase, clear its derived rows and re-run it:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
migrate reset --phase <phase>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Re-importing a batch upserts rows by id rather than duplicating them, so
|
|
178
|
+
progress from before the crash is not lost by retrying it.
|
|
179
|
+
|
|
180
|
+
## Aborting
|
|
181
|
+
|
|
182
|
+
There is no run directory to delete: the store lives at `.migrate/` inside the
|
|
183
|
+
target repo. `references/run-ops.md` holds the batch-checkpoint discipline (a
|
|
184
|
+
git commit after every `migrate import`); if it was followed, an aborted run
|
|
185
|
+
leaves behind exactly whatever the last commit captured. Leave `.migrate/` in
|
|
186
|
+
place either way. Discard only uncommitted scratch files, such as a
|
|
187
|
+
`batch.json` you built but never imported. `.migrate/.env`, if a runtime lens
|
|
188
|
+
created one, must never be committed regardless of how the run ends. `init`
|
|
189
|
+
takes care of the ignore entry in all three cases, and says on stdout when it
|
|
190
|
+
changed something: `init: created <path> with .migrate/.env` when the target
|
|
191
|
+
had no `.gitignore`, `init: appended .migrate/.env to <path>` when it had one
|
|
192
|
+
without the entry, and **nothing at all** when the entry was already there,
|
|
193
|
+
since there was nothing to change. Silence from `init` on this is the
|
|
194
|
+
already-correct case, not a skipped one. If you edited `.gitignore` after
|
|
195
|
+
`init` ran, check the entry is still there before committing anything, because
|
|
196
|
+
nothing re-checks it: the `leaks` gate that would catch a committed value is
|
|
197
|
+
opt-in (`migrate check --leaks`).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resolve symlinks so we find the real migrate.ts even when invoked via
|
|
3
|
+
# a /usr/local/bin or ~/.local/bin symlink.
|
|
4
|
+
SCRIPT="${BASH_SOURCE[0]}"
|
|
5
|
+
while [ -L "$SCRIPT" ]; do
|
|
6
|
+
DIR="$(cd -P "$(dirname "$SCRIPT")" && pwd)"
|
|
7
|
+
TARGET="$(readlink "$SCRIPT")"
|
|
8
|
+
if [[ "$TARGET" = /* ]]; then
|
|
9
|
+
SCRIPT="$TARGET"
|
|
10
|
+
else
|
|
11
|
+
SCRIPT="$DIR/$TARGET"
|
|
12
|
+
fi
|
|
13
|
+
done
|
|
14
|
+
DIR="$(cd -P "$(dirname "$SCRIPT")" && pwd)"
|
|
15
|
+
exec bun "$DIR/migrate.ts" "$@"
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import pkg from '../package.json' with { type: 'json' }
|
|
4
|
+
|
|
5
|
+
const VERSION = (pkg as { version: string }).version
|
|
6
|
+
|
|
7
|
+
const USAGE = `Usage: migrate <subcommand> [args]
|
|
8
|
+
|
|
9
|
+
Subcommands:
|
|
10
|
+
init --source <path> --scope <text> --name <target>
|
|
11
|
+
Write .migrate/config.toml
|
|
12
|
+
import <elements|reqs|deltas> <batch.json> [--force-unlock]
|
|
13
|
+
Validated bulk append to the store
|
|
14
|
+
census <record.json> [--force-unlock]
|
|
15
|
+
Record a lens accounting record
|
|
16
|
+
phase [<name>] [--status <s>] [--force-unlock]
|
|
17
|
+
Print phase state, or set one phase's status
|
|
18
|
+
queue add <file.md> Add a queue item
|
|
19
|
+
queue list [--open] List queue items, severity first
|
|
20
|
+
queue show <id> Print one queue item
|
|
21
|
+
check [--phase <p>] [--no-citations] [--leaks]
|
|
22
|
+
Run the gates; without --phase, exit 0 means
|
|
23
|
+
the whole migration is complete
|
|
24
|
+
status Phase state, counts, resume pointer
|
|
25
|
+
reset --phase <phase> [--force-unlock]
|
|
26
|
+
Clear one phase's derived rows
|
|
27
|
+
report [--out <dir>] Render markdown views
|
|
28
|
+
--version Print the migrate version
|
|
29
|
+
--help Show this message`
|
|
30
|
+
|
|
31
|
+
type Handler = (args: string[]) => Promise<number> | number
|
|
32
|
+
|
|
33
|
+
// A flag's value is missing, or the value slot is occupied by another flag
|
|
34
|
+
// (`init --scope --name newapp` used to read scope as the literal string
|
|
35
|
+
// "--name" and carry on), in both cases the request is malformed, not a
|
|
36
|
+
// value the command can act on. Every `--flag <value>` parser in this file
|
|
37
|
+
// shares this one check so the three no longer disagree about which of
|
|
38
|
+
// those two shapes is a usage error and which is silently tolerated.
|
|
39
|
+
function readFlag(args: string[], name: string): { value?: string; error?: string } {
|
|
40
|
+
const at = args.indexOf(name)
|
|
41
|
+
if (at === -1) return {}
|
|
42
|
+
const value = args[at + 1]
|
|
43
|
+
if (value === undefined || value.startsWith('--')) {
|
|
44
|
+
return { error: `${name} needs a value` }
|
|
45
|
+
}
|
|
46
|
+
return { value }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const HANDLERS: Record<string, Handler> = {
|
|
50
|
+
init: async (args) => {
|
|
51
|
+
const names = ['--source', '--scope', '--name', '--source-stack', '--target-stack', '--basis']
|
|
52
|
+
const values: Record<string, string | undefined> = {}
|
|
53
|
+
for (const name of names) {
|
|
54
|
+
const result = readFlag(args, name)
|
|
55
|
+
if (result.error) {
|
|
56
|
+
process.stderr.write(`init: ${result.error}\n`)
|
|
57
|
+
return 2
|
|
58
|
+
}
|
|
59
|
+
values[name] = result.value
|
|
60
|
+
}
|
|
61
|
+
const sourcePath = values['--source']
|
|
62
|
+
const scope = values['--scope']
|
|
63
|
+
const targetName = values['--name']
|
|
64
|
+
if (!sourcePath || !scope || !targetName) {
|
|
65
|
+
process.stderr.write(
|
|
66
|
+
'init: want --source <path> --scope <text> --name <target> [--source-stack <s>] [--target-stack <s>] [--basis <runnable|source-only>]\n',
|
|
67
|
+
)
|
|
68
|
+
return 2
|
|
69
|
+
}
|
|
70
|
+
const { runInit } = await import('../scripts/init-cmd.ts')
|
|
71
|
+
return runInit({
|
|
72
|
+
root: process.cwd(),
|
|
73
|
+
sourcePath,
|
|
74
|
+
scope,
|
|
75
|
+
targetName,
|
|
76
|
+
...(values['--source-stack'] ? { sourceStack: values['--source-stack'] } : {}),
|
|
77
|
+
...(values['--target-stack'] ? { targetStack: values['--target-stack'] } : {}),
|
|
78
|
+
...(values['--basis'] ? { basis: values['--basis'] } : {}),
|
|
79
|
+
})
|
|
80
|
+
},
|
|
81
|
+
import: async (args) => {
|
|
82
|
+
const kind = args[0]
|
|
83
|
+
const file = args[1]
|
|
84
|
+
if (kind !== 'elements' && kind !== 'reqs' && kind !== 'deltas') {
|
|
85
|
+
process.stderr.write('import: want <elements|reqs|deltas> <batch.json>\n')
|
|
86
|
+
return 2
|
|
87
|
+
}
|
|
88
|
+
if (!file) {
|
|
89
|
+
process.stderr.write('import: missing <batch.json>\n')
|
|
90
|
+
return 2
|
|
91
|
+
}
|
|
92
|
+
const { findStoreRoot } = await import('../scripts/paths.ts')
|
|
93
|
+
const root = await findStoreRoot(process.cwd())
|
|
94
|
+
if (!root) {
|
|
95
|
+
process.stderr.write('import: no .migrate store found above the cwd\n')
|
|
96
|
+
return 2
|
|
97
|
+
}
|
|
98
|
+
const forceUnlock = args.includes('--force-unlock')
|
|
99
|
+
const { runImport } = await import('../scripts/import-cmd.ts')
|
|
100
|
+
return runImport({
|
|
101
|
+
root,
|
|
102
|
+
kind,
|
|
103
|
+
batchFile: file,
|
|
104
|
+
...(forceUnlock ? { forceUnlock: true } : {}),
|
|
105
|
+
})
|
|
106
|
+
},
|
|
107
|
+
census: async (args) => {
|
|
108
|
+
const file = args[0]
|
|
109
|
+
if (!file) {
|
|
110
|
+
process.stderr.write('census: missing <record.json>\n')
|
|
111
|
+
return 2
|
|
112
|
+
}
|
|
113
|
+
const { findStoreRoot } = await import('../scripts/paths.ts')
|
|
114
|
+
const root = await findStoreRoot(process.cwd())
|
|
115
|
+
if (!root) {
|
|
116
|
+
process.stderr.write('census: no .migrate store found above the cwd\n')
|
|
117
|
+
return 2
|
|
118
|
+
}
|
|
119
|
+
const forceUnlock = args.includes('--force-unlock')
|
|
120
|
+
const { runCensus } = await import('../scripts/census-cmd.ts')
|
|
121
|
+
return runCensus({ root, file, ...(forceUnlock ? { forceUnlock: true } : {}) })
|
|
122
|
+
},
|
|
123
|
+
phase: async (args) => {
|
|
124
|
+
const status = readFlag(args, '--status')
|
|
125
|
+
if (status.error) {
|
|
126
|
+
process.stderr.write(`phase: ${status.error}\n`)
|
|
127
|
+
return 2
|
|
128
|
+
}
|
|
129
|
+
// args[0] is the phase name only when it is a positional. A flag sitting
|
|
130
|
+
// in that slot (`phase --force-unlock enumerate --status done`) leaves the
|
|
131
|
+
// name undefined, which used to silently downgrade a write to a read:
|
|
132
|
+
// every phase's line printed, exit 0, nothing moved. `import` and `census`
|
|
133
|
+
// both reject the identical flag-ordering mistake at 2, because their
|
|
134
|
+
// first positional is load-bearing too. Any write-intent flag with no
|
|
135
|
+
// phase name to apply it to is that same mistake, so it is refused here
|
|
136
|
+
// rather than serviced as a listing nobody asked for.
|
|
137
|
+
const name = args[0]?.startsWith('--') ? undefined : args[0]
|
|
138
|
+
if (!name && (status.value !== undefined || args.includes('--force-unlock'))) {
|
|
139
|
+
process.stderr.write(
|
|
140
|
+
'phase: want <name> before --status/--force-unlock, as in `phase enumerate --status done`\n',
|
|
141
|
+
)
|
|
142
|
+
return 2
|
|
143
|
+
}
|
|
144
|
+
const { findStoreRoot } = await import('../scripts/paths.ts')
|
|
145
|
+
const root = await findStoreRoot(process.cwd())
|
|
146
|
+
if (!root) {
|
|
147
|
+
process.stderr.write('phase: no .migrate store found above the cwd\n')
|
|
148
|
+
return 2
|
|
149
|
+
}
|
|
150
|
+
const forceUnlock = args.includes('--force-unlock')
|
|
151
|
+
const { runPhase } = await import('../scripts/phase-cmd.ts')
|
|
152
|
+
return runPhase({
|
|
153
|
+
root,
|
|
154
|
+
...(name ? { name } : {}),
|
|
155
|
+
...(status.value ? { status: status.value } : {}),
|
|
156
|
+
...(forceUnlock ? { forceUnlock: true } : {}),
|
|
157
|
+
})
|
|
158
|
+
},
|
|
159
|
+
queue: async (args) => {
|
|
160
|
+
const { findStoreRoot } = await import('../scripts/paths.ts')
|
|
161
|
+
const root = await findStoreRoot(process.cwd())
|
|
162
|
+
if (!root) {
|
|
163
|
+
process.stderr.write('queue: no .migrate store found above the cwd\n')
|
|
164
|
+
return 2
|
|
165
|
+
}
|
|
166
|
+
const { runQueue } = await import('../scripts/queue-cmd.ts')
|
|
167
|
+
return runQueue({ root, args })
|
|
168
|
+
},
|
|
169
|
+
check: async (args) => {
|
|
170
|
+
const phase = readFlag(args, '--phase')
|
|
171
|
+
if (phase.error) {
|
|
172
|
+
process.stderr.write(`check: ${phase.error}\n`)
|
|
173
|
+
return 2
|
|
174
|
+
}
|
|
175
|
+
const { isPhase, PHASES } = await import('../scripts/phases.ts')
|
|
176
|
+
if (phase.value && !isPhase(phase.value)) {
|
|
177
|
+
process.stderr.write(
|
|
178
|
+
`check: unknown phase ${phase.value}; want one of ${PHASES.join(', ')}\n`,
|
|
179
|
+
)
|
|
180
|
+
return 2
|
|
181
|
+
}
|
|
182
|
+
const { findStoreRoot } = await import('../scripts/paths.ts')
|
|
183
|
+
const root = await findStoreRoot(process.cwd())
|
|
184
|
+
if (!root) {
|
|
185
|
+
process.stderr.write('check: no .migrate store found above the cwd\n')
|
|
186
|
+
return 2
|
|
187
|
+
}
|
|
188
|
+
const { runCheckCmd } = await import('../scripts/check-cmd.ts')
|
|
189
|
+
return runCheckCmd({
|
|
190
|
+
root,
|
|
191
|
+
// --citations is accepted and ignored: citations are on by default now,
|
|
192
|
+
// and silently rejecting the old flag would break every invocation
|
|
193
|
+
// written against Milestone 1.
|
|
194
|
+
citations: !args.includes('--no-citations'),
|
|
195
|
+
leaks: args.includes('--leaks'),
|
|
196
|
+
...(phase.value && isPhase(phase.value) ? { phase: phase.value } : {}),
|
|
197
|
+
})
|
|
198
|
+
},
|
|
199
|
+
status: async () => {
|
|
200
|
+
const { findStoreRoot } = await import('../scripts/paths.ts')
|
|
201
|
+
const root = await findStoreRoot(process.cwd())
|
|
202
|
+
if (!root) {
|
|
203
|
+
process.stderr.write('status: no .migrate store found above the cwd\n')
|
|
204
|
+
return 2
|
|
205
|
+
}
|
|
206
|
+
const { runStatus } = await import('../scripts/status-cmd.ts')
|
|
207
|
+
return runStatus({ root })
|
|
208
|
+
},
|
|
209
|
+
reset: async (args) => {
|
|
210
|
+
const result = readFlag(args, '--phase')
|
|
211
|
+
if (result.error) {
|
|
212
|
+
process.stderr.write(`reset: ${result.error}\n`)
|
|
213
|
+
return 2
|
|
214
|
+
}
|
|
215
|
+
const phase = result.value
|
|
216
|
+
if (!phase) {
|
|
217
|
+
process.stderr.write('reset: missing --phase <phase>\n')
|
|
218
|
+
return 2
|
|
219
|
+
}
|
|
220
|
+
const { findStoreRoot } = await import('../scripts/paths.ts')
|
|
221
|
+
const root = await findStoreRoot(process.cwd())
|
|
222
|
+
if (!root) {
|
|
223
|
+
process.stderr.write('reset: no .migrate store found above the cwd\n')
|
|
224
|
+
return 2
|
|
225
|
+
}
|
|
226
|
+
const forceUnlock = args.includes('--force-unlock')
|
|
227
|
+
const { runReset } = await import('../scripts/reset-cmd.ts')
|
|
228
|
+
return runReset({ root, phase, ...(forceUnlock ? { forceUnlock: true } : {}) })
|
|
229
|
+
},
|
|
230
|
+
report: async (args) => {
|
|
231
|
+
const result = readFlag(args, '--out')
|
|
232
|
+
if (result.error) {
|
|
233
|
+
process.stderr.write(`report: ${result.error}\n`)
|
|
234
|
+
return 2
|
|
235
|
+
}
|
|
236
|
+
const outDir = result.value
|
|
237
|
+
const { findStoreRoot } = await import('../scripts/paths.ts')
|
|
238
|
+
const root = await findStoreRoot(process.cwd())
|
|
239
|
+
if (!root) {
|
|
240
|
+
process.stderr.write('report: no .migrate store found above the cwd\n')
|
|
241
|
+
return 2
|
|
242
|
+
}
|
|
243
|
+
const { runReport } = await import('../scripts/report-cmd.ts')
|
|
244
|
+
return runReport({ root, ...(outDir ? { outDir } : {}) })
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// A handler's rejection reaching the guard below is not guaranteed to be an
|
|
249
|
+
// Error: `(e as Error).message` on a non-Error rejection prints the useless
|
|
250
|
+
// "sub: undefined", and on a rejected `null` or `undefined` it throws inside
|
|
251
|
+
// the catch itself -- the one shape that would otherwise still escape this
|
|
252
|
+
// guard uncaught. Every other value (a string, a plain object, an Error)
|
|
253
|
+
// converts to a message without throwing.
|
|
254
|
+
export function errorMessage(e: unknown): string {
|
|
255
|
+
return e instanceof Error ? e.message : String(e)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function main(argv: string[]): Promise<number> {
|
|
259
|
+
const [sub, ...rest] = argv
|
|
260
|
+
if (sub === '--help' || sub === '-h') {
|
|
261
|
+
process.stdout.write(`${USAGE}\n`)
|
|
262
|
+
return 0
|
|
263
|
+
}
|
|
264
|
+
if (sub === '--version' || sub === '-V') {
|
|
265
|
+
process.stdout.write(`migrate ${VERSION}\n`)
|
|
266
|
+
return 0
|
|
267
|
+
}
|
|
268
|
+
if (!sub) {
|
|
269
|
+
process.stderr.write(`${USAGE}\n`)
|
|
270
|
+
return 2
|
|
271
|
+
}
|
|
272
|
+
const handler = HANDLERS[sub]
|
|
273
|
+
if (!handler) {
|
|
274
|
+
process.stderr.write(`Unknown subcommand: ${sub}\n${USAGE}\n`)
|
|
275
|
+
return 2
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
return await handler(rest)
|
|
279
|
+
} catch (e) {
|
|
280
|
+
// Every failure a handler recognizes already returns its own exit code
|
|
281
|
+
// without throwing (see import-cmd.ts, census-cmd.ts, queue-cmd.ts,
|
|
282
|
+
// check-cmd.ts). An Error reaching here is one none of them classified,
|
|
283
|
+
// and in this codebase that is dominated by one shape: loadConfig
|
|
284
|
+
// throwing because config.toml is missing or malformed, unguarded at
|
|
285
|
+
// its import-cmd.ts, census-cmd.ts, queue-cmd.ts and check.ts call
|
|
286
|
+
// sites, or an equivalent case where the store itself cannot be read
|
|
287
|
+
// (a corrupt phases.json, a malformed row in an existing store file).
|
|
288
|
+
// Every one of those means the request could never have been serviced
|
|
289
|
+
// as posed, not that a well-formed request turned up a bad answer, so
|
|
290
|
+
// it takes the same code already used a few lines up for "no .migrate
|
|
291
|
+
// store found above the cwd" and, in every handler, for the
|
|
292
|
+
// assertNotUnderSource containment refusal: 2, not 1. The message
|
|
293
|
+
// printed is the Error's own message (never a generic replacement, so a
|
|
294
|
+
// genuine bug is still visible), prefixed the way every handler prefixes
|
|
295
|
+
// its own diagnostics, and nothing else: no stack trace.
|
|
296
|
+
process.stderr.write(`${sub}: ${errorMessage(e)}\n`)
|
|
297
|
+
return 2
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Guarded so this module can be imported (e.g. by tests, to exercise `main`
|
|
302
|
+
// and `errorMessage` directly) without re-running the CLI against the
|
|
303
|
+
// importing process's own argv and calling process.exit out from under it.
|
|
304
|
+
// Running `bun bin/migrate.ts ...` directly still takes this branch exactly
|
|
305
|
+
// as before.
|
|
306
|
+
if (import.meta.main) {
|
|
307
|
+
const code = await main(process.argv.slice(2))
|
|
308
|
+
process.exit(code)
|
|
309
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.4.6/schema.json",
|
|
3
|
+
"assist": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"actions": {
|
|
6
|
+
"source": {
|
|
7
|
+
"organizeImports": "on"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"linter": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"rules": {
|
|
14
|
+
"recommended": true,
|
|
15
|
+
"suspicious": {
|
|
16
|
+
"noArrayIndexKey": "off"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"formatter": {
|
|
21
|
+
"enabled": true,
|
|
22
|
+
"indentStyle": "space",
|
|
23
|
+
"indentWidth": 2,
|
|
24
|
+
"lineWidth": 100
|
|
25
|
+
},
|
|
26
|
+
"javascript": {
|
|
27
|
+
"formatter": {
|
|
28
|
+
"quoteStyle": "single",
|
|
29
|
+
"semicolons": "asNeeded"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": {
|
|
33
|
+
"includes": ["bin/**", "scripts/**"]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "migrate",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/bun": "^1.2.0",
|
|
9
|
+
"typescript": "^5.9.0",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
"packages": {
|
|
14
|
+
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
|
15
|
+
|
|
16
|
+
"@types/node": ["@types/node@26.1.2", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="],
|
|
17
|
+
|
|
18
|
+
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
|
19
|
+
|
|
20
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
21
|
+
|
|
22
|
+
"undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
|
23
|
+
}
|
|
24
|
+
}
|