@llm4ts/shell 2.51.0 → 2.52.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.
@@ -5,6 +5,8 @@
5
5
  // llm4ts run epic-stories --repo ~/demo/portal -- --land # land the finished epic on main
6
6
  // llm4ts run epic-stories --repo ~/demo/portal -- --list # the repository's epics
7
7
  // llm4ts run epic-stories --repo ~/demo/portal -- --epic <id> # resume one, no text needed
8
+ // llm4ts run epic-stories --repo ~/demo/portal -- --defer-tests "…" # missing tests become debt
9
+ // llm4ts run epic-stories --repo ~/demo/portal -- --harden --epic <id> # plan that debt as a round
8
10
  //
9
11
  // The reasoning seat (LLM4TS_REASONER, default claude) splits the epic,
10
12
  // reviews every task and judges every story; the coder seat (LLM4TS_CODER,
@@ -21,6 +23,10 @@
21
23
  // Transcripts (what each seat was told and answered) are ON by default for
22
24
  // this flow — LLM4TS_TRANSCRIPT=off turns them off — and --land compacts them
23
25
  // (shape kept, content removed) rather than deleting them (ADR 0025).
26
+ // With --defer-tests the judge's tests score is never a finding and what it
27
+ // excused is written to debt.md; --harden plans that debt (ADR 0045).
28
+ import * as Effect from "effect/Effect";
24
29
  import { runFlowMain } from "@llm4ts/runner";
