@llm4ts/shell 2.24.0 → 2.26.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.
@@ -15,6 +15,7 @@ import { parseEpicBrief, renderEpicBrief, unanswered } from "@llm4ts/flow/EpicBr
15
15
  import { structuredAndPublish } from "@llm4ts/flow/Flow";
16
16
  import { EpicBriefInvalid, EpicBriefNotApproved, EpicCheckoutDirty, LandingFailed, RefineRefused } from "@llm4ts/flow/FlowError";
17
17
  import { Info } from "@llm4ts/flow/FlowEvents";
18
+ import { nodePreflight } from "@llm4ts/flow/NodePreflight";
18
19
  import { statusPaths } from "@llm4ts/flow/GitTool";
19
20
  import { EpicLanded, EpicLandedVersion, landedPath } from "@llm4ts/flow/Landing";
20
21
  import { RefineProposal, assembleRound, countNotPlanned, earlierStories, refineProposalJsonSchema, renderNotPlanned, roundPrefix, roundRepair, runAction } from "@llm4ts/flow/RefineRound";
@@ -863,7 +864,10 @@ export const storyJudgeQuery = (story, plan, subject = "diff") => {
863
864
  "the epic branch, shown as additions: judge whether the story is already in place —",
864
865
  "everything it provides exists and is tested — not the diff's size or novelty."
865
866
  ]
866
- : [])
867
+ : []),
868
+ "",
869
+ "The response holds the complete subject. Judge what it shows; do not explore the",
870
+ "repository, read other files, or run anything."
867
871
  ].join("\n");
868
872
  };
869
873
  /** The story-level judge over the branch diff, bounded by the character budget. */
@@ -1025,7 +1029,8 @@ export const storyContextChars = (environment) => {
1025
1029
  /** Today's story judge: the rubric judge over the four dimensions. */
1026
1030
  export const rubricStoryJudge = (context) => (story, diff, seats, subject) => judgeStory(
1027
1031
  // The story's own seat, not the run's: its time counts for the story.
1028
- seats.context.roster?.forRole("judge") ?? seats.context.reasoning, story, diff, context.budget, context.plan, subject);
1032
+ // And the judge seat, never the writing reasoner: a judge reads.
1033
+ seats.context.roster?.forRole("judge") ?? seats.context.judge ?? seats.context.reasoning, story, diff, context.budget, context.plan, subject);
1029
1034
  /** The runner options the two flags above add; empty when neither is set. */
