@llm4ts/shell 0.16.2 → 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.
- package/flows/modernize-pack-check.js +166 -0
- package/package.json +5 -5
|
@@ -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/flow": "0.
|
|
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"
|