@llm4ts/shell 0.16.1 → 0.17.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.
|
@@ -47,7 +47,7 @@ The expected split of the demo epic against the internet-banking portal fixture
|
|
|
47
47
|
{
|
|
48
48
|
"id": "payments-contract",
|
|
49
49
|
"title": "Payments domain contract and stateful fake routes",
|
|
50
|
-
"description": "Declare the Payments HttpApi in src/contracts/payments.ts: list saved beneficiaries (name, IBAN), create a transfer
|
|
50
|
+
"description": "Declare the Payments HttpApi in src/contracts/payments.ts: list saved beneficiaries (name, IBAN), create a transfer from a payload with exactly these fields: fromAccountId (an Accounts domain account id), beneficiary as either { beneficiaryId } for a saved one or { name, iban } typed by the customer, amountCents, description, executionDate (ISO date), returning a pending transfer with an id, confirm a transfer with a six-digit SCA code: TransferState is exactly \"pending\" | \"confirmed\" | \"refused\"; any six-digit code other than 000000 moves the transfer to confirmed, the code 000000 moves it to refused and returns the refused transfer (HTTP 200, not an error), and a code that is not six digits is UnprocessableEntity; list transfers newest first with their state, 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.",
|
|
51
51
|
"dependsOn": [],
|
|
52
52
|
"owned": [
|
|
53
53
|
"src/contracts/payments.ts",
|
|
@@ -68,7 +68,8 @@ The expected split of the demo epic against the internet-banking portal fixture
|
|
|
68
68
|
"client.payments.confirm({ params: { transferId }, payload: { code } })",
|
|
69
69
|
"client.payments.list()",
|
|
70
70
|
"client.payments.get({ params: { transferId } })",
|
|
71
|
-
"src/contracts/payments.test.ts"
|
|
71
|
+
"src/contracts/payments.test.ts",
|
|
72
|
+
"TransferState = \"pending\" | \"confirmed\" | \"refused\""
|
|
72
73
|
]
|
|
73
74
|
},
|
|
74
75
|
{
|
|
@@ -136,10 +137,11 @@ The expected split of the demo epic against the internet-banking portal fixture
|
|
|
136
137
|
{
|
|
137
138
|
"id": "bonifico-form",
|
|
138
139
|
"title": "Bonifico: new transfer with review, SCA confirmation and outcome",
|
|
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.",
|
|
140
|
+
"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 chosen among the customer's accounts loaded through accountsDomain from src/contracts/accounts.fake.ts, 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.",
|
|
140
141
|
"dependsOn": [
|
|
141
142
|
"payments-contract",
|
|
142
|
-
"iban-field"
|
|
143
|
+
"iban-field",
|
|
144
|
+
"accounts-contract"
|
|
143
145
|
],
|
|
144
146
|
"owned": [
|
|
145
147
|
"src/features/bonifico/nuovo"
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Legacy modernization phase -1: load a pack and match its rules against an estate; no LLM.
|
|
2
|
+
//
|
|
3
|
+
// The pack equivalent of the mock provider: a deterministic first success
|
|
4
|
+
// before any paid phase. The pack is loaded exactly as survey and extract load
|
|
5
|
+
// it, then the flow reports what it found and how each rule matches the
|
|
6
|
+
// estate at `--repo`:
|
|
7
|
+
//
|
|
8
|
+
// 1. manifest — name, source, scaffold, specs/features dirs, gates, replay,
|
|
9
|
+
// equivalence policy, judge dimensions, prompt and reviewer sidecars.
|
|
10
|
+
// 2. estate — the files `sources:` selects (minus `exclude:`), the files
|
|
11
|
+
// `programs:` selects, and for every `## Coverage:` and `## Survey:` rule
|
|
12
|
+
// the units it captures, with a sample of each so a regex typo is
|
|
13
|
+
// visible at a glance.
|
|
14
|
+
// 3. verdict — hard failures (the pack does not load, `sources:` or
|
|
15
|
+
// `programs:` match nothing) abort with exit code 1; likely mistakes (a
|
|
16
|
+
// rule capturing no unit, a missing phase prompt, a scaffold path that
|
|
17
|
+
// does not exist, no reviewer lens, no judge dimension) are listed as
|
|
18
|
+
// warnings and the flow exits 0.
|
|
19
|
+
//
|
|
20
|
+
// Run: LLM4TS_PACK=packs/my-pack modernize-pack-check --repo ~/estates/legacy
|
|
21
|
+
import { existsSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import * as Effect from "effect/Effect";
|
|
24
|
+
import { FlowAborted } from "@llm4ts/flow/FlowError";
|
|
25
|
+
import { Info } from "@llm4ts/flow/FlowEvents";
|
|
26
|
+
import { stage } from "@llm4ts/flow/PlanExecution";
|
|
27
|
+
import { coverageUnits, matchingFiles } from "@llm4ts/flow/SpecChecks";
|
|
28
|
+
import { legacySourceWorkspaceLimits, workspaceLimitsFromEnv } from "@llm4ts/flow/Workspace";
|
|
29
|
+
import { mock } from "@llm4ts/runner/Connectors";
|
|
30
|
+
import { resolveFlowInput } from "@llm4ts/runner/FlowArgs";
|
|
31
|
+
import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner";
|
|
32
|
+
import { makeNodeWorkspace } from "@llm4ts/runner/NodeWorkspace";
|
|
33
|
+
import { openPack } from "@llm4ts/runner/Packs";
|
|
34
|
+
/** Prompt sidecars the modernization phases read, with the phase that reads each. */
|
|
35
|
+
const phasePrompts = [
|
|
36
|
+
["analysis", "extract"],
|
|
37
|
+
["spec", "extract"],
|
|
38
|
+
["bdd", "extract"],
|
|
39
|
+
["plan", "extract"],
|
|
40
|
+
["implement", "implement"],
|
|
41
|
+
["review", "review"],
|
|
42
|
+
["vectors", "verify"]
|
|
43
|
+
];
|
|
44
|
+
const sampleSize = 5;
|
|
45
|
+
const sample = (units) => units.length === 0
|
|
46
|
+
? "(none)"
|
|
47
|
+
: units.slice(0, sampleSize).join(", ") +
|
|
48
|
+
(units.length > sampleSize ? ` … +${units.length - sampleSize} more` : "");
|
|
49
|
+
const plural = (count, noun, many = `${noun}s`) => `${count} ${count === 1 ? noun : many}`;
|
|
50
|
+
const program = Effect.gen(function* () {
|
|
51
|
+
const input = yield* resolveFlowInput("Check the pack against the estate");
|
|
52
|
+
yield* runNode({
|
|
53
|
+
workDir: input.workDir,
|
|
54
|
+
workspace: input.workspace,
|
|
55
|
+
userPrompt: input.prompt,
|
|
56
|
+
// This phase makes no model call; the mock seat satisfies the one
|
|
57
|
+
// context shape the runner composes for every flow.
|
|
58
|
+
coder: mock,
|
|
59
|
+
environment: process.env
|
|
60
|
+
}, (context) => Effect.gen(function* () {
|
|
61
|
+
const say = (message) => context.events.publish(Info.make({ message }));
|
|
62
|
+
const warnings = [];
|
|
63
|
+
const failures = [];
|
|
64
|
+
const opened = yield* stage(context.events, "pack", openPack({
|
|
65
|
+
environment: process.env,
|
|
66
|
+
launchDir: input.workspace,
|
|
67
|
+
flowDir: import.meta.dirname
|
|
68
|
+
}));
|
|
69
|
+
const pack = opened.pack;
|
|
70
|
+
const packRoot = join(opened.workspace.root, opened.dir);
|
|
71
|
+
yield* stage(context.events, "manifest", Effect.gen(function* () {
|
|
72
|
+
yield* say(`pack '${pack.name}' (source: ${pack.source}) at ${packRoot}`);
|
|
73
|
+
yield* say(`specs-dir: ${pack.specsDir} · features-dir: ${pack.featuresDir}`);
|
|
74
|
+
if (pack.scaffold === undefined) {
|
|
75
|
+
yield* say("scaffold: (none — seed needs a non-empty target)");
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const scaffoldRoot = join(packRoot, pack.scaffold);
|
|
79
|
+
yield* say(`scaffold: ${pack.scaffold}`);
|
|
80
|
+
if (!existsSync(scaffoldRoot)) {
|
|
81
|
+
warnings.push(`scaffold '${pack.scaffold}' does not exist at ${scaffoldRoot}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const gates = Object.entries(pack.gates);
|
|
85
|
+
yield* say(gates.length === 0
|
|
86
|
+
? "gates: (none)"
|
|
87
|
+
: `gates: ${gates.map(([name, command]) => `${name} → ${command.join(" ")}`).join(" · ")}`);
|
|
88
|
+
if (gates.length === 0) {
|
|
89
|
+
warnings.push("## Gates is empty — implement and verify have nothing to enforce");
|
|
90
|
+
}
|
|
91
|
+
yield* say(pack.replay === undefined
|
|
92
|
+
? "replay: (none — verify cannot run equivalence vectors)"
|
|
93
|
+
: `replay: ${pack.replay.join(" ")} (${pack.equivalence.ordering.toLowerCase()} comparison)`);
|
|
94
|
+
yield* say(`judge: ${plural(pack.judgeDimensions.length, "dimension")}` +
|
|
95
|
+
(pack.judgeDimensions.length === 0
|
|
96
|
+
? ""
|
|
97
|
+
: ` — ${pack.judgeDimensions.map((d) => `${d.name} (0..${d.maxScore})`).join(", ")}`));
|
|
98
|
+
if (pack.judgeDimensions.length === 0) {
|
|
99
|
+
warnings.push("## Judge has no dimensions — extract's gate cannot score a spec");
|
|
100
|
+
}
|
|
101
|
+
const present = phasePrompts.filter(([name]) => pack.prompt(name) !== undefined);
|
|
102
|
+
const missing = phasePrompts.filter(([name]) => pack.prompt(name) === undefined);
|
|
103
|
+
yield* say(`prompts: ${present.length}/${phasePrompts.length} phase sidecars` +
|
|
104
|
+
(present.length === 0 ? "" : ` — ${present.map(([name]) => name).join(", ")}`));
|
|
105
|
+
for (const [name, phase] of missing) {
|
|
106
|
+
warnings.push(`prompts/${name}.md not found (read by ${phase})`);
|
|
107
|
+
}
|
|
108
|
+
yield* say(`reviewers: ${plural(pack.lenses.length, "lens", "lenses")}` +
|
|
109
|
+
(pack.lenses.length === 0 ? "" : ` — ${pack.lenses.map((l) => l.name).join(", ")}`));
|
|
110
|
+
if (pack.lenses.length === 0) {
|
|
111
|
+
warnings.push("no reviewers/*.md sidecar — review runs without pack lenses");
|
|
112
|
+
}
|
|
113
|
+
yield* say(`lessons: ${pack.lessons === undefined ? "(none yet)" : "present"}`);
|
|
114
|
+
}));
|
|
115
|
+
yield* stage(context.events, "estate", Effect.gen(function* () {
|
|
116
|
+
const repo = yield* makeNodeWorkspace(input.workDir, workspaceLimitsFromEnv(process.env, legacySourceWorkspaceLimits));
|
|
117
|
+
const sourcesRegex = pack.sources ?? ".*";
|
|
118
|
+
const sources = yield* matchingFiles(repo, sourcesRegex, pack.exclude);
|
|
119
|
+
yield* say(`sources: ${plural(sources.length, "file")} match '${sourcesRegex}'` +
|
|
120
|
+
(pack.exclude === undefined ? "" : ` minus '${pack.exclude}'`) +
|
|
121
|
+
` — ${sample(sources)}`);
|
|
122
|
+
if (sources.length === 0) {
|
|
123
|
+
failures.push(`sources '${sourcesRegex}' matched no file under ${input.workDir}`);
|
|
124
|
+
}
|
|
125
|
+
if (pack.programs === undefined) {
|
|
126
|
+
yield* say("programs: (not set — every source file is a program)");
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const programs = yield* matchingFiles(repo, pack.programs, pack.exclude);
|
|
130
|
+
yield* say(`programs: ${plural(programs.length, "file")} match '${pack.programs}' — ${sample(programs)}`);
|
|
131
|
+
if (programs.length === 0) {
|
|
132
|
+
failures.push(`programs '${pack.programs}' matched no file under ${input.workDir}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const report = Effect.fn("modernize-pack-check.rules")(function* (kind, rules) {
|
|
136
|
+
if (rules.length === 0) {
|
|
137
|
+
yield* say(`${kind}: (no rules)`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const units = yield* coverageUnits(repo, rules);
|
|
141
|
+
for (const rule of rules) {
|
|
142
|
+
const found = units[rule.name] ?? [];
|
|
143
|
+
yield* say(`${kind} '${rule.name}': ${plural(found.length, "unit")} — ${sample(found)}`);
|
|
144
|
+
if (found.length === 0) {
|
|
145
|
+
warnings.push(`${kind} rule '${rule.name}' captured no unit: files '${rule.files}', unit '${rule.unit}'`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
yield* report("coverage", pack.coverage);
|
|
150
|
+
yield* report("survey", pack.survey);
|
|
151
|
+
}));
|
|
152
|
+
yield* stage(context.events, "verdict", Effect.gen(function* () {
|
|
153
|
+
for (const warning of warnings) {
|
|
154
|
+
yield* say(`warning: ${warning}`);
|
|
155
|
+
}
|
|
156
|
+
if (failures.length > 0) {
|
|
157
|
+
return yield* FlowAborted.make({
|
|
158
|
+
message: `pack '${pack.name}' failed the check: ${failures.join("; ")}`
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
yield* say(`pack '${pack.name}' check passed with ${plural(warnings.length, "warning")} — ` +
|
|
162
|
+
`next: LLM4TS_PACK=${process.env.LLM4TS_PACK ?? opened.dir} modernize-survey --repo ${input.workDir}`);
|
|
163
|
+
}));
|
|
164
|
+
}));
|
|
165
|
+
});
|
|
166
|
+
runFlowMain(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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",
|
|
@@ -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/core": "0.
|
|
54
|
-
"@llm4ts/
|
|
55
|
-
"@llm4ts/
|
|
56
|
-
"@llm4ts/
|
|
53
|
+
"@llm4ts/core": "0.17.0",
|
|
54
|
+
"@llm4ts/flow": "0.17.0",
|
|
55
|
+
"@llm4ts/runner": "0.17.0",
|
|
56
|
+
"@llm4ts/modernize": "0.17.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"effect": "4.0.0-beta.102"
|