@llm4ts/shell 0.16.0 → 0.16.1

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.
@@ -1,11 +1,12 @@
1
1
  // Split an epic into stories with a declared dependency graph, approve the plan, then implement the stories with parallel coders in per-story worktrees merged into an epic branch.
2
2
  //
3
3
  // llm4ts run epic-stories --repo ~/demo/portal "Add the current account and wire transfers"
4
- // llm4ts run epic-stories --repo ~/demo/portal --plan-only "…" # write the plan and stop
4
+ // llm4ts run epic-stories --repo ~/demo/portal -- --plan-only "…" # write the plan and stop
5
5
  //
6
6
  // The reasoning seat (LLM4TS_REASONER, default claude) splits the epic,
7
7
  // reviews every task and judges every story; the coder seat (LLM4TS_CODER,
8
- // default pi) implements. The story plan is persisted under
8
+ // default pi) implements; LLM4TS_CODER_MODEL / LLM4TS_REASONING_MODEL pick
9
+ // their models (pi: "provider/model"). The story plan is persisted under
9
10
  // .llm4ts/epics/<epic-id>/plan.md BEFORE any coder runs, and an existing
10
11
  // file wins over regeneration — editing it is the approval and the re-plan
11
12
  // path. Stories run in .llm4ts/worktrees/<story-id> under --concurrency
@@ -22,19 +23,24 @@ import { Info } from "@llm4ts/flow/FlowEvents";
22
23
  import { stage } from "@llm4ts/flow/PlanExecution";
23
24
  import { implementStoriesFlow } from "@llm4ts/flow/Stories";
24
25
  import { makeStoryPlanStore, validateStoryPlan } from "@llm4ts/flow/StoryPlan";
25
- import { asReadOnly } from "@llm4ts/runner/Connectors";
26
+ import { asReadOnly, withModel } from "@llm4ts/runner/Connectors";
26
27
  import { resolveFlowInput } from "@llm4ts/runner/FlowArgs";
27
28
  import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner";
28
29
  import { nodePlainFileStore } from "@llm4ts/runner/NodePlainFileStore";
29
30
  import { nodeProcessExecutor } from "@llm4ts/runner/NodeProcessExecutor";
30
- import { combineTotals, epicIdFor, gateCommands, gatesIn, generateStoryPlan, judgeStory, parseEpicArgs, reasonerFromEnvironment, storyCoderFromEnvironment } from "./lib/epic-stories.js";
31
+ import { combineTotals, epicIdFor, gateCommands, gatesIn, generateStoryPlan, judgeStory, parseEpicArgs, reasonerFromEnvironment, setupIn, storyCoderFromEnvironment, worktreeSetupCommand } from "./lib/epic-stories.js";
32
+ /** `LLM4TS_CODER_MODEL` / `LLM4TS_REASONING_MODEL`: pi takes `provider/model` (e.g. `openai-codex/gpt-5.5`). */
33
+ const withOptionalModel = (config, model) => {
34
+ const trimmed = model?.trim();
35
+ return trimmed === undefined || trimmed.length === 0 ? config : withModel(config, trimmed);
36
+ };
31
37
  const defaultEpic = "Add the retail customer's current account (Conto) with balance and movements, and wire " +
32
38
  "transfers (Bonifico) with beneficiary, review, SCA confirmation, and history.";
