@llm4ts/shell 2.48.0 → 2.50.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/epic-draft.js +17 -0
- package/flows/lib/epic-draft.js +171 -0
- package/flows/lib/epic-mvp.js +3 -14
- package/flows/lib/epic-stories.js +43 -10
- package/package.json +4 -4
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Working code first, tests and validation later: the planner cuts stories without tests, coders build under one review lens and the build gates, and what was deferred is written as debt that --harden plans under the full bar (ADR 0043).
|
|
2
|
+
//
|
|
3
|
+
// llm4ts run epic-draft --repo ~/demo/portal "Add the current account and wire transfers"
|
|
4
|
+
// llm4ts run epic-draft --repo ~/demo/portal -- --plan-only "…" # write the draft plan and stop
|
|
5
|
+
// llm4ts run epic-draft --repo ~/demo/portal -- --harden # write the tests owed, close findings
|
|
6
|
+
// llm4ts run epic-draft --repo ~/demo/portal -- --land # land, hardened or not
|
|
7
|
+
//
|
|
8
|
+
// Same seats, flags and environment as epic-stories, plus --with-tests to
|
|
9
|
+
// keep the test gate while drafting. After a draft run, debt.md beside the
|
|
10
|
+
// plan lists each merged story's tests owed, its deferred findings and the
|
|
11
|
+
// test gate's failures on the epic checkout; a settled --harden round moves
|
|
12
|
+
// what it paid to debt.paid.md.
|
|
13
|
+
import { runFlowMain } from "@llm4ts/runner";
|
|
14
|
+
import { epicDraftOptions, parseDraftFlags } from "./lib/epic-draft.js";
|
|
15
|
+
import { runEpicStories } from "./lib/epic-stories.js";
|
|
16
|
+
const flags = parseDraftFlags(process.argv.slice(2));
|
|
17
|
+
runFlowMain(runEpicStories({ ...epicDraftOptions(flags), argv: flags.argv }));
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// epic-draft (ADR 0043): the epic-stories spine with tests and validation
|
|
2
|
+
// deferred, not dropped. A draft run plans and builds without tests, under
|
|
3
|
+
// one review lens and the build gates; what it deferred is written to
|
|
4
|
+
// debt.md, and `--harden` plans that debt as a round under the full bar.
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import { Info } from "@llm4ts/flow/FlowEvents";
|
|
8
|
+
import { failingLinesOf } from "@llm4ts/flow/GateTriage";
|
|
9
|
+
import { countNotPlanned } from "@llm4ts/flow/RefineRound";
|
|
10
|
+
import { correctnessReviewer } from "@llm4ts/flow/Review";
|
|
11
|
+
import { deferredPath, EpicReport } from "@llm4ts/flow/Stories";
|
|
12
|
+
import { StoryPlan } from "@llm4ts/flow/StoryPlan";
|
|
13
|
+
import { structuredAndPublish } from "@llm4ts/flow/Usage";
|
|
14
|
+
import { loadRounds, rubricStoryJudge, storyPlanInstructions, storyPlanJsonSchema, unitStateDirs, withoutTestGate } from "./epic-stories.js";
|
|
15
|
+
export const parseDraftFlags = (argv) => {
|
|
16
|
+
const harden = argv.includes("--harden");
|
|
17
|
+
const withTests = argv.includes("--with-tests");
|
|
18
|
+
const rest = argv.filter((arg) => arg !== "--harden" && arg !== "--with-tests");
|
|
19
|
+
return { harden, withTests, argv: harden ? ["--refine", ...rest] : rest };
|
|
20
|
+
};
|
|
21
|
+
// ---- The draft plan ---------------------------------------------------------
|
|
22
|
+
/** The epic-stories planner's rules, then the draft cut on top. */
|
|
23
|
+
export const draftPlanInstructions = (epicId, guidance, orientation) => [
|
|
24
|
+
storyPlanInstructions(epicId, guidance, orientation),
|
|
25
|
+
"",
|
|
26
|
+
"Draft cut (this run is epic-draft: working code first, tests and validation in a later",
|
|
27
|
+
"hardening round). These rules override the ones above where they differ:",
|
|
28
|
+
"- No story writes or owns test files now; leave them out of `owned`.",
|
|
29
|
+
"- `acceptance` still lists two to six outcomes per story: each one is a test the hardening",
|
|
30
|
+
" round will write, so make each checkable by an automated test.",
|
|
31
|
+
"- Prefer fewer, thinner stories: the shortest path to working code.",
|
|
32
|
+
"- Leave `testsDeferred` out: every outcome's test is owed and planned later."
|
|
33
|
+
].join("\n");
|
|
34
|
+
/** The draft planner: the epic-stories plan under the draft cut. */
|
|
35
|
+
export const generateDraftPlan = (inputs) => structuredAndPublish(inputs.reasoning, inputs.events, `${draftPlanInstructions(inputs.epicId, inputs.guidance, inputs.orientation)}\n\nEpic:\n${inputs.brief ?? inputs.epic}`, StoryPlan, storyPlanJsonSchema).pipe(
|
|
36
|
+
// The id and the epic text are ours, whatever the model echoed back.
|
|
37
|
+
Effect.map((plan) => StoryPlan.make({ ...plan, epicId: inputs.epicId, epic: inputs.epic })));
|
|
38
|
+
// ---- 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");
|
|
43
|
+
const merged = (plan, report) => plan.stories.filter((story) => report.stories.some((outcome) => outcome.id === story.id && outcome.status === "done"));
|
|
44
|
+
export const renderDebt = (plan, report, deferred, testFailures) => [
|
|
45
|
+
`# Debt — ${plan.epicId}`,
|
|
46
|
+
"",
|
|
47
|
+
...merged(plan, report).flatMap((story) => [
|
|
48
|
+
`## ${story.id}`,
|
|
49
|
+
"",
|
|
50
|
+
...story.acceptance.map((outcome) => `- [ ] test: ${outcome}`),
|
|
51
|
+
""
|
|
52
|
+
]),
|
|
53
|
+
...(deferred === undefined ? [] : ["## Deferred findings", "", deferred, ""]),
|
|
54
|
+
...(testFailures === undefined
|
|
55
|
+
? []
|
|
56
|
+
: ["## Test gate on the epic checkout (skipped while drafting)", "", testFailures, ""])
|
|
57
|
+
].join("\n");
|
|
58
|
+
const counted = (count, one, many) => `${count} ${count === 1 ? one : many}`;
|
|
59
|
+
/**
|
|
60
|
+
* After a draft unit: `debt.md` with the merged stories' owed tests, their
|
|
61
|
+
* deferred findings and, when the draft skipped it, one run of the test gate
|
|
62
|
+
* on the epic checkout — gates with a memory would otherwise call those
|
|
63
|
+
* failures inherited and never charge a hardening story with them.
|
|
64
|
+
*/
|
|
65
|
+
export const draftAfterUnit = (withTests) => (unit, report) => Effect.gen(function* () {
|
|
66
|
+
// A story whose debt a hardening round already paid owes nothing again.
|
|
67
|
+
const paid = new Set(((yield* unit.files.read(paidPath(unit.stateDir))) ?? "")
|
|
68
|
+
.split("\n")
|
|
69
|
+
.flatMap((line) => (line.startsWith("## ") ? [line.slice(3).trim()] : [])));
|
|
70
|
+
const stories = merged(unit.plan, report).filter((story) => !paid.has(story.id));
|
|
71
|
+
const findings = [];
|
|
72
|
+
for (const story of stories) {
|
|
73
|
+
const text = yield* unit.files.read(deferredPath(unit.stateDir, story.id));
|
|
74
|
+
findings.push(...(text ?? "").split("\n").filter((line) => line.startsWith("- [")));
|
|
75
|
+
}
|
|
76
|
+
const gate = withTests || unit.testGate === undefined
|
|
77
|
+
? undefined
|
|
78
|
+
: yield* unit.testGate(unit.workDir, unit.events);
|
|
79
|
+
// The failing lines alone: a test run's whole output would crowd the planner's prompt.
|
|
80
|
+
const failing = gate === undefined || gate.issues.length === 0
|
|
81
|
+
? []
|
|
82
|
+
: failingLinesOf(gate, [unit.workDir]).map((line) => `- ${line}`);
|
|
83
|
+
const testFailures = gate === undefined || gate.issues.length === 0
|
|
84
|
+
? undefined
|
|
85
|
+
: (failing.length > 0 ? failing : gate.issues.map((issue) => `- ${issue.title}`)).join("\n");
|
|
86
|
+
const owed = stories.reduce((sum, story) => sum + story.acceptance.length, 0);
|
|
87
|
+
if (owed === 0 && findings.length === 0 && testFailures === undefined) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const path = debtPath(unit.stateDir);
|
|
91
|
+
yield* unit.files.writeAtomic(path, renderDebt(unit.plan, EpicReport.make({
|
|
92
|
+
...report,
|
|
93
|
+
stories: report.stories.filter((outcome) => !paid.has(outcome.id))
|
|
94
|
+
}), findings.length === 0 ? undefined : findings.join("\n"), testFailures));
|
|
95
|
+
yield* unit.events.publish(Info.make({
|
|
96
|
+
message: `debt: ${counted(owed, "test owed", "tests owed")}, ${findings.length === 0
|
|
97
|
+
? "no deferred findings"
|
|
98
|
+
: counted(findings.length, "deferred finding", "deferred findings")}${testFailures === undefined ? "" : ", test gate red"} — run epic-draft --harden (${path})`
|
|
99
|
+
}));
|
|
100
|
+
});
|
|
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
|
+
// ---- The policy -------------------------------------------------------------
|
|
152
|
+
/**
|
|
153
|
+
* The epic-stories spine with epic-draft's policy (ADR 0043). A draft defers
|
|
154
|
+
* tests and validation; `--harden` runs epic-stories' own bar over the debt.
|
|
155
|
+
*/
|
|
156
|
+
export const epicDraftOptions = (flags) => flags.harden
|
|
157
|
+
? {
|
|
158
|
+
storyJudge: rubricStoryJudge,
|
|
159
|
+
refineFeedback: hardenFeedback,
|
|
160
|
+
afterUnit: hardenAfterUnit
|
|
161
|
+
}
|
|
162
|
+
: {
|
|
163
|
+
generatePlan: generateDraftPlan,
|
|
164
|
+
stories: () => ({
|
|
165
|
+
reviewers: [correctnessReviewer],
|
|
166
|
+
maxRounds: 1,
|
|
167
|
+
deferNonBlocking: true
|
|
168
|
+
}),
|
|
169
|
+
...(flags.withTests ? {} : { selectGates: withoutTestGate }),
|
|
170
|
+
afterUnit: draftAfterUnit(flags.withTests)
|
|
171
|
+
};
|
package/flows/lib/epic-mvp.js
CHANGED
|
@@ -21,7 +21,7 @@ import { Story, StoryPlan } from "@llm4ts/flow/StoryPlan";
|
|
|
21
21
|
import { commandFor, criterionTags, evidenceSourceFromEnvironment, parseJUnit, statusFromReport, testPaths } from "@llm4ts/flow/TestEvidence";
|
|
22
22
|
import { structuredAndPublish } from "@llm4ts/flow/Usage";
|
|
23
23
|
import { nodeProcessExecutor, ScriptUsage } from "@llm4ts/runner";
|
|
24
|
-
import { storyJsonSchema, storyPlanInstructions } from "./epic-stories.js";
|
|
24
|
+
import { storyJsonSchema, storyPlanInstructions, unitStateDirs } from "./epic-stories.js";
|
|
25
25
|
export const parseMvpFlags = (argv) => {
|
|
26
26
|
const iterate = argv.includes("--iterate");
|
|
27
27
|
const review = argv.includes("--review");
|
|
@@ -381,20 +381,9 @@ export const mvpAfterUnit = (source) => (unit, report) => Effect.gen(function* (
|
|
|
381
381
|
const Verdict = Schema.fromJsonString(MvpVerdict);
|
|
382
382
|
const encodeVerdict = Schema.encodeSync(Verdict);
|
|
383
383
|
const decodeVerdict = Schema.decodeUnknownOption(Verdict);
|
|
384
|
-
/** The state folders whose verdicts the landing reads: the epic's and each round's. */
|
|
385
|
-
const verdictDirs = (files, epicStateDir) => Effect.gen(function* () {
|
|
386
|
-
const dirs = [epicStateDir];
|
|
387
|
-
for (let round = 1;; round += 1) {
|
|
388
|
-
const dir = join(epicStateDir, "rounds", String(round));
|
|
389
|
-
if ((yield* files.read(join(dir, "plan.md"))) === undefined) {
|
|
390
|
-
return dirs;
|
|
391
|
-
}
|
|
392
|
-
dirs.push(dir);
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
384
|
/** `--land` only once the MVP is reached, in the epic and in every round. */
|
|
396
385
|
export const mvpBeforeLand = (epicStateDir, files) => Effect.gen(function* () {
|
|
397
|
-
for (const dir of yield*
|
|
386
|
+
for (const dir of yield* unitStateDirs(files, epicStateDir)) {
|
|
398
387
|
const text = yield* files.read(verdictPath(dir));
|
|
399
388
|
const verdict = text === undefined ? undefined : decodeVerdict(text);
|
|
400
389
|
if (verdict === undefined || Option.isNone(verdict)) {
|
|
@@ -415,7 +404,7 @@ export const mvpBeforeLand = (epicStateDir, files) => Effect.gen(function* () {
|
|
|
415
404
|
/** `--iterate`: the text given, then every parked list of the epic as what to plan. */
|
|
416
405
|
export const mvpIterateFeedback = (epicStateDir, text, files) => Effect.gen(function* () {
|
|
417
406
|
const parked = [];
|
|
418
|
-
for (const dir of yield*
|
|
407
|
+
for (const dir of yield* unitStateDirs(files, epicStateDir)) {
|
|
419
408
|
const list = yield* files.read(parkedPath(dir));
|
|
420
409
|
if (list !== undefined) {
|
|
421
410
|
parked.push(list);
|
|
@@ -60,9 +60,10 @@ export const epicUsage = [
|
|
|
60
60
|
" --fail-fast stop the epic at the first failed story",
|
|
61
61
|
" --judge-rounds <n> judge attempts per story (default 2 = one revision); a story the judge",
|
|
62
62
|
" has not cleared after n attempts fails. LLM4TS_JUDGE_ROUNDS sets it too",
|
|
63
|
-
" --task-concurrency <n> coders one story may hold at once (default
|
|
64
|
-
"
|
|
65
|
-
"
|
|
63
|
+
" --task-concurrency <n> coders one story may hold at once (default 1: every task in order).",
|
|
64
|
+
" Above 1, a task the plan says needs nothing still running, and whose",
|
|
65
|
+
" Owns: paths overlap no running task's, goes to a free coder in its own",
|
|
66
|
+
" worktree. LLM4TS_TASK_CONCURRENCY sets it too",
|
|
66
67
|
" --merge-revisions <n> times a story whose merge turns the epic gates red goes back to",
|
|
67
68
|
" catch up and fix it before failing (default 1; 0 fails at once).",
|
|
68
69
|
" LLM4TS_MERGE_REVISIONS sets it too",
|
|
@@ -1284,6 +1285,16 @@ export const stallOptions = (environment) => {
|
|
|
1284
1285
|
export const gateRunOptions = (environment) => ({ timeout: Duration.seconds(gateTimeoutSeconds(environment)) });
|
|
1285
1286
|
/** The gate command that runs the tests, for the one flaky rerun; undefined when none does. */
|
|
1286
1287
|
export const testGateCommand = (commands) => commands.find((command) => command.some((part, index) => index > 0 && /(?:^|[^a-z])(?:test|vitest|jest)/iu.test(part)));
|
|
1288
|
+
/** The gates less the test command (ADR 0043): a draft defers its tests, not its build. */
|
|
1289
|
+
export const withoutTestGate = (commands) => {
|
|
1290
|
+
const test = testGateCommand(commands);
|
|
1291
|
+
return test === undefined
|
|
1292
|
+
? { commands, note: "test gate: none recognised, every gate kept" }
|
|
1293
|
+
: {
|
|
1294
|
+
commands: commands.filter((command) => command !== test),
|
|
1295
|
+
note: `test gate deferred: ${test.join(" ")}`
|
|
1296
|
+
};
|
|
1297
|
+
};
|
|
1287
1298
|
/**
|
|
1288
1299
|
* How gate output reaches the coder: a capped tail, and the log's path. The
|
|
1289
1300
|
* epic-stories coders are CLI harnesses that can open a file; an API coder
|
|
@@ -1405,6 +1416,17 @@ export const rubricStoryJudge = (context) => (story, diff, seats, subject) => ju
|
|
|
1405
1416
|
// The story's own seat, not the run's: its time counts for the story.
|
|
1406
1417
|
// And the judge seat, never the writing reasoner: a judge reads.
|
|
1407
1418
|
seats.context.roster?.forRole("judge") ?? seats.context.judge ?? seats.context.reasoning, story, diff, context.budget, context.plan, subject);
|
|
1419
|
+
/** A unit's state folders, the epic's and then each planned round's (ADR 0021). */
|
|
1420
|
+
export const unitStateDirs = (files, epicStateDir) => Effect.gen(function* () {
|
|
1421
|
+
const dirs = [epicStateDir];
|
|
1422
|
+
for (let round = 1;; round += 1) {
|
|
1423
|
+
const dir = join(epicStateDir, "rounds", String(round));
|
|
1424
|
+
if ((yield* files.read(join(dir, "plan.md"))) === undefined) {
|
|
1425
|
+
return dirs;
|
|
1426
|
+
}
|
|
1427
|
+
dirs.push(dir);
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1408
1430
|
/** The runner options the two flags above add; empty when neither is set. */
|
|
1409
1431
|
export const epicRunnerOptions = Effect.fn("epic-stories.runnerOptions")(function* (options, environment) {
|
|
1410
1432
|
const judgment = options.judgmentFromEnvironment === true
|
|
@@ -1663,8 +1685,14 @@ export const runEpicStories = (options) => Effect.gen(function* () {
|
|
|
1663
1685
|
scripts: yield* appScripts(files, join(input.workDir, appDir)),
|
|
1664
1686
|
pack
|
|
1665
1687
|
});
|
|
1666
|
-
const
|
|
1667
|
-
|
|
1688
|
+
const selected = options.selectGates === undefined
|
|
1689
|
+
? undefined
|
|
1690
|
+
: options.selectGates(resolvedGates.commands);
|
|
1691
|
+
const commands = selected?.commands ?? resolvedGates.commands;
|
|
1692
|
+
yield* events.publish(Info.make({ message: gateListMessage({ ...resolvedGates, commands }) }));
|
|
1693
|
+
if (selected?.note !== undefined) {
|
|
1694
|
+
yield* events.publish(Info.make({ message: selected.note }));
|
|
1695
|
+
}
|
|
1668
1696
|
// The Node the gates would run on is the one on PATH, not the one
|
|
1669
1697
|
// the application pins; a mismatch is a red first story that says
|
|
1670
1698
|
// nothing about Node. Ask now, and name both.
|
|
@@ -1734,10 +1762,15 @@ export const runEpicStories = (options) => Effect.gen(function* () {
|
|
|
1734
1762
|
`The application lives in ${appDir}/ — its package.json, sources and tests; the gates run there.`
|
|
1735
1763
|
];
|
|
1736
1764
|
const gates = inAppDir(appDir, gatesIn(nodeProcessExecutor, events, commands, gateRunOptions(process.env)));
|
|
1737
|
-
const
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1765
|
+
const testGateOf = (from) => {
|
|
1766
|
+
const testCommand = testGateCommand(from);
|
|
1767
|
+
return testCommand === undefined
|
|
1768
|
+
? undefined
|
|
1769
|
+
: inAppDir(appDir, gatesIn(nodeProcessExecutor, events, [testCommand], gateRunOptions(process.env)));
|
|
1770
|
+
};
|
|
1771
|
+
const testGate = testGateOf(commands);
|
|
1772
|
+
// A policy that deferred the test gate still gets it, to run once on its own terms.
|
|
1773
|
+
const unitTestGate = testGateOf(resolvedGates.commands);
|
|
1741
1774
|
const unitContext = (work) => ({
|
|
1742
1775
|
plan: work.plan,
|
|
1743
1776
|
stateDir: work.stateDir,
|
|
@@ -1746,7 +1779,7 @@ export const runEpicStories = (options) => Effect.gen(function* () {
|
|
|
1746
1779
|
appDir,
|
|
1747
1780
|
files,
|
|
1748
1781
|
events,
|
|
1749
|
-
testGate
|
|
1782
|
+
testGate: unitTestGate
|
|
1750
1783
|
});
|
|
1751
1784
|
const setupCommand = worktreeSetupCommand(process.env, pack);
|
|
1752
1785
|
const healthUrl = serverHealthUrl(localServer, process.env);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/shell",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.50.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.
|
|
65
|
-
"@llm4ts/
|
|
66
|
-
"@llm4ts/
|
|
64
|
+
"@llm4ts/flow": "2.50.0",
|
|
65
|
+
"@llm4ts/runner": "2.50.0",
|
|
66
|
+
"@llm4ts/core": "2.50.0"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"effect": "4.0.0"
|