25
- import { rubricStoryJudge, runEpicStories } from "./lib/epic-stories.js";
26
- runFlowMain(runEpicStories({ storyJudge: rubricStoryJudge }));
30
+ import { epicStoriesOptions, parseStoriesFlags } from "./lib/debt.js";
31
+ import { runEpicStories } from "./lib/epic-stories.js";
32
+ runFlowMain(Effect.flatMap(parseStoriesFlags(process.argv.slice(2), process.env), (flags) => runEpicStories({ ...epicStoriesOptions(flags), argv: flags.argv })));
@@ -0,0 +1,132 @@
1
+ // Debt an epic run deferred and a later round pays (ADR 0043, ADR 0045):
2
+ // `debt.md` per unit, `debt.paid.md` once a hardening round settles, and
3
+ // the `--harden` policy both epic-draft and epic-stories use.
4
+ import { join } from "node:path";
5
+ import * as Effect from "effect/Effect";
6
+ import { Info } from "@llm4ts/flow/FlowEvents";
7
+ import { countNotPlanned } from "@llm4ts/flow/RefineRound";
8
+ import { lastStoryVerdict } from "@llm4ts/flow/Stories";
9
+ import { ScriptUsage } from "@llm4ts/runner";
10
+ import { deferringTestsJudge, epicUsage, loadRounds, rubricStoryJudge, unitStateDirs } from "./epic-stories.js";
11
+ // ---- The debt ---------------------------------------------------------------
12
+ export const counted = (count, one, many) => `${count} ${count === 1 ? one : many}`;
13
+ /** What a unit deferred: the tests owed, the findings, the test gate it skipped. */
14
+ export const debtPath = (stateDir) => join(stateDir, "debt.md");
15
+ /** Debt a settled hardening round consumed. */
16
+ export const paidPath = (stateDir) => join(stateDir, "debt.paid.md");
17
+ // ---- Tests the judge excused (ADR 0045) -------------------------------------
18
+ /**
19
+ * After an epic-stories unit run with `--defer-tests`: `debt.md` with one
20
+ * section per merged story whose last verdict scored `tests` below the bar,
21
+ * each acceptance outcome a test owed. A story the plan exempts from tests,
22
+ * or one a hardening round paid, owes nothing. Deferred findings are left
23
+ * out: `--defer-findings` already plans them as their own round.
24
+ */
25
+ export const testsDebtAfterUnit = (unit, report) => Effect.gen(function* () {
26
+ const paid = new Set(((yield* unit.files.read(paidPath(unit.stateDir))) ?? "")
27
+ .split("\n")
28
+ .flatMap((line) => (line.startsWith("## ") ? [line.slice(3).trim()] : [])));
29
+ const sections = [];
30
+ let owed = 0;
31
+ let stories = 0;
32
+ for (const story of unit.plan.stories) {
33
+ const merged = report.stories.some((outcome) => outcome.id === story.id && outcome.status === "done");
34
+ if (!merged || story.testsDeferred !== undefined || paid.has(story.id)) {
35
+ continue;
36
+ }
37
+ const verdict = yield* lastStoryVerdict(unit.files, unit.stateDir, story.id);
38
+ const tests = verdict?.excused?.find((dimension) => dimension.id === "tests");
39
+ if (tests === undefined) {
40
+ continue;
41
+ }
42
+ owed += story.acceptance.length;
43
+ stories += 1;
44
+ sections.push(`## ${story.id}`, "", `judge scored tests ${tests.score}/${tests.max}: ${tests.reasoning}`, "", ...story.acceptance.map((outcome) => `- [ ] test: ${outcome}`), "");
45
+ }
46
+ if (stories === 0) {
47
+ return;
48
+ }
49
+ const path = debtPath(unit.stateDir);
50
+ yield* unit.files.writeAtomic(path, [`# Debt — ${unit.plan.epicId}`, "", ...sections].join("\n"));
51
+ yield* unit.events.publish(Info.make({
52
+ message: `debt: ${counted(owed, "test owed", "tests owed")} by ${counted(stories, "story", "stories")} — read it, then run epic-stories --harden (${path})`
53
+ }));
54
+ });
55
+ // ---- Hardening --------------------------------------------------------------
56
+ /** The open debt lists of an epic, by state folder; empty ones are paid. */
57
+ export const openDebt = (files, epicStateDir) => Effect.gen(function* () {
58
+ const open = [];
59
+ for (const dir of yield* unitStateDirs(files, epicStateDir)) {
60
+ const text = (yield* files.read(debtPath(dir)))?.trim() ?? "";
61
+ if (text.length > 0) {
62
+ open.push({ dir, text });
63
+ }
64
+ }
65
+ return open;
66
+ });
67
+ /** `--harden`: the text given, then every open debt list, as the round to plan. */
68
+ export const hardenFeedback = (epicStateDir, text, files) => Effect.gen(function* () {
69
+ // An open round finishes before another is planned: with text, runAction would refuse it.
70
+ const rounds = yield* loadRounds(files, epicStateDir).pipe(Effect.catch(() => Effect.succeed([])));
71
+ const last = rounds.at(-1);
72
+ if (last?.plan !== undefined && last.merged < last.plan.stories.length) {
73
+ return text;
74
+ }
75
+ const open = yield* openDebt(files, epicStateDir);
76
+ return open.length === 0
77
+ ? text
78
+ : [
79
+ "Pay this epic's debt: write the tests owed as deterministic tests in the house",
80
+ "style, make the test gate green, and close the findings that still apply.",
81
+ ...(text.length === 0 ? [] : ["", text]),
82
+ "",
83
+ ...open.flatMap((debt) => [debt.text, ""])
84
+ ].join("\n");
85
+ });
86
+ /** After a hardening round that settled (nothing failed or waiting), its debt is paid. */
87
+ export const hardenAfterUnit = (unit, report) => Effect.gen(function* () {
88
+ if (report.count("failed") > 0 || report.count("waiting") > 0) {
89
+ return;
90
+ }
91
+ for (const debt of yield* openDebt(unit.files, unit.epicStateDir)) {
92
+ const paid = (yield* unit.files.read(paidPath(debt.dir))) ?? "";
93
+ yield* unit.files.writeAtomic(paidPath(debt.dir), `${paid}${debt.text}\n`);
94
+ yield* unit.files.writeAtomic(debtPath(debt.dir), "");
95
+ }
96
+ // What the round's planner left out is listed, never carried (ADR 0031 decision 6).
97
+ const notPlannedPath = join(unit.stateDir, "not-planned.md");
98
+ const left = countNotPlanned((yield* unit.files.read(notPlannedPath)) ?? "");
99
+ if (left > 0) {
100
+ yield* unit.events.publish(Info.make({
101
+ message: `harden: ${counted(left, "debt item was", "debt items were")} not planned (${notPlannedPath}); pass them to --harden as text if they still matter`
102
+ }));
103
+ }
104
+ });
105
+ /** `--harden`: the open debt planned as a round under epic-stories' full bar. */
106
+ export const hardenOptions = {
107
+ storyJudge: rubricStoryJudge,
108
+ refineFeedback: hardenFeedback,
109
+ afterUnit: hardenAfterUnit
110
+ };
111
+ export const parseStoriesFlags = (argv, environment) => Effect.gen(function* () {
112
+ const deferTests = argv.includes("--defer-tests") ||
113
+ /^(1|true|on|yes)$/i.test(environment.LLM4TS_DEFER_TESTS?.trim() ?? "");
114
+ const harden = argv.includes("--harden");
115
+ if (deferTests && harden) {
116
+ return yield* ScriptUsage.make({
117
+ message: `--harden pays the debt under the full bar; it does not take --defer-tests (or LLM4TS_DEFER_TESTS)\n${epicUsage}`
118
+ });
119
+ }
120
+ const rest = argv.filter((arg) => arg !== "--defer-tests" && arg !== "--harden");
121
+ return { deferTests, harden, argv: harden ? ["--refine", ...rest] : rest };
122
+ });
123
+ /** epic-stories' policy for its flags: the rubric judge, excusing tests, or hardening. */
124
+ export const epicStoriesOptions = (flags) => flags.harden
125
+ ? hardenOptions
126
+ : flags.deferTests
127
+ ? {
128
+ storyJudge: deferringTestsJudge,
129
+ stories: () => ({ judgeBar: "defer-tests" }),
130
+ afterUnit: testsDebtAfterUnit
131
+ }
132
+ : { storyJudge: rubricStoryJudge };
@@ -2,16 +2,15 @@
2
2
  // deferred, not dropped. A draft run plans and builds without tests, under