33
39
  const program = Effect.gen(function* () {
34
40
  const flags = yield* parseEpicArgs(process.argv.slice(2));
35
41
  const input = yield* resolveFlowInput(defaultEpic, flags.rest);
36
- const reasoning = yield* reasonerFromEnvironment(process.env);
37
- const coder = yield* storyCoderFromEnvironment(process.env);
42
+ const reasoning = withOptionalModel(yield* reasonerFromEnvironment(process.env), process.env.LLM4TS_REASONING_MODEL);
43
+ const coder = withOptionalModel(yield* storyCoderFromEnvironment(process.env), process.env.LLM4TS_CODER_MODEL);
38
44
  const files = nodePlainFileStore;
39
45
  const epicId = epicIdFor(input.prompt);
40
46
  const stateDir = join(input.workDir, ".llm4ts", "epics", epicId);
@@ -70,6 +76,7 @@ const program = Effect.gen(function* () {
70
76
  });
71
77
  }
72
78
  const gates = gatesIn(nodeProcessExecutor, events, gateCommands(process.env));
79
+ const setupCommand = worktreeSetupCommand(process.env);
73
80
  const report = yield* implementStoriesFlow({ ...context, reasoning: reasoningMeter.service }, {
74
81
  plan,
75
82
  files,
@@ -90,6 +97,9 @@ const program = Effect.gen(function* () {
90
97
  };
91
98
  return seats;
92
99
  }),
100
+ ...(setupCommand === undefined
101
+ ? {}
102
+ : { setup: setupIn(nodeProcessExecutor, events, setupCommand) }),
93
103
  gates,
94
104
  judge: (story, diff) => judgeStory(reasoningMeter.service, story, diff, contextBudget),
95
105
  system: (story) => Effect.succeed([
@@ -22,74 +22,185 @@ The expected split of the demo epic against the internet-banking portal fixture
22
22
  {
23
23
  "id": "accounts-contract",
24
24
  "title": "Accounts domain contract and fake routes",
25
- "description": "Declare the Accounts HttpApi in src/contracts/accounts.ts: list the customer's current accounts (id, IBAN, label, balance in cents, available balance), one account's detail, and its movements as a cursor-paged list (date, description, amount in cents, running balance, category). Implement src/contracts/accounts.fake.ts with deterministic fixture data for customer C-000123 (two accounts, at least 30 movements on the first), a reset function, and the exported accountsDomain. Run pnpm openapi and commit contracts/openapi/accounts.json. Follow src/contracts/profile.ts and profile.fake.ts exactly.",
25
+ "description": "Declare the Accounts HttpApi in src/contracts/accounts.ts: list the customer's current accounts (id, IBAN, label, balance in cents, available balance), one account's detail, and its movements as a cursor-paged list (date, description, amount in cents, running balance, category). Implement src/contracts/accounts.fake.ts with deterministic fixture data for customer C-000123 (two accounts, at least 30 movements on the first), a reset function, and the exported accountsDomain. Run pnpm openapi and commit contracts/openapi/accounts.json. Follow src/contracts/profile.ts and profile.fake.ts exactly. Add src/contracts/accounts.test.ts covering the fake routes through the typed client: list, detail, and a page of movements.",
26
26
  "dependsOn": [],
27
- "owned": ["src/contracts/accounts.ts", "src/contracts/accounts.fake.ts", "contracts/openapi/accounts.json"],
28
- "sharedReadOnly": ["src/kit", "src/contracts/profile.ts", "src/contracts/profile.fake.ts", "CONTRIBUTING.md"],
29
- "provides": ["accountsDomain from src/contracts/accounts.fake.ts", "client.accounts.list()", "client.accounts.get({ params: { accountId } })", "client.accounts.movements({ params: { accountId }, urlParams: { cursor } })"]
27
+ "owned": [
28
+ "src/contracts/accounts.ts",
29
+ "src/contracts/accounts.fake.ts",
30
+ "contracts/openapi/accounts.json",
31
+ "src/contracts/accounts.test.ts"
32
+ ],
33
+ "sharedReadOnly": [
34
+ "src/kit",
35
+ "src/contracts/profile.ts",
36
+ "src/contracts/profile.fake.ts",
37
+ "CONTRIBUTING.md"
38
+ ],
39
+ "provides": [
40
+ "accountsDomain from src/contracts/accounts.fake.ts",
41
+ "client.accounts.list()",
42
+ "client.accounts.get({ params: { accountId } })",
43
+ "client.accounts.movements({ params: { accountId }, urlParams: { cursor } })",
44
+ "src/contracts/accounts.test.ts"
45
+ ]
30
46
  },
31
47
  {
32
48
  "id": "payments-contract",
33
49
  "title": "Payments domain contract and stateful fake routes",
34
- "description": "Declare the Payments HttpApi in src/contracts/payments.ts: list saved beneficiaries (name, IBAN), create a transfer (from account id, beneficiary name, IBAN, amount in cents, description, execution date) returning a pending transfer with an id, confirm a transfer with a six-digit SCA code (any code confirms except 000000, which is refused as UnprocessableEntity), list transfers newest first with their state (pending, confirmed, refused), and get one transfer. Implement src/contracts/payments.fake.ts with an in-memory store per page session so a created then confirmed transfer appears in the list, a reset function, and the exported paymentsDomain. Run pnpm openapi and commit contracts/openapi/payments.json. Follow src/contracts/profile.ts and profile.fake.ts exactly.",
50
+ "description": "Declare the Payments HttpApi in src/contracts/payments.ts: list saved beneficiaries (name, IBAN), create a transfer (from account id, beneficiary name, IBAN, amount in cents, description, execution date) returning a pending transfer with an id, confirm a transfer with a six-digit SCA code (any code confirms except 000000, which is refused as UnprocessableEntity), list transfers newest first with their state (pending, confirmed, refused), and get one transfer. Implement src/contracts/payments.fake.ts with an in-memory store per page session so a created then confirmed transfer appears in the list, a reset function, and the exported paymentsDomain. Run pnpm openapi and commit contracts/openapi/payments.json. Follow src/contracts/profile.ts and profile.fake.ts exactly. Add src/contracts/payments.test.ts covering the fake routes through the typed client: create then confirm then list, and the refused code 000000.",
35
51
  "dependsOn": [],
36
- "owned": ["src/contracts/payments.ts", "src/contracts/payments.fake.ts", "contracts/openapi/payments.json"],
37
- "sharedReadOnly": ["src/kit", "src/contracts/profile.ts", "src/contracts/profile.fake.ts", "CONTRIBUTING.md"],
38
- "provides": ["paymentsDomain from src/contracts/payments.fake.ts", "client.payments.beneficiaries()", "client.payments.create({ payload })", "client.payments.confirm({ params: { transferId }, payload: { code } })", "client.payments.list()", "client.payments.get({ params: { transferId } })"]
52
+ "owned": [
53
+ "src/contracts/payments.ts",
54
+ "src/contracts/payments.fake.ts",
55
+ "contracts/openapi/payments.json",
56
+ "src/contracts/payments.test.ts"
57
+ ],
58
+ "sharedReadOnly": [
59
+ "src/kit",
60
+ "src/contracts/profile.ts",
61
+ "src/contracts/profile.fake.ts",
62
+ "CONTRIBUTING.md"
63
+ ],
64
+ "provides": [
65
+ "paymentsDomain from src/contracts/payments.fake.ts",
66
+ "client.payments.beneficiaries()",
67
+ "client.payments.create({ payload })",
68
+ "client.payments.confirm({ params: { transferId }, payload: { code } })",
69
+ "client.payments.list()",
70
+ "client.payments.get({ params: { transferId } })",
71
+ "src/contracts/payments.test.ts"
72
+ ]
39
73
  },
40
74
  {
41
75
  "id": "iban-field",
42
76
  "title": "IBAN input component in the kit",
43
77
  "description": "Add src/kit/iban-field.tsx exporting IbanField (a labelled input that formats the IBAN in groups of four as the customer types, keeps the compact value, and reports validity) and isValidIban (the mod-97 check over the ISO 13616 alphabet). Add src/kit/iban-field.test.tsx covering formatting and valid and invalid IBANs, using the existing Field component and theme classes only.",
44
78
  "dependsOn": [],
45
- "owned": ["src/kit/iban-field.tsx", "src/kit/iban-field.test.tsx"],
46
- "sharedReadOnly": ["src/kit/components.tsx", "src/kit/theme.css", "src/kit/i18n.tsx", "src/kit/format.ts", "CONTRIBUTING.md"],
47
- "provides": ["IbanField and isValidIban from src/kit/iban-field.tsx"]
79
+ "owned": [
80
+ "src/kit/iban-field.tsx",
81
+ "src/kit/iban-field.test.tsx"
82
+ ],
83
+ "sharedReadOnly": [
84
+ "src/kit/components.tsx",
85
+ "src/kit/theme.css",
86
+ "src/kit/i18n.tsx",
87
+ "src/kit/format.ts",
88
+ "CONTRIBUTING.md"
89
+ ],
90
+ "provides": [
91
+ "IbanField and isValidIban from src/kit/iban-field.tsx"
92
+ ]
48
93
  },
49
94
  {
50
95
  "id": "conto-overview",
51
96
  "title": "Conto: account overview screen",
52
97
  "description": "Add the feature src/features/conto/overview/ with messages.ts (English and Italian), route.tsx exporting contoOverviewFeature (id conto), and ContoScreen.tsx: an account picker across the customer's accounts, the balance and available balance as Figures, the IBAN and account details as KeyValues, loaded through accountsDomain. Add ContoScreen.test.tsx in the house style: rows and figures render in both languages against the fake transport.",
53
- "dependsOn": ["accounts-contract"],
54
- "owned": ["src/features/conto/overview"],
55
- "sharedReadOnly": ["src/kit", "src/contracts", "src/features/profilo", "CONTRIBUTING.md"],
56
- "provides": ["contoOverviewFeature from src/features/conto/overview/route.tsx", "screen id conto"]
98
+ "dependsOn": [
99
+ "accounts-contract"
100
+ ],
101
+ "owned": [
102
+ "src/features/conto/overview"
103
+ ],
104
+ "sharedReadOnly": [
105
+ "src/kit",
106
+ "src/contracts",
107
+ "src/features/profilo",
108
+ "CONTRIBUTING.md"
109
+ ],
110
+ "provides": [
111
+ "contoOverviewFeature from src/features/conto/overview/route.tsx",
112
+ "screen id conto"
113
+ ]
57
114
  },
58
115
  {
59
116
  "id": "conto-movimenti",
60
117
  "title": "Conto: movements list with filter and CSV export",
61
118
  "description": "Add the feature src/features/conto/movimenti/ with messages.ts (English and Italian), route.tsx exporting contoMovimentiFeature (id movimenti), and MovimentiScreen.tsx: the selected account's movements as a DataTable paged with usePages and LoadMore, a SelectFilter by category, and a CSV export through downloadCsv, loaded through accountsDomain. Add MovimentiScreen.test.tsx in the house style: rows render, the filter narrows them, the next page loads.",
62
- "dependsOn": ["accounts-contract"],
63
- "owned": ["src/features/conto/movimenti"],
64
- "sharedReadOnly": ["src/kit", "src/contracts", "src/features/profilo", "CONTRIBUTING.md"],
65
- "provides": ["contoMovimentiFeature from src/features/conto/movimenti/route.tsx", "screen id movimenti"]
119
+ "dependsOn": [
120
+ "accounts-contract"
121
+ ],
122
+ "owned": [
123
+ "src/features/conto/movimenti"
124
+ ],
125
+ "sharedReadOnly": [
126
+ "src/kit",
127
+ "src/contracts",
128
+ "src/features/profilo",
129
+ "CONTRIBUTING.md"
130
+ ],
131
+ "provides": [
132
+ "contoMovimentiFeature from src/features/conto/movimenti/route.tsx",
133
+ "screen id movimenti"
134
+ ]
66
135
  },
67
136
  {
68
137
  "id": "bonifico-form",
69
138
  "title": "Bonifico: new transfer with review, SCA confirmation and outcome",
70
139
  "description": "Add the feature src/features/bonifico/nuovo/ with messages.ts (English and Italian), route.tsx exporting bonificoNuovoFeature (id bonifico), and BonificoScreen.tsx: a form (from account, beneficiary picked from saved beneficiaries or typed with IbanField, amount with parseEuro, description, execution date) validated by a pure function, a review step, a six-digit SCA code step calling client.payments.confirm, and an outcome panel for confirmed and refused, all through paymentsDomain and useAction. Add BonificoScreen.test.tsx in the house style: validation blocks a bad submit, a good transfer reaches the fake and is confirmed, code 000000 shows the refusal.",
71
- "dependsOn": ["payments-contract", "iban-field"],
72
- "owned": ["src/features/bonifico/nuovo"],
73
- "sharedReadOnly": ["src/kit", "src/contracts", "src/features/profilo", "CONTRIBUTING.md"],
74
- "provides": ["bonificoNuovoFeature from src/features/bonifico/nuovo/route.tsx", "screen id bonifico"]
140
+ "dependsOn": [
141
+ "payments-contract",
142
+ "iban-field"
143
+ ],
144
+ "owned": [
145
+ "src/features/bonifico/nuovo"
146
+ ],
147
+ "sharedReadOnly": [
148
+ "src/kit",
149
+ "src/contracts",
150
+ "src/features/profilo",
151
+ "CONTRIBUTING.md"
152
+ ],
153
+ "provides": [
154
+ "bonificoNuovoFeature from src/features/bonifico/nuovo/route.tsx",
155
+ "screen id bonifico"
156
+ ]
75
157
  },
76
158
  {
77
159
  "id": "bonifici-list",
78
160
  "title": "Bonifico: transfer history with state badges and detail",
79
161
  "description": "Add the feature src/features/bonifico/elenco/ with messages.ts (English and Italian), route.tsx exporting bonificiElencoFeature (id bonifici), and BonificiScreen.tsx: the customer's transfers newest first as a DataTable with a StateBadge per state (pending, confirmed, refused mapped to tones) and a detail panel for the selected transfer, through paymentsDomain. Add BonificiScreen.test.tsx in the house style: rows render with translated states in both languages, selecting a row shows its detail.",
80
- "dependsOn": ["payments-contract"],
81
- "owned": ["src/features/bonifico/elenco"],
82
- "sharedReadOnly": ["src/kit", "src/contracts", "src/features/profilo", "CONTRIBUTING.md"],
83
- "provides": ["bonificiElencoFeature from src/features/bonifico/elenco/route.tsx", "screen id bonifici"]
162
+ "dependsOn": [
163
+ "payments-contract"
164
+ ],
165
+ "owned": [
166
+ "src/features/bonifico/elenco"
167
+ ],
168
+ "sharedReadOnly": [
169
+ "src/kit",
170
+ "src/contracts",
171
+ "src/features/profilo",
172
+ "CONTRIBUTING.md"
173
+ ],
174
+ "provides": [
175
+ "bonificiElencoFeature from src/features/bonifico/elenco/route.tsx",
176
+ "screen id bonifici"
177
+ ]
84
178
  },
85
179
  {
86
180
  "id": "home",
87
181
  "title": "Home dashboard and the composition point",
88
182
  "description": "Replace the placeholder src/features/home/ with a dashboard: the first account's balance as a Figure, the last five movements, a quick action navigating to the transfer form with useNavigate, and the three most recent transfers, through accountsDomain and paymentsDomain. Rewrite src/App.tsx so the feature list is home, conto, movimenti, bonifico, bonifici, profilo, in that order. Add HomeScreen.test.tsx in the house style: the figures and lists render, the quick action navigates.",
89
- "dependsOn": ["conto-overview", "conto-movimenti", "bonifico-form", "bonifici-list"],
90
- "owned": ["src/features/home", "src/App.tsx"],
91
- "sharedReadOnly": ["src/kit", "src/contracts", "src/features/conto", "src/features/bonifico", "src/features/profilo", "CONTRIBUTING.md"],
92
- "provides": ["the composed application with every screen reachable from the shell"]
183
+ "dependsOn": [
184
+ "conto-overview",
185
+ "conto-movimenti",
186
+ "bonifico-form",
187
+ "bonifici-list"
188
+ ],
189
+ "owned": [
190
+ "src/features/home",
191
+ "src/App.tsx"
192
+ ],
193
+ "sharedReadOnly": [
194
+ "src/kit",
195
+ "src/contracts",
196
+ "src/features/conto",
197
+ "src/features/bonifico",
198
+ "src/features/profilo",
199
+ "CONTRIBUTING.md"
200
+ ],
201
+ "provides": [
202
+ "the composed application with every screen reachable from the shell"
203
+ ]
93
204
  }
94
205
  ]
95
206
  }
@@ -7,7 +7,7 @@ import { judge } from "@llm4ts/core/eval/Judge";
7
7
  import { TokenUsage } from "@llm4ts/core/Models";
8
8
  import { cap } from "@llm4ts/flow/Context";
9
9
  import { structuredAndPublish } from "@llm4ts/flow/Flow";
10
- import { FlowLlmError } from "@llm4ts/flow/FlowError";
10
+ import { FlowAborted, FlowLlmError } from "@llm4ts/flow/FlowError";
11
11
  import { stableHash } from "@llm4ts/flow/Plan";
12
12
  import { lintCommand, mergeReviewResults, ReviewIssue, ReviewResult } from "@llm4ts/flow/Review";
13
13
  import { StoryPlan } from "@llm4ts/flow/StoryPlan";
@@ -19,7 +19,9 @@ export const epicUsage = [
19
19
  " --concurrency <n> stories implemented at once (default 3)",
20
20
  " --fail-fast stop the epic at the first failed story",
21
21
  "Seats: LLM4TS_REASONER (claude|gemini|…, default claude) splits, reviews, judges;",
22
- " LLM4TS_CODER (default pi) implements. LLM4TS_GATES overrides the gate commands."
22
+ " LLM4TS_CODER (default pi) implements; LLM4TS_REASONING_MODEL / LLM4TS_CODER_MODEL",
23
+ " pick their models (pi: provider/model). LLM4TS_GATES overrides the gate commands;",
24
+ " LLM4TS_WORKTREE_SETUP (default: pnpm install --offline) prepares each worktree."
23
25
  ].join("\n");
24
26
  /** The flow's own flags, taken out before the shared `--repo`/prompt parsing sees the rest. */
25
27
  export const parseEpicArgs = (argv) => Effect.gen(function* () {
@@ -139,6 +141,8 @@ export const storyPlanInstructions = (epicId, guidance) => [
139
141
  "- Exactly ONE story owns the composition point (`src/App.tsx`): the fan-in that depends on",
140
142
  " every screen story and wires them in.",
141
143
  "- A story fits one agent session: one screen, one contract, or one component.",
144
+ "- Every story OWNS the test files it must write (the judge asks for tests): list them",
145
+ " in `owned` explicitly — a story cannot add a test outside its owned paths.",
142
146
  `- Use exactly this epicId: "${epicId}". Copy the epic text into "epic".`,
143
147
  "",
144
148
  "Respond only with JSON:",
@@ -210,6 +214,32 @@ export const judgeStory = (reasoning, story, diff, budget) => judge(reasoning, s
210
214
  response: cap(diff, budget).text
211
215
  }))
212
216
  .pipe(Effect.mapError(FlowLlmError.from), Effect.map((scored) => subBar(scored, story)));
217
+ // ---- Worktree setup --------------------------------------------------------------
218
+ export const defaultWorktreeSetup = ["pnpm", "install", "--offline"];
219
+ /**
220
+ * `LLM4TS_WORKTREE_SETUP="pnpm install --offline"` (the default) prepares
221
+ * each story worktree; an empty value disables the step. Offline by default:
222
+ * the runbook warms the pnpm store once, and a workshop stage has no network.
223
+ */
224
+ export const worktreeSetupCommand = (environment) => {
225
+ const raw = environment.LLM4TS_WORKTREE_SETUP;
226
+ if (raw === undefined) {
227
+ return defaultWorktreeSetup;
228
+ }
229
+ const parts = raw
230
+ .trim()
231
+ .split(/\s+/)
232
+ .filter((part) => part.length > 0);
233
+ return parts.length === 0 ? undefined : parts;
234
+ };
235
+ /** Runs the setup command in a worktree; a non-zero exit fails the story with the output. */
236
+ export const setupIn = (process, events, command) => (workDir) => Effect.flatMap(lintCommand(process, events, command, workDir), (result) => result.isClean
237
+ ? Effect.void
238
+ : FlowAborted.make({
239
+ message: `worktree setup failed (${command.join(" ")}):\n${result.issues
240
+ .map((issue) => issue.description)
241
+ .join("\n")}`
242
+ }));
213
243
  // ---- Gates ---------------------------------------------------------------------
214
244
  export const defaultGateCommands = [
215
245
  ["pnpm", "typecheck"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llm4ts/shell",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Interactive shell and CLI for llm4ts: flow discovery, run-a-flow, and view",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -50,10 +50,10 @@
50
50
  "dependencies": {
51
51
  "@effect/platform-node": "4.0.0-beta.102",
52
52
  "@effect/platform-node-shared": "4.0.0-beta.102",
53
- "@llm4ts/flow": "0.16.0",
54
- "@llm4ts/core": "0.16.0",
55
- "@llm4ts/runner": "0.16.0",
56
- "@llm4ts/modernize": "0.16.0"
53
+ "@llm4ts/core": "0.16.1",
54
+ "@llm4ts/modernize": "0.16.1",
55
+ "@llm4ts/flow": "0.16.1",
56
+ "@llm4ts/runner": "0.16.1"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "effect": "4.0.0-beta.102"