@llm4ts/shell 2.50.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.
- package/dist/OperatorTools.d.ts +2 -0
- package/dist/OperatorTools.d.ts.map +1 -1
- package/flows/epic-stories.js +8 -2
- package/flows/lib/debt.js +132 -0
- package/flows/lib/epic-draft.js +7 -63
- package/flows/lib/epic-stories.js +86 -24
- package/package.json +4 -4
package/dist/OperatorTools.d.ts
CHANGED
|
@@ -235,6 +235,7 @@ export declare const profile: Tool.Tool<"profile", {
|
|
|
235
235
|
readonly exploreBeforeEdit: Schema.Int;
|
|
236
236
|
readonly unverifiedClaims: Schema.Int;
|
|
237
237
|
readonly lowConfidenceTasks: Schema.Int;
|
|
238
|
+
readonly reviewDiffLines: Schema.optionalKey<Schema.Int>;
|
|
238
239
|
}>>;
|
|
239
240
|
readonly skipped: Schema.$Array<Schema.String>;
|
|
240
241
|
readonly totals: Schema.Struct<{
|
|
@@ -484,6 +485,7 @@ export declare const readToolkit: Toolkit.Toolkit<{
|
|
|
484
485
|
readonly exploreBeforeEdit: Schema.Int;
|
|
485
486
|
readonly unverifiedClaims: Schema.Int;
|
|
486
487
|
readonly lowConfidenceTasks: Schema.Int;
|
|
488
|
+
readonly reviewDiffLines: Schema.optionalKey<Schema.Int>;
|
|
487
489
|
}>>;
|
|
488
490
|
readonly skipped: Schema.$Array<Schema.String>;
|
|
489
491
|
readonly totals: Schema.Struct<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OperatorTools.d.ts","sourceRoot":"","sources":["../src/OperatorTools.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"OperatorTools.d.ts","sourceRoot":"","sources":["../src/OperatorTools.ts"],"names":[],"mappings":"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"}
|
package/flows/epic-stories.js
CHANGED
|
@@ -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 {
|
|
26
|
-
|
|
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 };
|
package/flows/lib/epic-draft.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
40
|
-
export
|
|
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,72 +94,20 @@ 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: () => ({
|
|
165
107
|
reviewers: [correctnessReviewer],
|
|
108
|
+
// One round, whether the lens reviews each task or the story (ADR 0044).
|
|
166
109
|
maxRounds: 1,
|
|
110
|
+
reviewRounds: 1,
|
|
167
111
|
deferNonBlocking: true
|
|
168
112
|
}),
|
|
169
113
|
...(flags.withTests ? {} : { selectGates: withoutTestGate }),
|
|
@@ -58,8 +58,10 @@ export const epicUsage = [
|
|
|
58
58
|
" A plain rerun finishes an open round; --plan-only stops after planning",
|
|
59
59
|
" --concurrency <n> stories implemented at once (default 3)",
|
|
60
60
|
" --fail-fast stop the epic at the first failed story",
|
|
61
|
-
" --
|
|
62
|
-
"
|
|
61
|
+
" --review-at <task|story> where the review lenses run (default story: once per story,",
|
|
62
|
+
" beside the judge; task: every task, as before 2.51)",
|
|
63
|
+
" --review-rounds <n> rounds of the story review (default 3); in task mode, judge attempts",
|
|
64
|
+
" (default 2). --judge-rounds and LLM4TS_JUDGE_ROUNDS set it too",
|
|
63
65
|
" --task-concurrency <n> coders one story may hold at once (default 1: every task in order).",
|
|
64
66
|
" Above 1, a task the plan says needs nothing still running, and whose",
|
|
65
67
|
" Owns: paths overlap no running task's, goes to a free coder in its own",
|
|
@@ -71,6 +73,10 @@ export const epicUsage = [
|
|
|
71
73
|
" settle, the judge clears without them); the rest are deferred and, once",
|
|
72
74
|
" every story is merged, planned and run as one refine round.",
|
|
73
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",
|
|
74
80
|
"Seats: LLM4TS_REASONER (claude|gemini|…, default claude) splits, reviews, judges;",
|
|
75
81
|
" LLM4TS_CODER (default pi) implements; LLM4TS_REASONING_MODEL / LLM4TS_CODER_MODEL",
|
|
76
82
|
" pick their models (pi: provider/model); LLM4TS_CODER_FLAGS / LLM4TS_REASONING_FLAGS",
|
|
@@ -91,6 +97,7 @@ export const parseEpicArgs = (argv) => Effect.gen(function* () {
|
|
|
91
97
|
let failFast = false;
|
|
92
98
|
let concurrency;
|
|
93
99
|
let judgeRounds;
|
|
100
|
+
let reviewAt;
|
|
94
101
|
let deferFindings = false;
|
|
95
102
|
let mergeRevisions;
|
|
96
103
|
let taskConcurrency;
|
|
@@ -183,17 +190,31 @@ export const parseEpicArgs = (argv) => Effect.gen(function* () {
|
|
|
183
190
|
}
|
|
184
191
|
mergeRevisions = parsed;
|
|
185
192
|
}
|
|
186
|
-
else if (argument === "--
|
|
187
|
-
const raw = argument.includes("=")
|
|
188
|
-
|
|
189
|
-
|
|
193
|
+
else if (argument === "--review-at" || argument.startsWith("--review-at=")) {
|
|
194
|
+
const raw = argument.includes("=") ? argument.slice("--review-at=".length) : argv[index + 1];
|
|
195
|
+
if (!argument.includes("=")) {
|
|
196
|
+
index += 1;
|
|
197
|
+
}
|
|
198
|
+
if (raw !== "task" && raw !== "story") {
|
|
199
|
+
return yield* ScriptUsage.make({
|
|
200
|
+
message: `--review-at takes task or story\n${epicUsage}`
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
reviewAt = raw;
|
|
204
|
+
}
|
|
205
|
+
else if (argument === "--judge-rounds" ||
|
|
206
|
+
argument.startsWith("--judge-rounds=") ||
|
|
207
|
+
argument === "--review-rounds" ||
|
|
208
|
+
argument.startsWith("--review-rounds=")) {
|
|
209
|
+
const name = argument.split("=")[0] ?? argument;
|
|
210
|
+
const raw = argument.includes("=") ? argument.slice(name.length + 1) : argv[index + 1];
|
|
190
211
|
if (!argument.includes("=")) {
|
|
191
212
|
index += 1;
|
|
192
213
|
}
|
|
193
214
|
const parsed = Number.parseInt(raw ?? "", 10);
|
|
194
215
|
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
195
216
|
return yield* ScriptUsage.make({
|
|
196
|
-
message:
|
|
217
|
+
message: `${name} requires a positive integer\n${epicUsage}`
|
|
197
218
|
});
|
|
198
219
|
}
|
|
199
220
|
judgeRounds = parsed;
|
|
@@ -207,6 +228,8 @@ export const parseEpicArgs = (argv) => Effect.gen(function* () {
|
|
|
207
228
|
failFast,
|
|
208
229
|
concurrency,
|
|
209
230
|
judgeRounds,
|
|
231
|
+
reviewAt,
|
|
232
|
+
reviewRounds: judgeRounds,
|
|
210
233
|
deferFindings,
|
|
211
234
|
mergeRevisions,
|
|
212
235
|
taskConcurrency,
|
|
@@ -270,6 +293,21 @@ export const judgeRoundsOption = (flag, environment) => {
|
|
|
270
293
|
const raw = Number.parseInt(environment.LLM4TS_JUDGE_ROUNDS ?? "", 10);
|
|
271
294
|
return Number.isInteger(raw) && raw > 0 ? { judgeRounds: raw } : {};
|
|
272
295
|
};
|
|
296
|
+
/**
|
|
297
|
+
* Where the lenses run and for how many rounds (ADR 0044): per story unless
|
|
298
|
+
* `--review-at task`. The rounds (`--review-rounds`, `--judge-rounds`, or
|
|
299
|
+
* `LLM4TS_JUDGE_ROUNDS`) bound the story review, or in task mode count judge
|
|
300
|
+
* attempts as before.
|
|
301
|
+
*/
|
|
302
|
+
export const reviewOptions = (reviewAt, rounds, environment) => {
|
|
303
|
+
const at = reviewAt ?? "story";
|
|
304
|
+
const { judgeRounds } = judgeRoundsOption(rounds, environment);
|
|
305
|
+
return judgeRounds === undefined
|
|
306
|
+
? { reviewAt: at }
|
|
307
|
+
: at === "story"
|
|
308
|
+
? { reviewAt: at, reviewRounds: judgeRounds }
|
|
309
|
+
: { reviewAt: at, judgeRounds };
|
|
310
|
+
};
|
|
273
311
|
export const roundDir = (stateDir, round) => join(stateDir, "rounds", String(round));
|
|
274
312
|
const mergedStories = (files, stateDir, plan) => Effect.gen(function* () {
|
|
275
313
|
let merged = 0;
|
|
@@ -1004,15 +1042,18 @@ export const storyDimensions = [
|
|
|
1004
1042
|
* a partial score elsewhere is a Warning a follow-up round can close.
|
|
1005
1043
|
*/
|
|
1006
1044
|
export const judgeSeverity = (name, score) => name === "provides" || score <= 0 ? "Critical" : "Warning";
|
|
1007
|
-
/**
|
|
1008
|
-
|
|
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
|
|
1009
1050
|
.map((dimension) => dimension.name)
|
|
1010
|
-
.filter((name) => story.testsDeferred === undefined
|
|
1011
|
-
const
|
|
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({
|
|
1012
1054
|
issues: scored.scores
|
|
1013
|
-
.filter((score) => judgedDimensionsOf(story).includes(score.name) &&
|
|
1014
|
-
score.score <
|
|
1015
|
-
(storyDimensions.find((dimension) => dimension.name === score.name)?.maxScore ?? 2))
|
|
1055
|
+
.filter((score) => judgedDimensionsOf(story, bar).includes(score.name) &&
|
|
1056
|
+
score.score < maxScoreOf(score.name))
|
|
1016
1057
|
.map((score) => ReviewIssue.make({
|
|
1017
1058
|
severity: judgeSeverity(score.name, score.score),
|
|
1018
1059
|
title: `judge[${story.id}]: ${score.name} scored ${score.score}`,
|
|
@@ -1027,6 +1068,11 @@ export const testsDeferredLines = (reason) => reason === undefined
|
|
|
1027
1068
|
`Tests deferred by the plan: ${reason}`,
|
|
1028
1069
|
"No test file is expected; do not mark any dimension down for its absence."
|
|
1029
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
|
+
];
|
|
1030
1076
|
/**
|
|
1031
1077
|
* What the story judge reads beside the diff: the story, and what every
|
|
1032
1078
|
* story it depends on provides. Without the dependencies the judge cannot
|
|
@@ -1034,7 +1080,7 @@ export const testsDeferredLines = (reason) => reason === undefined
|
|
|
1034
1080
|
* importing `paymentsDomain` from `payments.fake.ts`, exactly where the
|
|
1035
1081
|
* payments contract story declared it.
|
|
1036
1082
|
*/
|
|
1037
|
-
export const storyJudgeQuery = (story, plan, subject = "diff") => {
|
|
1083
|
+
export const storyJudgeQuery = (story, plan, subject = "diff", bar = {}) => {
|
|
1038
1084
|
const dependencies = plan === undefined
|
|
1039
1085
|
? []
|
|
1040
1086
|
: dependenciesOf(plan, story.id).flatMap((id) => {
|
|
@@ -1058,6 +1104,7 @@ export const storyJudgeQuery = (story, plan, subject = "diff") => {
|
|
|
1058
1104
|
`Owned paths: ${story.owned.join(", ")}`,
|
|
1059
1105
|
`Shared read-only: ${story.sharedReadOnly.join(", ") || "(none)"}`,
|
|
1060
1106
|
...testsDeferredLines(story.testsDeferred),
|
|
1107
|
+
...(story.testsDeferred === undefined && bar.deferTests === true ? deferTestsLines : []),
|
|
1061
1108
|
...(dependencies.length === 0
|
|
1062
1109
|
? []
|
|
1063
1110
|
: [
|
|
@@ -1083,19 +1130,32 @@ export const storyJudgeQuery = (story, plan, subject = "diff") => {
|
|
|
1083
1130
|
].join("\n");
|
|
1084
1131
|
};
|
|
1085
1132
|
/** The story-level judge over the branch diff, bounded by the character budget. */
|
|
1086
|
-
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)
|
|
1087
1134
|
.evaluate(Sample.make({
|
|
1088
|
-
query: storyJudgeQuery(story, plan, subject),
|
|
1135
|
+
query: storyJudgeQuery(story, plan, subject, bar),
|
|
1089
1136
|
response: cap(diff, budget).text
|
|
1090
1137
|
}))
|
|
1091
|
-
.pipe(Effect.mapError(FlowLlmError.from), Effect.map((scored) =>
|
|
1092
|
-
|
|
1093
|
-
|
|
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) => ({
|
|
1094
1144
|
id: score.name,
|
|
1095
1145
|
score: score.score,
|
|
1096
|
-
max:
|
|
1097
|
-
|
|
1098
|
-
}))
|
|
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
|
+
}));
|
|
1099
1159
|
// ---- App directory ----------------------------------------------------------------
|
|
1100
1160
|
/** Folders never taken for the application: dependencies, build output, llm4ts state. */
|
|
1101
1161
|
const ignoredAppDirs = new Set(["node_modules", "dist", "build", "out", "coverage"]);
|
|
@@ -1416,6 +1476,8 @@ export const rubricStoryJudge = (context) => (story, diff, seats, subject) => ju
|
|
|
1416
1476
|
// The story's own seat, not the run's: its time counts for the story.
|
|
1417
1477
|
// And the judge seat, never the writing reasoner: a judge reads.
|
|
1418
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 });
|
|
1419
1481
|
/** A unit's state folders, the epic's and then each planned round's (ADR 0021). */
|
|
1420
1482
|
export const unitStateDirs = (files, epicStateDir) => Effect.gen(function* () {
|
|
1421
1483
|
const dirs = [epicStateDir];
|
|
@@ -1857,7 +1919,7 @@ export const runEpicStories = (options) => Effect.gen(function* () {
|
|
|
1857
1919
|
...appDirNote,
|
|
1858
1920
|
...(orientation === undefined ? [] : ["", orientation])
|
|
1859
1921
|
].join("\n")),
|
|
1860
|
-
...
|
|
1922
|
+
...reviewOptions(flags.reviewAt, flags.reviewRounds, process.env),
|
|
1861
1923
|
...mergeRevisionsOption(flags.mergeRevisions, process.env),
|
|
1862
1924
|
...taskConcurrencyOption(flags.taskConcurrency, process.env),
|
|
1863
1925
|
concurrency,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/shell",
|
|
3
|
-
"version": "2.
|
|
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/
|
|
65
|
-
"@llm4ts/
|
|
66
|
-
"@llm4ts/
|
|
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"
|