@llm4ts/shell 0.18.0 → 1.0.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/flows/modernize-extract.js +30 -2
- package/kits/j2ee-nextjs/fixtures/demo-bank/RUNBOOK.md +89 -13
- package/kits/j2ee-nextjs/fixtures/demo-bank/preflight.mjs +8 -0
- package/kits/j2ee-nextjs/packs/j2ee-nextjs-spa/pack.md +1 -0
- package/kits/j2ee-nextjs/packs/j2ee-nextjs-spa/prompts/spec.md +5 -1
- package/package.json +4 -4
|
@@ -41,7 +41,7 @@ import { loadPatternCards, matchingPatternCards } from "@llm4ts/flow/Patterns";
|
|
|
41
41
|
import { legacySourceWorkspaceLimits, workspaceLimitsFromEnv } from "@llm4ts/flow/Workspace";
|
|
42
42
|
import { ReviewIssue } from "@llm4ts/flow/Review";
|
|
43
43
|
import { cachedReview } from "@llm4ts/flow/ReviewCache";
|
|
44
|
-
import {
|
|
44
|
+
import { coverageReport, coverageUnits, features, matchingFiles, specSchemaIssues } from "@llm4ts/flow/SpecChecks";
|
|
45
45
|
import { SurveyGraph, closureFor, surveyGraph } from "@llm4ts/flow/Survey";
|
|
46
46
|
import { withDraftApproval, requireApproval } from "@llm4ts/flow/Approval";
|
|
47
47
|
import { ProgramArtifacts, ProgramUnit, extractProgramsResumably, programArtifactPaths } from "@llm4ts/flow/Artifacts";
|
|
@@ -227,6 +227,12 @@ const program = Effect.gen(function* () {
|
|
|
227
227
|
message: `no source units matched the pack's programs/sources regex under ${input.workDir}`
|
|
228
228
|
});
|
|
229
229
|
}
|
|
230
|
+
const waveScope = wave === undefined || wave.length === 0
|
|
231
|
+
? undefined
|
|
232
|
+
: (() => {
|
|
233
|
+
const names = new Set(programs.map(programName));
|
|
234
|
+
return (path) => names.has(programName(path));
|
|
235
|
+
})();
|
|
230
236
|
const units = programs.map((rel) => ProgramUnit.make({ name: programName(rel), sourcePath: rel }));
|
|
231
237
|
// Pattern cards are selected deterministically from the SOURCE, never by
|
|
232
238
|
// the model: implementation later injects exactly the cards cited here.
|
|
@@ -371,8 +377,16 @@ const program = Effect.gen(function* () {
|
|
|
371
377
|
yield* rebuildIndexes;
|
|
372
378
|
const trace = (yield* files.read(join(modDirAbs, "traceability.md"))) ?? "";
|
|
373
379
|
const mapping = (yield* files.read(join(modDirAbs, "mapping.md"))) ?? "";
|
|
380
|
+
// The pack's declared spec schema is checked by code, per program,
|
|
381
|
+
// before any judge runs: a pagespec block the converter could not
|
|
382
|
+
// decode is an incomplete extraction, not a page to guess at.
|
|
383
|
+
const schemaIssues = yield* specSchemaIssues(pack.specSchema, yield* Effect.forEach(units, (unit) => Effect.map(files.read(join(modDirAbs, "specs", `${unit.name}.md`)), (markdown) => ({
|
|
384
|
+
name: unit.name,
|
|
385
|
+
markdown
|
|
386
|
+
}))));
|
|
374
387
|
const docs = ReviewResult.make({
|
|
375
388
|
issues: [
|
|
389
|
+
...schemaIssues,
|
|
376
390
|
...(trace.trim().length === 0
|
|
377
391
|
? [
|
|
378
392
|
ReviewIssue.make({
|
|
@@ -394,7 +408,21 @@ const program = Effect.gen(function* () {
|
|
|
394
408
|
],
|
|
395
409
|
summary: "docs"
|
|
396
410
|
});
|
|
397
|
-
|
|
411
|
+
// A wave-scoped run gates only the units its own programs' files
|
|
412
|
+
// capture; units of other waves (and of estate-wide descriptors such
|
|
413
|
+
// as web.xml) are left to the run without LLM4TS_WAVE that closes
|
|
414
|
+
// the estate — otherwise no wave but the last could ever clear.
|
|
415
|
+
const scoped = yield* coverageReport(repo, pack.coverage, trace, {
|
|
416
|
+
...(waveScope === undefined ? {} : { inScope: waveScope })
|
|
417
|
+
});
|
|
418
|
+
if (scoped.outOfScope.length > 0) {
|
|
419
|
+
yield* context.events.publish(Info.make({
|
|
420
|
+
message: `${scoped.outOfScope.length} uncovered unit(s) belong to other waves and do not gate ` +
|
|
421
|
+
`'${wave}' (${scoped.outOfScope.slice(0, 5).join(", ")}${scoped.outOfScope.length > 5 ? ", …" : ""}); ` +
|
|
422
|
+
"run modernize-extract without LLM4TS_WAVE after the last wave to enforce estate-wide coverage"
|
|
423
|
+
}));
|
|
424
|
+
}
|
|
425
|
+
const covered = scoped.result;
|
|
398
426
|
const wellFormed = yield* features(repo, join(ModDir, "features"));
|
|
399
427
|
// Verdicts are per program and cached per program, so they judge
|
|
400
428
|
// under the same bound as extraction; the merge is order-stable.
|
|
@@ -18,9 +18,10 @@ node kits/j2ee-nextjs/fixtures/demo-bank/preflight.mjs
|
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Preflight seeds both fixtures into a temp dir, runs their smoke checks, and
|
|
21
|
-
verifies git, pnpm,
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
verifies git, pnpm, the `claude` CLI, and that the installed `llm4ts` ships
|
|
22
|
+
the `j2ee-nextjs` kit with its `j2ee-nextjs-spa` pack (`@llm4ts/shell`
|
|
23
|
+
0.18.0 or newer). Fix anything red before continuing. Then materialize the
|
|
24
|
+
demo estate (pick a short path you can type on stage):
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
27
|
node kits/j2ee-nextjs/fixtures/demo-bank/reset-demo.mjs ~/demo
|
|
@@ -43,11 +44,26 @@ export LLM4TS_EXTRACT_CONCURRENCY=3 # pages extracted and judged at once (measur
|
|
|
43
44
|
|
|
44
45
|
`LLM4TS_PACK` is the one that matters for Act 1: it is what makes the survey
|
|
45
46
|
reason in J2EE terms (web.xml mappings, includes, forwards, ajax targets)
|
|
46
|
-
instead of the default COBOL pack's.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
instead of the default COBOL pack's. It is a bare pack name, resolved across
|
|
48
|
+
the kits `llm4ts kits` lists — the `j2ee-nextjs` kit ships it — and every
|
|
49
|
+
`llm4ts run` below accepts the same value as `--pack j2ee-nextjs-spa` if you
|
|
50
|
+
would rather have it visible in the command on stage. Prove the whole
|
|
51
|
+
selection before anyone is watching, with no model call and no cost:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
llm4ts kits
|
|
55
|
+
llm4ts run modernize-pack-check --pack j2ee-nextjs-spa --repo ~/demo/legacy-j2ee
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The check must end `check passed with 1 warning`: 34 source files, 18 JSP
|
|
59
|
+
programs, the servlet, form, ajax, include, and servlet-class rules each
|
|
60
|
+
capturing real units, and the one expected warning — the pack ships no
|
|
61
|
+
`vectors` prompt because it runs no replay phase. If it says `not found: no kit ships it`, the installed shell is
|
|
62
|
+
older than 0.18.0. Discovery needs nothing extra for the fixture or for a
|
|
63
|
+
typical client estate — only files the pack's `sources:` regex matches
|
|
64
|
+
count, and `.git`, `node_modules`, `target`, `build`, `dist` are never
|
|
65
|
+
entered. Keep these two in your back pocket for a real estate with an
|
|
66
|
+
unusual layout, never set them on stage without a reason:
|
|
51
67
|
|
|
52
68
|
```bash
|
|
53
69
|
export LLM4TS_EXCLUDE_DIRS=.git,node_modules,target,generated # replaces the pruned list
|
|
@@ -78,15 +94,31 @@ triaged out, waves proposed. Three beats, in the order the artifacts land:
|
|
|
78
94
|
then the session-backed transfer stepper last.
|
|
79
95
|
|
|
80
96
|
Say once that the prompts behind beats 2 and 3 are the pack's
|
|
81
|
-
(`kits/j2ee-nextjs/packs/j2ee-nextjs-spa/prompts/survey-*.md`), not the
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
(`kits/j2ee-nextjs/packs/j2ee-nextjs-spa/prompts/survey-*.md`), not the
|
|
98
|
+
tool's, and that the pack travels in a **kit** with its scaffold and
|
|
99
|
+
review lenses: a client with a different stack copies the kit into
|
|
100
|
+
`.llm4ts/kits/<client>/`, edits markdown, and runs `modernize-pack-check`
|
|
101
|
+
until it passes — the pipeline is untouched. Review the plan WITH the
|
|
102
|
+
audience, flip `- [x] Approved` (the human gate is the point — banks like
|
|
103
|
+
this beat).
|
|
85
104
|
|
|
86
105
|
```bash
|
|
87
106
|
LLM4TS_WAVE=wave-1 llm4ts run modernize-extract --repo ~/demo/legacy-j2ee
|
|
88
107
|
```
|
|
89
108
|
|
|
109
|
+
Act 2 opens with `accountOverview`, and the survey's plan puts that page in
|
|
110
|
+
**wave-2** (wave-1 is the navigation shell: login, header, footer, nav,
|
|
111
|
+
dashboard, profile, settings, messages, help), so extract wave-2 as well
|
|
112
|
+
before Act 2 — it is one page plus its servlet and DTO, minutes not tens of
|
|
113
|
+
minutes. Each wave gates only its own units; the pages of later waves are
|
|
114
|
+
listed as "not gating" — say so when the line scrolls past, and mention the
|
|
115
|
+
closing run without `LLM4TS_WAVE` that enforces coverage over the whole
|
|
116
|
+
estate once every wave is in.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
LLM4TS_WAVE=wave-2 llm4ts run modernize-extract --repo ~/demo/legacy-j2ee
|
|
120
|
+
```
|
|
121
|
+
|
|
90
122
|
Extraction runs three pages at once (`LLM4TS_EXTRACT_CONCURRENCY=3` from Act
|
|
91
123
|
0): the pages of a wave are independent, each lands in its own commit holding
|
|
92
124
|
only its four files, and the log interleaves — say so before it starts, then
|
|
@@ -124,6 +156,8 @@ Open `docs/conversion/migration-report.md`: per-page estimates, the remaining
|
|
|
124
156
|
estate, and the projection — then scale the projection aloud to the client's
|
|
125
157
|
real page count. Close on governance: branches await human review (no
|
|
126
158
|
auto-merge), every page has a spec, a contract, a judge verdict, and a report.
|
|
159
|
+
The kit is the deliverable the client keeps: their packs, scaffold, lenses,
|
|
160
|
+
and the lessons the review phase appends, in one directory they own.
|
|
127
161
|
|
|
128
162
|
Optional ADO mirror (decide before the workshop, never set it up live):
|
|
129
163
|
|
|
@@ -145,6 +179,7 @@ export LLM4TS_ADO_PROJECT=<project>
|
|
|
145
179
|
| Judge keeps rejecting | `LLM4TS_JUDGE_ROUNDS=1` already bounds it; the failure lands on the board with its reason — governance beat, not a crash. |
|
|
146
180
|
| `⟳ flaky … (fresh retry)` lines scrolling past — Gemini CLI's `Loop detected` / `A potential loop was detected`, an empty response, a malformed tool call | Nothing to do: the seat restarts the turn in a fresh process, up to 6 times, and the flow carries on. Say it out loud: the turn was lost, the quota and the prompt were not. Only if all 6 fail does the stage abort — rerun, it resumes. |
|
|
147
181
|
| `⟳ structured output (repair retry)` lines — `Failed to parse response as structured output` | Nothing to do: the model gets its own parse failure quoted back and is asked for the JSON alone, up to 2 more times. If the third reply still does not parse the stage aborts with the reason — rerun resumes; a schema the model can never satisfy is a pack bug, not a stage bug. |
|
|
182
|
+
| `pack 'j2ee-nextjs-spa' not found: no kit ships it` | The shell predates kits (0.18.0) or `LLM4TS_PACK` was overwritten with a path: `llm4ts kits` shows what it sees; `npm i -g @llm4ts/shell@latest`; re-export the bare name. Never happens after a green Act 0. |
|
|
148
183
|
| Survey aborts with `discovery stopped at N matching files` (a client's real estate, never the fixture) | The abort names the knobs: tighten the pack's `sources:`/`exclude:`, prune more with `LLM4TS_EXCLUDE_DIRS=.git,node_modules,generated`, or raise `LLM4TS_MAX_DISCOVER_RESULTS`. Rerun. |
|
|
149
184
|
| Everything is on fire | `node kits/j2ee-nextjs/fixtures/demo-bank/reset-demo.mjs ~/demo` and restart the act; Act 1 re-runs in minutes. |
|
|
150
185
|
|
|
@@ -152,5 +187,46 @@ export LLM4TS_ADO_PROJECT=<project>
|
|
|
152
187
|
|
|
153
188
|
| Rehearsal | Date | Act 1 | Act 2/page | Full walk | Notes |
|
|
154
189
|
| --- | --- | --- | --- | --- | --- |
|
|
155
|
-
| 1 | | | | | |
|
|
190
|
+
| 1 | 2026-09-14/15 | survey 5m47s ($1.08 est.); extract wave-1 (9 pages, concurrency 3): 11m06s extraction + 1m40s gate + fix rounds ≈ 30–45 min; extract wave-2 (1 page): 2m27s extraction + gate/fix rounds ≈ 16 min | accountOverview 44m29s: acl 12m36s, page 18m33s, tests 12m25s, verify 11s, judge 44s — judge NOT cleared | ≈ 2h30 of flow time, plus reruns | first live walk; 4 findings below, 3 fixed in 0.18.1; the demo estate was a scratch copy, the coder seat `claude` |
|
|
156
191
|
| 2 | | | | | |
|
|
192
|
+
|
|
193
|
+
### Rehearsal 1 findings (2026-09-14/15)
|
|
194
|
+
|
|
195
|
+
1. **Act 2's first page is not in wave-1.** The survey puts `accountOverview`
|
|
196
|
+
in wave-2 (wave-1 is the navigation shell). Act 1 now extracts wave-2 as
|
|
197
|
+
well (see above). Runbook fixed.
|
|
198
|
+
2. **A wave-scoped extraction could never clear its gate.** Coverage ran
|
|
199
|
+
over the whole estate, so wave-1 failed on `/accountOverview` and
|
|
200
|
+
`doTransfer` (wave-2/4 units) and burned three fix rounds — ~17 min per
|
|
201
|
+
attempt — trying to cover them. Fixed in 0.18.1: a wave gates only its
|
|
202
|
+
own units and lists the rest as not gating; one closing run without
|
|
203
|
+
`LLM4TS_WAVE` enforces estate-wide coverage.
|
|
204
|
+
3. **A malformed `pagespec` block reached `convert-page`.** The old
|
|
205
|
+
estate-wide fix round had drafted `accountOverview.md` with `apiCalls`
|
|
206
|
+
as prose strings; the judge scored it, and `convert-page` was the first
|
|
207
|
+
to reject it (`invalid page spec block ... at ["apiCalls"][0]`). Fixed in
|
|
208
|
+
0.18.1: J2EE packs declare `spec-schema: pagespec`, the extraction gate
|
|
209
|
+
decodes every block by code before the judge, and the finding states the
|
|
210
|
+
exact shape the analyst must produce (the first fix round without that
|
|
211
|
+
hint failed twice; with it, one round).
|
|
212
|
+
4. **The page spec cannot express a list response.** `apiCalls[].response`
|
|
213
|
+
is a flat list of field mappings, so the deterministic OpenAPI contract
|
|
214
|
+
flattened `accts[].curBal` into a single `currentBalance` scalar and the
|
|
215
|
+
coder built a one-balance page; the judge scored spec-compliance 0
|
|
216
|
+
against the spec's per-account table. OPEN: `PageSpec` needs a DTO
|
|
217
|
+
reference with a list/single shape on the response (and `openApiFor`
|
|
218
|
+
an array schema) before Act 2 can pass on `accountOverview`. Until then,
|
|
219
|
+
rehearse Act 2 on a single-object page, or accept the judge failure as
|
|
220
|
+
the governance beat it is (the branch, contract, tests, and gates are all
|
|
221
|
+
there; only the judge verdict blocks).
|
|
222
|
+
|
|
223
|
+
Also observed, not fixed: Claude's structured replies for page specs
|
|
224
|
+
arrived with raw control characters inside JSON strings on three of nine
|
|
225
|
+
wave-1 pages, each costing one repair retry (self-healed); the extraction
|
|
226
|
+
judge's context is narrower than the analyst's closure, so it scored
|
|
227
|
+
`faithfulness` 1 for facts the analyst legitimately read from the servlet
|
|
228
|
+
and ESB sources; `convert-page` places `layout.tsx`, `registry.ts`, and
|
|
229
|
+
`components.css` edits outside the pack's `programFiles`, so the judge sees
|
|
230
|
+
them only as a summary and returns three "cannot verify" findings. Cost
|
|
231
|
+
figures were estimates throughout; the first wave-1 attempt alone reported
|
|
232
|
+
$12.26 estimated before it was stopped.
|
|
@@ -26,6 +26,14 @@ tool("git available", "git", ["--version"])
|
|
|
26
26
|
tool("node >= 20", "node", ["--version"])
|
|
27
27
|
tool("pnpm available", "pnpm", ["--version"])
|
|
28
28
|
tool("claude CLI available (the coder seat)", "claude", ["--version"])
|
|
29
|
+
check("llm4ts CLI available", () => execFileSync("llm4ts", ["--version"], { stdio: "pipe" }))
|
|
30
|
+
check("llm4ts kits ships j2ee-nextjs/j2ee-nextjs-spa (ADR 0014)", () => {
|
|
31
|
+
const kits = JSON.parse(execFileSync("llm4ts", ["kits", "--json"], { encoding: "utf8" }))
|
|
32
|
+
const kit = kits.find((candidate) => candidate.name === "j2ee-nextjs")
|
|
33
|
+
if (kit === undefined || !kit.packs.includes("j2ee-nextjs-spa")) {
|
|
34
|
+
throw new Error("the j2ee-nextjs kit or its j2ee-nextjs-spa pack is missing — upgrade @llm4ts/shell to 0.18.0+")
|
|
35
|
+
}
|
|
36
|
+
})
|
|
29
37
|
|
|
30
38
|
check("legacy fixture seeds + smoke", () =>
|
|
31
39
|
execFileSync("node", [join(here, "smoke-legacy-j2ee.mjs")], { stdio: "pipe" })
|
|
@@ -23,7 +23,11 @@ The spec markdown for a page has two mandatory parts:
|
|
|
23
23
|
"domainName", "type" }] }],
|
|
24
24
|
"apiCalls": [{ "operation": "<domain verb, e.g. listAccounts>",
|
|
25
25
|
"method", "path", "esbService", "request": [{ "legacyName",
|
|
26
|
-
"domainName", "type" }],
|
|
26
|
+
"domainName", "type" }],
|
|
27
|
+
"response": [{ "legacyName", "domainName", "type" }] // ad-hoc object, OR
|
|
28
|
+
"responseDto": "<domainName of one of the dtos>", // the DTO returned,
|
|
29
|
+
"responseShape": "single" | "list" // a table screen is a list
|
|
30
|
+
}],
|
|
27
31
|
"navigation": { "inbound": [], "outbound": [], "steps": [] },
|
|
28
32
|
"sessionState": ["<what the session carries and why>"],
|
|
29
33
|
"openQuestions": []
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Interactive shell and CLI for llm4ts: flow discovery, run-a-flow, and view",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@effect/platform-node": "4.0.0-beta.102",
|
|
53
53
|
"@effect/platform-node-shared": "4.0.0-beta.102",
|
|
54
|
-
"@llm4ts/core": "0.
|
|
55
|
-
"@llm4ts/flow": "0.
|
|
56
|
-
"@llm4ts/runner": "0.
|
|
54
|
+
"@llm4ts/core": "1.0.0",
|
|
55
|
+
"@llm4ts/flow": "1.0.0",
|
|
56
|
+
"@llm4ts/runner": "1.0.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"effect": "4.0.0-beta.102"
|