1030
1035
  export const epicRunnerOptions = Effect.fn("epic-stories.runnerOptions")(function* (options, environment) {
1031
1036
  const judgment = options.judgmentFromEnvironment === true
@@ -1237,6 +1242,12 @@ export const runEpicStories = (options) => Effect.gen(function* () {
1237
1242
  ? "gates: none (the app defines none of typecheck, lint, test, build; set LLM4TS_GATES)"
1238
1243
  : `gates: ${commands.map((command) => command.join(" ")).join(" · ")}`
1239
1244
  }));
1245
+ // The Node the gates would run on is the one on PATH, not the one
1246
+ // the application pins; a mismatch is a red first story that says
1247
+ // nothing about Node. Ask now, and name both.
1248
+ if (commands.length > 0) {
1249
+ yield* nodePreflight(nodeProcessExecutor, files, events, join(input.workDir, appDir), process.env);
1250
+ }
1240
1251
  if (flags.land !== undefined) {
1241
1252
  const landed = yield* landEpic(context, {
1242
1253
  plan,
@@ -29,6 +29,7 @@ import { judgeAllPrograms } from "@llm4ts/flow/ProgramJudge";
29
29
  import { Provenance, makeProvenanceStore } from "@llm4ts/flow/Provenance";
30
30
  import { ApprovedMarker, DraftApprovalMarker, requireApproval } from "@llm4ts/flow/Approval";
31
31
  import { loadPatternCards, taggedPatternIds } from "@llm4ts/flow/Patterns";
32
+ import { nodePreflight } from "@llm4ts/flow/NodePreflight";
32
33
  import { ReviewIssue } from "@llm4ts/flow/Review";
33
34
  import { checkWall, wallBreachMessage } from "@llm4ts/flow/Wall";
34
35
  import { parseDecisions } from "@llm4ts/flow/Decisions";
@@ -194,6 +195,8 @@ const program = Effect.gen(function* () {
194
195
  message: `no plan at ${planPath} — run modernize-seed first`
195
196
  });
196
197
  }
198
+ // The pack's gates run on the node on PATH: check it against the target's pin first.
199
+ yield* nodePreflight(nodeProcessExecutor, files, context.events, input.workDir, process.env);
197
200
  const gate = (name) => {
198
201
  const command = pack.gate(name);
199
202
  return command === undefined
@@ -28,3 +28,15 @@ LLM4TS_LEGACY_REPO=/path/to/legacy-estate llm4ts run convert-page --pack j2ee-ne
28
28
 
29
29
  The convert flows default to `j2ee-nextjs-spa`; `kits/test/packs.test.ts`
30
30
  shows what the flows require of a manifest.
31
+
32
+ ## Node for the gates
33
+
34
+ The Next.js scaffold pins Node 22 (`.nvmrc`, `package.json` `engines`). The
35
+ pack's gates run on the `node` on the PATH of the shell that launches
36
+ `llm4ts`, so `convert-*`, `modernize-implement` and `epic-stories` check it
37
+ against the target's pin before the first gate and stop, naming both, when
38
+ they disagree; `llm4ts doctor` reports the same under `gates:`. Switch Node
39
+ (`nvm use` / `fnm use`), or uncomment `use-node-version` in the target's
40
+ `.npmrc` so pnpm runs the pinned Node itself, or give the pack gates a
41
+ version-manager wrapper (`fnm exec --using=.nvmrc pnpm lint`). Set
42
+ `LLM4TS_NODE_CHECK=off` to run anyway.
@@ -18,10 +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, 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):
21
+ verifies git, Node 22 on PATH, pnpm, the `claude` CLI, and that the installed
22
+ `llm4ts` ships the `j2ee-nextjs` kit with its `j2ee-nextjs-spa` pack
23
+ (`@llm4ts/shell` 0.18.0 or newer). Fix anything red before continuing. Then
24
+ materialize the demo estate (pick a short path you can type on stage):
25
25
 
26
26
  ```bash
27
27
  node kits/j2ee-nextjs/fixtures/demo-bank/reset-demo.mjs ~/demo
@@ -32,6 +32,17 @@ warms the target's node_modules from the pnpm store (documented offline
32
32
  strategy: the lockfile is committed; run it once on hotel wifi, never on
33
33
  stage). Finally: `cd ~/demo/nextjs && pnpm test` must be green.
34
34
 
35
+ **Node for the gates.** The target pins Node 22 (`.nvmrc`, `package.json`
36
+ `engines`). Gates run on the `node` found on the PATH of the shell that
37
+ launches `llm4ts`, so every gated flow checks that node against the pin
38
+ before its first gate and stops with both versions named when they
39
+ disagree (`llm4ts doctor` shows the same line under `gates:`). On a machine
40
+ whose Node you cannot switch (`nvm use 22` / `fnm use 22`), uncomment
41
+ `use-node-version` in the target's `.npmrc` so pnpm fetches and runs that
42
+ Node itself (one download, then offline), or wrap the pack's gate commands
43
+ in a version manager (`fnm exec --using=.nvmrc pnpm lint` works as a gate
44
+ line, since gates are plain argv). `LLM4TS_NODE_CHECK=off` skips the check.
45
+
35
46
  Timing bounds for the day (set in the environment you run flows from):
36
47
 
37
48
  ```bash
@@ -2,6 +2,9 @@
2
2
  "name": "demo-bank-nextjs",
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
+ "engines": {
6
+ "node": ">=22"
7
+ },
5
8
  "scripts": {
6
9
  "dev": "next dev",
7
10
  "build": "next build",
@@ -23,7 +23,12 @@ const tool = (label, command, args) =>
23
23
  check(label, () => execFileSync(command, args, { stdio: "pipe" }))
24
24
 
25
25
  tool("git available", "git", ["--version"])
26
- tool("node >= 20", "node", ["--version"])
26
+ check("node >= 22 (the fixtures pin it in .nvmrc and engines)", () => {
27
+ const version = execFileSync("node", ["--version"], { encoding: "utf8" }).trim()
28
+ if (Number(version.replace(/^v/, "").split(".")[0]) < 22) {
29
+ throw new Error(`${version} on PATH; the gates need Node 22 (nvm use 22 / fnm use 22)`)
30
+ }
31
+ })
27
32
  tool("pnpm available", "pnpm", ["--version"])
28
33
  tool("claude CLI available (the coder seat)", "claude", ["--version"])
29
34
  check("llm4ts CLI available", () => execFileSync("llm4ts", ["--version"], { stdio: "pipe" }))
@@ -14,6 +14,7 @@ import { budget, capped } from "@llm4ts/flow/Context";
14
14
  import { Decisions, parseDecisions } from "@llm4ts/flow/Decisions";
15
15
  import { navigationOrder, parseDomains } from "@llm4ts/flow/Domains";
16
16
  import { FlowEvents } from "@llm4ts/flow/FlowEvents";
17
+ import { nodePreflight } from "@llm4ts/flow/NodePreflight";
17
18
  import { openApiFor, openApiForFeature, parsePageSpec, renderPageSpec } from "@llm4ts/flow/PageSpec";
18
19
  import { loadPatternCards, matchingPatternCards } from "@llm4ts/flow/Patterns";
19
20
  import { Task } from "@llm4ts/flow/Plan";
@@ -554,6 +555,8 @@ export const setupConversion = Effect.fn("convert.setup")(function* (context, in
554
555
  };
555
556
  const legacy = yield* makeNodeWorkspace(legacyDir, workspaceLimitsFromEnv(environment, legacySourceWorkspaceLimits));
556
557
  const target = yield* makeNodeWorkspace(input.workDir);
558
+ // The pack's gates run on the node on PATH: check it against the target's pin first.
559
+ yield* nodePreflight(nodeProcessExecutor, nodePlainFileStore, context.events, input.workDir, environment);
557
560
  const opened = yield* stage(context.events, "pack", openPack({
558
561
  environment: {
559
562
  ...environment,
@@ -2,6 +2,9 @@
2
2
  "name": "meridian-spa",
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
+ "engines": {
6
+ "node": ">=22"
7
+ },
5
8
  "scripts": {
6
9
  "test": "node --test tests/*.test.js",
7
10
  "build": "next build",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llm4ts/shell",
3
- "version": "2.24.0",
3
+ "version": "2.26.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/core": "2.24.0",
56
- "@llm4ts/flow": "2.24.0",
57
- "@llm4ts/runner": "2.24.0"
55
+ "@llm4ts/core": "2.26.0",
56
+ "@llm4ts/flow": "2.26.0",
57
+ "@llm4ts/runner": "2.26.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "effect": "4.0.0"