@llm4ts/shell 2.44.0 → 2.45.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/lib/epic-stories.js +83 -30
- package/kits/j2ee-nextjs/packs/j2ee-nextjs-spa/pack.md +1 -0
- package/kits/j2ee-nextjs/packs/jsp-nextjs/pack.md +1 -0
- package/kits/port/packs/scala-ts/pack.md +1 -0
- package/kits/soap-ace/fixtures/demo-bank-soap/RUNBOOK.md +5 -2
- package/kits/soap-ace/flows/soap-epic.js +1 -2
- package/package.json +4 -4
|
@@ -40,7 +40,7 @@ import { estimatedUsageOptionsFromEnv, makeEstimatedUsageMeter } from "@llm4ts/f
|
|
|
40
40
|
import { landEpic } from "@llm4ts/flow/Landing";
|
|
41
41
|
import { deferredPath, implementStoriesFlow } from "@llm4ts/flow/Stories";
|
|
42
42
|
import { validateStoryPlan } from "@llm4ts/flow/StoryPlan";
|
|
43
|
-
import { asReadOnly, nodePlainFileStore, nodeProcessExecutor, resolveFlowInput, runNode, stage, withModel } from "@llm4ts/runner";
|
|
43
|
+
import { asReadOnly, nodePlainFileStore, nodeProcessExecutor, openPack, resolveFlowInput, runNode, stage, withModel } from "@llm4ts/runner";
|
|
44
44
|
import { claude, coderIds, judgmentConnectorFromEnvironment, pi } from "@llm4ts/runner/Connectors";
|
|
45
45
|
import { FlowAborted, FlowLlmError, ReviewResult, ScriptUsage, coderFor, lintCommand } from "@llm4ts/runner";
|
|
46
46
|
export const epicUsage = [
|
|
@@ -73,9 +73,11 @@ export const epicUsage = [
|
|
|
73
73
|
"Seats: LLM4TS_REASONER (claude|gemini|…, default claude) splits, reviews, judges;",
|
|
74
74
|
" LLM4TS_CODER (default pi) implements; LLM4TS_REASONING_MODEL / LLM4TS_CODER_MODEL",
|
|
75
75
|
" pick their models (pi: provider/model); LLM4TS_CODER_FLAGS / LLM4TS_REASONING_FLAGS",
|
|
76
|
-
" add CLI flags (key=value;key=value).
|
|
77
|
-
" (
|
|
78
|
-
"
|
|
76
|
+
" add CLI flags (key=value;key=value). LLM4TS_PACK (or llm4ts run --pack) uses",
|
|
77
|
+
" that pack's gates; LLM4TS_GATES overrides them (otherwise: pnpm typecheck|lint|",
|
|
78
|
+
" test|build, each only when the app's package.json defines that script);",
|
|
79
|
+
" LLM4TS_WORKTREE_SETUP prepares each worktree (default: the pack's setup:, or",
|
|
80
|
+
" pnpm install --offline with no pack; empty: none);",
|
|
79
81
|
" LLM4TS_WORKTREE_ROOT (default: <repo>.worktrees beside the repository) holds them;",
|
|
80
82
|
" LLM4TS_APP_DIR (e.g. frontend) runs setup and gates in that subfolder; unset, a",
|
|
81
83
|
" repository without a root package.json uses its one subfolder that has one;",
|
|
@@ -1119,13 +1121,15 @@ export const inAppDir = (appDir, step) => (workDir, ...rest) => step(appDir ===
|
|
|
1119
1121
|
export const defaultWorktreeSetup = ["pnpm", "install", "--offline"];
|
|
1120
1122
|
/**
|
|
1121
1123
|
* `LLM4TS_WORKTREE_SETUP="pnpm install --offline"` (the default) prepares
|
|
1122
|
-
* each story worktree; an empty value disables the step.
|
|
1123
|
-
*
|
|
1124
|
+
* each story worktree; an empty value disables the step. Unset, a selected
|
|
1125
|
+
* pack's `setup:` is the command, and a pack without one runs none: its
|
|
1126
|
+
* target is not assumed to be a pnpm project. Offline by default: the
|
|
1127
|
+
* runbook warms the pnpm store once, and a workshop stage has no network.
|
|
1124
1128
|
*/
|
|
1125
|
-
export const worktreeSetupCommand = (environment) => {
|
|
1129
|
+
export const worktreeSetupCommand = (environment, pack) => {
|
|
1126
1130
|
const raw = environment.LLM4TS_WORKTREE_SETUP;
|
|
1127
1131
|
if (raw === undefined) {
|
|
1128
|
-
return defaultWorktreeSetup;
|
|
1132
|
+
return pack === undefined ? defaultWorktreeSetup : pack.setup;
|
|
1129
1133
|
}
|
|
1130
1134
|
const parts = raw
|
|
1131
1135
|
.trim()
|
|
@@ -1166,25 +1170,67 @@ export const appScripts = (files, appRoot) => files.read(join(appRoot, "package.
|
|
|
1166
1170
|
? Effect.succeed(undefined)
|
|
1167
1171
|
: Effect.map(Schema.decodeUnknownEffect(PackageScripts)(text), (manifest) => new Set(Object.keys(manifest.scripts ?? {})))), Effect.catch(() => Effect.succeed(undefined)));
|
|
1168
1172
|
/**
|
|
1169
|
-
* `LLM4TS_GATES="pnpm typecheck;pnpm test"`
|
|
1170
|
-
*
|
|
1171
|
-
*
|
|
1173
|
+
* `LLM4TS_GATES="pnpm typecheck;pnpm test"` wins. Otherwise `packGates` —
|
|
1174
|
+
* the selected pack's `## Gates`, in manifest order — are the commands, and
|
|
1175
|
+
* an empty list stays empty (it is not filled with the defaults). With no
|
|
1176
|
+
* pack, a default gate runs only when the application defines that script:
|
|
1177
|
+
* a Next.js app has `lint` and `build` but seldom `typecheck`, and
|
|
1172
1178
|
* `pnpm typecheck` there fails with "Command not found", not a red gate.
|
|
1173
1179
|
*/
|
|
1174
|
-
export const gateCommands = (environment, scripts) => {
|
|
1180
|
+
export const gateCommands = (environment, scripts, packGates) => {
|
|
1175
1181
|
const raw = environment.LLM4TS_GATES?.trim();
|
|
1176
|
-
if (raw
|
|
1177
|
-
return
|
|
1178
|
-
|
|
1179
|
-
|
|
1182
|
+
if (raw !== undefined && raw.length > 0) {
|
|
1183
|
+
return raw
|
|
1184
|
+
.split(";")
|
|
1185
|
+
.map((command) => command
|
|
1186
|
+
.trim()
|
|
1187
|
+
.split(/\s+/)
|
|
1188
|
+
.filter((part) => part.length > 0))
|
|
1189
|
+
.filter((command) => command.length > 0);
|
|
1180
1190
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
.filter((
|
|
1187
|
-
|
|
1191
|
+
if (packGates !== undefined) {
|
|
1192
|
+
return packGates;
|
|
1193
|
+
}
|
|
1194
|
+
return scripts === undefined
|
|
1195
|
+
? defaultGateCommands
|
|
1196
|
+
: defaultGateCommands.filter((command) => scripts.has(command[1] ?? ""));
|
|
1197
|
+
};
|
|
1198
|
+
/**
|
|
1199
|
+
* The pack `LLM4TS_PACK` names (what `llm4ts run --pack` forwards), opened
|
|
1200
|
+
* before any seat is resolved so a wrong name fails before the planner
|
|
1201
|
+
* spends anything. Unset, there is none — a pack is never implied:
|
|
1202
|
+
* `openPack` would otherwise load `cobol-springboot`, which is the
|
|
1203
|
+
* modernization flows' default, not an epic's.
|
|
1204
|
+
*/
|
|
1205
|
+
export const selectedPack = (options) => (options.environment.LLM4TS_PACK?.trim() ?? "").length === 0
|
|
1206
|
+
? Effect.succeed(undefined)
|
|
1207
|
+
: Effect.map(openPack({
|
|
1208
|
+
environment: options.environment,
|
|
1209
|
+
launchDir: options.launchDir,
|
|
1210
|
+
flowDir: options.flowDir
|
|
1211
|
+
}), (opened) => opened.pack);
|
|
1212
|
+
/** The run's gates: `LLM4TS_GATES`, else the selected pack's `## Gates`, else the defaults. */
|
|
1213
|
+
export const epicGateCommands = (options) => {
|
|
1214
|
+
const override = (options.environment.LLM4TS_GATES?.trim() ?? "").length > 0;
|
|
1215
|
+
return {
|
|
1216
|
+
commands: gateCommands(options.environment, options.scripts, options.pack === undefined
|
|
1217
|
+
? undefined
|
|
1218
|
+
: (options.pack.gateList ?? Object.values(options.pack.gates))),
|
|
1219
|
+
source: override ? "override" : options.pack === undefined ? "defaults" : "pack",
|
|
1220
|
+
...(options.pack === undefined ? {} : { packName: options.pack.name })
|
|
1221
|
+
};
|
|
1222
|
+
};
|
|
1223
|
+
/** The line the run prints before the first gate. */
|
|
1224
|
+
export const gateListMessage = (resolved) => {
|
|
1225
|
+
if (resolved.commands.length === 0) {
|
|
1226
|
+
return resolved.source === "pack"
|
|
1227
|
+
? `gates: none (pack '${resolved.packName ?? ""}' defines none; set LLM4TS_GATES)`
|
|
1228
|
+
: "gates: none (the app defines none of typecheck, lint, test, build; set LLM4TS_GATES)";
|
|
1229
|
+
}
|
|
1230
|
+
const listed = resolved.commands.map((command) => command.join(" ")).join(" · ");
|
|
1231
|
+
return resolved.source === "pack"
|
|
1232
|
+
? `gates: ${listed} (pack ${resolved.packName})`
|
|
1233
|
+
: `gates: ${listed}`;
|
|
1188
1234
|
};
|
|
1189
1235
|
/** The gate runner lives in `@llm4ts/flow/Gates` (ADR 0027); re-exported for forks and tests. */
|
|
1190
1236
|
export { gatesIn };
|
|
@@ -1421,6 +1467,12 @@ export const runEpicStories = (options) => Effect.gen(function* () {
|
|
|
1421
1467
|
? choice.request
|
|
1422
1468
|
: choice.prompt
|
|
1423
1469
|
};
|
|
1470
|
+
// The pack whose `## Gates` the run uses; a wrong name fails here, not after planning.
|
|
1471
|
+
const pack = yield* selectedPack({
|
|
1472
|
+
environment: process.env,
|
|
1473
|
+
launchDir: given.workspace,
|
|
1474
|
+
flowDir: import.meta.dirname
|
|
1475
|
+
});
|
|
1424
1476
|
const coderFlags = flagsFromEnvironment(process.env.LLM4TS_CODER_FLAGS);
|
|
1425
1477
|
const reasoning = withExtraFlags(withOptionalModel(yield* reasonerFromEnvironment(process.env), process.env.LLM4TS_REASONING_MODEL), flagsFromEnvironment(process.env.LLM4TS_REASONING_FLAGS));
|
|
1426
1478
|
const coder = withExtraFlags(withOptionalModel(yield* storyCoderFromEnvironment(process.env), process.env.LLM4TS_CODER_MODEL), coderFlags);
|
|
@@ -1571,12 +1623,13 @@ export const runEpicStories = (options) => Effect.gen(function* () {
|
|
|
1571
1623
|
message: `app dir: ${appDir} (setup and gates run there; LLM4TS_APP_DIR)`
|
|
1572
1624
|
}));
|
|
1573
1625
|
}
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1626
|
+
const resolvedGates = epicGateCommands({
|
|
1627
|
+
environment: process.env,
|
|
1628
|
+
scripts: yield* appScripts(files, join(input.workDir, appDir)),
|
|
1629
|
+
pack
|
|
1630
|
+
});
|
|
1631
|
+
const commands = resolvedGates.commands;
|
|
1632
|
+
yield* events.publish(Info.make({ message: gateListMessage(resolvedGates) }));
|
|
1580
1633
|
// The Node the gates would run on is the one on PATH, not the one
|
|
1581
1634
|
// the application pins; a mismatch is a red first story that says
|
|
1582
1635
|
// nothing about Node. Ask now, and name both.
|
|
@@ -1644,7 +1697,7 @@ export const runEpicStories = (options) => Effect.gen(function* () {
|
|
|
1644
1697
|
];
|
|
1645
1698
|
const gates = inAppDir(appDir, gatesIn(nodeProcessExecutor, events, commands, gateRunOptions(process.env)));
|
|
1646
1699
|
const testCommand = testGateCommand(commands);
|
|
1647
|
-
const setupCommand = worktreeSetupCommand(process.env);
|
|
1700
|
+
const setupCommand = worktreeSetupCommand(process.env, pack);
|
|
1648
1701
|
const healthUrl = serverHealthUrl(localServer, process.env);
|
|
1649
1702
|
// The executor over one unit: the epic's plan or one refine round.
|
|
1650
1703
|
const runUnit = (work) => implementStoriesFlow({ ...context, reasoning: reasoningMeter.service }, {
|
|
@@ -9,6 +9,7 @@ specs-dir: docs/modernization/specs
|
|
|
9
9
|
features-dir: docs/modernization/features
|
|
10
10
|
program-files: (?:src/app/<NAME>(?:/.*)?|src/services/<NAME>(?:/.*)?|contracts/<NAME>\.openapi\.yaml|tests/<NAME>\..*)
|
|
11
11
|
feature-files: (?:src/services/<NAME>(?:/.*)?|contracts/<NAME>\.openapi\.yaml)
|
|
12
|
+
setup: pnpm install --offline
|
|
12
13
|
|
|
13
14
|
## Gates
|
|
14
15
|
|
|
@@ -112,10 +112,13 @@ Review `.llm4ts/epics/<epic-id>/plan.md` in the ACE repository (edit it to
|
|
|
112
112
|
re-plan), then run the command `plan` printed:
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
llm4ts run epic-stories --pack ace12-rest --repo ~/work/conti-ace -- \
|
|
116
|
+
--concurrency 2 "<the epic sentence>"
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
The pack supplies the gates (`bash scripts/ace-gates.sh build` and `test`)
|
|
120
|
+
and declares no `setup:`, so the story worktrees skip `pnpm install`.
|
|
121
|
+
|
|
119
122
|
Each story lands on its own branch and merges into `epic/<epic-id>` after its
|
|
120
123
|
gates and judge pass; nothing reaches `main` without a human. The board and
|
|
121
124
|
report live under `.llm4ts/epics/<epic-id>/`. Azure DevOps mirroring is the
|
|
@@ -196,8 +196,7 @@ const program = Effect.gen(function* () {
|
|
|
196
196
|
}
|
|
197
197
|
yield* say([
|
|
198
198
|
"next: review the plan, commit the seed in the ACE repository, then run",
|
|
199
|
-
`
|
|
200
|
-
` llm4ts run epic-stories --repo ${targetRoot} -- --concurrency 2 ${JSON.stringify(epicSentence(file.design, api, service))}`
|
|
199
|
+
` llm4ts run epic-stories --pack ace12-rest --repo ${targetRoot} -- --concurrency 2 ${JSON.stringify(epicSentence(file.design, api, service))}`
|
|
201
200
|
].join("\n"));
|
|
202
201
|
}));
|
|
203
202
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/shell",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.45.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",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@effect/platform-node": "4.0.0",
|
|
54
54
|
"@effect/platform-node-shared": "4.0.0",
|
|
55
|
-
"@llm4ts/
|
|
56
|
-
"@llm4ts/
|
|
57
|
-
"@llm4ts/
|
|
55
|
+
"@llm4ts/flow": "2.45.0",
|
|
56
|
+
"@llm4ts/core": "2.45.0",
|
|
57
|
+
"@llm4ts/runner": "2.45.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"effect": "4.0.0"
|