3
3
  // one review lens and the build gates; what it deferred is written to
4
4
  // debt.md, and `--harden` plans that debt as a round under the full bar.
5
- import { join } from "node:path";
6
5
  import * as Effect from "effect/Effect";
7
6
  import { Info } from "@llm4ts/flow/FlowEvents";
8
7
  import { failingLinesOf } from "@llm4ts/flow/GateTriage";
9
- import { countNotPlanned } from "@llm4ts/flow/RefineRound";
10
8
  import { correctnessReviewer } from "@llm4ts/flow/Review";
11
9
  import { deferredPath, EpicReport } from "@llm4ts/flow/Stories";
12
10
  import { StoryPlan } from "@llm4ts/flow/StoryPlan";
13
11
  import { structuredAndPublish } from "@llm4ts/flow/Usage";
14
- import { loadRounds, rubricStoryJudge, storyPlanInstructions, storyPlanJsonSchema, unitStateDirs, withoutTestGate } from "./epic-stories.js";
12
+ import { counted, debtPath, hardenOptions, paidPath } from "./debt.js";
13
+ import { storyPlanInstructions, storyPlanJsonSchema, withoutTestGate } from "./epic-stories.js";
15
14
  export const parseDraftFlags = (argv) => {
16
15
  const harden = argv.includes("--harden");
17
16
  const withTests = argv.includes("--with-tests");
@@ -36,10 +35,8 @@ export const generateDraftPlan = (inputs) => structuredAndPublish(inputs.reasoni
36
35
  // The id and the epic text are ours, whatever the model echoed back.
37
36
  Effect.map((plan) => StoryPlan.make({ ...plan, epicId: inputs.epicId, epic: inputs.epic })));
38
37
  // ---- The debt ---------------------------------------------------------------
39
- /** What a draft unit deferred: the tests owed, the findings, the skipped test gate. */
40
- export const debtPath = (stateDir) => join(stateDir, "debt.md");
41
- /** Debt a settled hardening round consumed. */
42
- export const paidPath = (stateDir) => join(stateDir, "debt.paid.md");
38
+ // The debt files and `--harden` live in debt.ts, shared with epic-stories (ADR 0045).
39
+ export { debtPath, hardenAfterUnit, hardenFeedback, paidPath } from "./debt.js";
43
40
  const merged = (plan, report) => plan.stories.filter((story) => report.stories.some((outcome) => outcome.id === story.id && outcome.status === "done"));
44
41
  export const renderDebt = (plan, report, deferred, testFailures) => [
45
42
  `# Debt — ${plan.epicId}`,
@@ -55,7 +52,6 @@ export const renderDebt = (plan, report, deferred, testFailures) => [
55
52
  ? []
56
53
  : ["## Test gate on the epic checkout (skipped while drafting)", "", testFailures, ""])
57
54
  ].join("\n");
58
- const counted = (count, one, many) => `${count} ${count === 1 ? one : many}`;
59
55
  /**
60
56
  * After a draft unit: `debt.md` with the merged stories' owed tests, their
61
57
  * deferred findings and, when the draft skipped it, one run of the test gate
@@ -98,67 +94,13 @@ export const draftAfterUnit = (withTests) => (unit, report) => Effect.gen(functi
98
94
  : counted(findings.length, "deferred finding", "deferred findings")}${testFailures === undefined ? "" : ", test gate red"} — run epic-draft --harden (${path})`
99
95
  }));
100
96
  });
101
- // ---- Hardening --------------------------------------------------------------
102
- /** The open debt lists of an epic, by state folder; empty ones are paid. */
103
- const openDebt = (files, epicStateDir) => Effect.gen(function* () {
104
- const open = [];
105
- for (const dir of yield* unitStateDirs(files, epicStateDir)) {
106
- const text = (yield* files.read(debtPath(dir)))?.trim() ?? "";
107
- if (text.length > 0) {
108
- open.push({ dir, text });
109
- }
110
- }
111
- return open;
112
- });
113
- /** `--harden`: the text given, then every open debt list, as the round to plan. */
114
- export const hardenFeedback = (epicStateDir, text, files) => Effect.gen(function* () {
115
- // An open round finishes before another is planned: with text, runAction would refuse it.
116
- const rounds = yield* loadRounds(files, epicStateDir).pipe(Effect.catch(() => Effect.succeed([])));
117
- const last = rounds.at(-1);
118
- if (last?.plan !== undefined && last.merged < last.plan.stories.length) {
119
- return text;
120
- }
121
- const open = yield* openDebt(files, epicStateDir);
122
- return open.length === 0
123
- ? text
124
- : [
125
- "Pay this epic's draft debt: write the tests owed as deterministic tests in the house",
126
- "style, make the test gate green, and close the findings that still apply.",
127
- ...(text.length === 0 ? [] : ["", text]),
128
- "",
129
- ...open.flatMap((debt) => [debt.text, ""])
130
- ].join("\n");
131
- });
132
- /** After a hardening round that settled (nothing failed or waiting), its debt is paid. */
133
- export const hardenAfterUnit = (unit, report) => Effect.gen(function* () {
134
- if (report.count("failed") > 0 || report.count("waiting") > 0) {
135
- return;
136
- }
137
- for (const debt of yield* openDebt(unit.files, unit.epicStateDir)) {
138
- const paid = (yield* unit.files.read(paidPath(debt.dir))) ?? "";
139
- yield* unit.files.writeAtomic(paidPath(debt.dir), `${paid}${debt.text}\n`);
140
- yield* unit.files.writeAtomic(debtPath(debt.dir), "");
141
- }
142
- // What the round's planner left out is listed, never carried (ADR 0031 decision 6).
143
- const notPlannedPath = join(unit.stateDir, "not-planned.md");
144
- const left = countNotPlanned((yield* unit.files.read(notPlannedPath)) ?? "");
145
- if (left > 0) {
146
- yield* unit.events.publish(Info.make({
147
- message: `harden: ${counted(left, "debt item was", "debt items were")} not planned (${notPlannedPath}); pass them to --harden as text if they still matter`
148
- }));
149
- }
150
- });
151
97
  // ---- The policy -------------------------------------------------------------
152
98
  /**
153
99
  * The epic-stories spine with epic-draft's policy (ADR 0043). A draft defers
154
100
  * tests and validation; `--harden` runs epic-stories' own bar over the debt.
155
101
  */
156
102
  export const epicDraftOptions = (flags) => flags.harden
157
- ? {
158
- storyJudge: rubricStoryJudge,
159
- refineFeedback: hardenFeedback,
160
- afterUnit: hardenAfterUnit
161
- }
103
+ ? hardenOptions
162
104
  : {
163
105
  generatePlan: generateDraftPlan,
164
106
  stories: () => ({
@@ -73,6 +73,10 @@ export const epicUsage = [
73
73
  " settle, the judge clears without them); the rest are deferred and, once",
74
74
  " every story is merged, planned and run as one refine round.",
75
75
  " LLM4TS_DEFER_FINDINGS=1 sets it too",
76
+ " --defer-tests the judge's tests score, even 0, never fails a story: the tests it",
77
+ " excused are written to debt.md (the coder is still asked for tests;",
78
+ " a red test gate still fails). LLM4TS_DEFER_TESTS=1 sets it too",
79
+ " --harden plan the epic's open debt as a refine round under the full bar",
76
80
  "Seats: LLM4TS_REASONER (claude|gemini|…, default claude) splits, reviews, judges;",
77
81
  " LLM4TS_CODER (default pi) implements; LLM4TS_REASONING_MODEL / LLM4TS_CODER_MODEL",
78
82
  " pick their models (pi: provider/model); LLM4TS_CODER_FLAGS / LLM4TS_REASONING_FLAGS",
@@ -1038,15 +1042,18 @@ export const storyDimensions = [
1038
1042
  * a partial score elsewhere is a Warning a follow-up round can close.
1039
1043
  */
1040
1044
  export const judgeSeverity = (name, score) => name === "provides" || score <= 0 ? "Critical" : "Warning";
1041
- /** The dimensions a story is held to: all of them, less `tests` when the plan deferred them. */
1042
- export const judgedDimensionsOf = (story) => storyDimensions
1045
+ /**
1046
+ * The dimensions a story is held to: all of them, less `tests` when the plan
1047
+ * deferred them or the run's bar does.
1048
+ */
1049
+ export const judgedDimensionsOf = (story, bar = {}) => storyDimensions
1043
1050
  .map((dimension) => dimension.name)
1044
- .filter((name) => story.testsDeferred === undefined || name !== "tests");
1045
- const subBar = (scored, story) => ReviewResult.make({
1051
+ .filter((name) => name !== "tests" || (story.testsDeferred === undefined && bar.deferTests !== true));
1052
+ const maxScoreOf = (name) => storyDimensions.find((dimension) => dimension.name === name)?.maxScore ?? 2;
1053
+ const subBar = (scored, story, bar) => ReviewResult.make({
1046
1054
  issues: scored.scores
1047
- .filter((score) => judgedDimensionsOf(story).includes(score.name) &&
1048
- score.score <
1049
- (storyDimensions.find((dimension) => dimension.name === score.name)?.maxScore ?? 2))
1055
+ .filter((score) => judgedDimensionsOf(story, bar).includes(score.name) &&
1056
+ score.score < maxScoreOf(score.name))
1050
1057
  .map((score) => ReviewIssue.make({
1051
1058
  severity: judgeSeverity(score.name, score.score),
1052
1059
  title: `judge[${story.id}]: ${score.name} scored ${score.score}`,
@@ -1061,6 +1068,11 @@ export const testsDeferredLines = (reason) => reason === undefined
1061
1068
  `Tests deferred by the plan: ${reason}`,
1062
1069
  "No test file is expected; do not mark any dimension down for its absence."
1063
1070
  ];
1071
+ /** What a judge is told under `--defer-tests` (ADR 0045): score tests, hold nothing else to them. */
1072
+ export const deferTestsLines = [
1073
+ "Tests may be deferred to a later round: score `tests` on what the diff shows,",
1074
+ "but do not mark any other dimension down because tests are missing."
1075
+ ];
1064
1076
  /**
1065
1077
  * What the story judge reads beside the diff: the story, and what every
1066
1078
  * story it depends on provides. Without the dependencies the judge cannot
@@ -1068,7 +1080,7 @@ export const testsDeferredLines = (reason) => reason === undefined
1068
1080
  * importing `paymentsDomain` from `payments.fake.ts`, exactly where the
1069
1081
  * payments contract story declared it.
1070
1082
  */
1071
- export const storyJudgeQuery = (story, plan, subject = "diff") => {
1083
+ export const storyJudgeQuery = (story, plan, subject = "diff", bar = {}) => {
1072
1084
  const dependencies = plan === undefined
1073
1085
  ? []
1074
1086
  : dependenciesOf(plan, story.id).flatMap((id) => {
@@ -1092,6 +1104,7 @@ export const storyJudgeQuery = (story, plan, subject = "diff") => {
1092
1104
  `Owned paths: ${story.owned.join(", ")}`,
1093
1105
  `Shared read-only: ${story.sharedReadOnly.join(", ") || "(none)"}`,
1094
1106
  ...testsDeferredLines(story.testsDeferred),
1107
+ ...(story.testsDeferred === undefined && bar.deferTests === true ? deferTestsLines : []),
1095
1108
  ...(dependencies.length === 0
1096
1109
  ? []
1097
1110
  : [
@@ -1117,19 +1130,32 @@ export const storyJudgeQuery = (story, plan, subject = "diff") => {
1117
1130
  ].join("\n");
1118
1131
  };
1119
1132
  /** The story-level judge over the branch diff, bounded by the character budget. */
1120
- export const judgeStory = (reasoning, story, diff, budget, plan, subject = "diff") => judge(reasoning, storyDimensions)
1133
+ export const judgeStory = (reasoning, story, diff, budget, plan, subject = "diff", bar = {}) => judge(reasoning, storyDimensions)
1121
1134
  .evaluate(Sample.make({
1122
- query: storyJudgeQuery(story, plan, subject),
1135
+ query: storyJudgeQuery(story, plan, subject, bar),
1123
1136
  response: cap(diff, budget).text
1124
1137
  }))
1125
- .pipe(Effect.mapError(FlowLlmError.from), Effect.map((scored) => StoryVerdict.make({
1126
- ...subBar(scored, story),
1127
- dimensions: scored.scores.map((score) => ({
1138
+ .pipe(Effect.mapError(FlowLlmError.from), Effect.map((scored) => {
1139
+ // Below the bar but not held to it: kept, with why, for the debt (ADR 0045).
1140
+ const excused = scored.scores
1141
+ .filter((score) => !judgedDimensionsOf(story, bar).includes(score.name) &&
1142
+ score.score < maxScoreOf(score.name))
1143
+ .map((score) => ({
1128
1144
  id: score.name,
1129
1145
  score: score.score,
1130
- max: storyDimensions.find((dimension) => dimension.name === score.name)?.maxScore ?? 2
1131
- }))
1132
- })));
1146
+ max: maxScoreOf(score.name),
1147
+ reasoning: score.reasoning
1148
+ }));
1149
+ return StoryVerdict.make({
1150
+ ...subBar(scored, story, bar),
1151
+ dimensions: scored.scores.map((score) => ({
1152
+ id: score.name,
1153
+ score: score.score,
1154
+ max: maxScoreOf(score.name)
1155
+ })),
1156
+ ...(excused.length === 0 ? {} : { excused })
1157
+ });
1158
+ }));
1133
1159
  // ---- App directory ----------------------------------------------------------------
1134
1160
  /** Folders never taken for the application: dependencies, build output, llm4ts state. */
1135
1161
  const ignoredAppDirs = new Set(["node_modules", "dist", "build", "out", "coverage"]);
@@ -1450,6 +1476,8 @@ export const rubricStoryJudge = (context) => (story, diff, seats, subject) => ju
1450
1476
  // The story's own seat, not the run's: its time counts for the story.
1451
1477
  // And the judge seat, never the writing reasoner: a judge reads.
1452
1478
  seats.context.roster?.forRole("judge") ?? seats.context.judge ?? seats.context.reasoning, story, diff, context.budget, context.plan, subject);
1479
+ /** The rubric judge under `--defer-tests` (ADR 0045): `tests` is scored, never a finding. */
1480
+ export const deferringTestsJudge = (context) => (story, diff, seats, subject) => judgeStory(seats.context.roster?.forRole("judge") ?? seats.context.judge ?? seats.context.reasoning, story, diff, context.budget, context.plan, subject, { deferTests: true });
1453
1481
  /** A unit's state folders, the epic's and then each planned round's (ADR 0021). */
1454
1482
  export const unitStateDirs = (files, epicStateDir) => Effect.gen(function* () {
1455
1483
  const dirs = [epicStateDir];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llm4ts/shell",
3
- "version": "2.51.0",
3
+ "version": "2.52.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",
@@ -61,9 +61,9 @@
61
61
  "dependencies": {
62
62
  "@effect/platform-node": "4.0.0",
63
63
  "@effect/platform-node-shared": "4.0.0",
64
- "@llm4ts/flow": "2.51.0",
65
- "@llm4ts/core": "2.51.0",
66
- "@llm4ts/runner": "2.51.0"
64
+ "@llm4ts/core": "2.52.0",
65
+ "@llm4ts/flow": "2.52.0",
66
+ "@llm4ts/runner": "2.52.0"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "effect": "4.0.0"