@llm4ts/shell 0.14.0 → 0.15.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-extract.js +64 -38
- package/package.json +5 -5
|
@@ -24,8 +24,13 @@
|
|
|
24
24
|
// context is bounded by LLM4TS_CONTEXT_BUDGET (chars;
|
|
25
25
|
// LLM4TS_JUDGE_SOURCES_LIMIT is the deprecated alias). The analyst is bounded
|
|
26
26
|
// by LLM4TS_ANALYST_TURNS and LLM4TS_MAX_CLOSURE_FILES.
|
|
27
|
+
// LLM4TS_EXTRACT_CONCURRENCY=<n> extracts and judges n programs at once (default
|
|
28
|
+
// 1): the programs of a wave are independent, each gets a commit scoped to its
|
|
29
|
+
// own files, and a failure lets in-flight programs finish before it surfaces.
|
|
27
30
|
import { join } from "node:path";
|
|
28
31
|
import * as Effect from "effect/Effect";
|
|
32
|
+
import * as Ref from "effect/Ref";
|
|
33
|
+
import * as Semaphore from "effect/Semaphore";
|
|
29
34
|
import { Sample } from "@llm4ts/core/eval/Eval";
|
|
30
35
|
import { judge } from "@llm4ts/core/eval/Judge";
|
|
31
36
|
import { budget, capped, withShrink } from "@llm4ts/flow/Context";
|
|
@@ -42,7 +47,7 @@ import { cachedReview } from "@llm4ts/flow/ReviewCache";
|
|
|
42
47
|
import { coverage, coverageUnits, features, matchingFiles } from "@llm4ts/flow/SpecChecks";
|
|
43
48
|
import { SurveyGraph, closureFor, surveyGraph } from "@llm4ts/flow/Survey";
|
|
44
49
|
import { withDraftApproval, requireApproval } from "@llm4ts/modernize/Approval";
|
|
45
|
-
import { ProgramArtifacts, ProgramUnit, extractProgramsResumably } from "@llm4ts/modernize/Artifacts";
|
|
50
|
+
import { ProgramArtifacts, ProgramUnit, extractProgramsResumably, programArtifactPaths } from "@llm4ts/modernize/Artifacts";
|
|
46
51
|
import { asReadOnly, coderFromEnv, withTurnLimit } from "@llm4ts/runner/Connectors";
|
|
47
52
|
import { resolveFlowInput } from "@llm4ts/runner/FlowArgs";
|
|
48
53
|
import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner";
|
|
@@ -63,6 +68,14 @@ const analystTurns = () => positiveEnvInt("LLM4TS_ANALYST_TURNS", 48);
|
|
|
63
68
|
* than this gets a bounded, visible subset rather than an unbounded read.
|
|
64
69
|
*/
|
|
65
70
|
const maxClosureFiles = () => positiveEnvInt("LLM4TS_MAX_CLOSURE_FILES", 40);
|
|
71
|
+
/**
|
|
72
|
+
* Programs extracted (and judged) at once. The programs of a wave are
|
|
73
|
+
* independent — each analyst reads its source and resolved closure, writes
|
|
74
|
+
* its own four files, and gets its own scoped commit — so this divides wall
|
|
75
|
+
* time without changing tokens or cost. Default 1 keeps the sequential
|
|
76
|
+
* narration; the bound is about the coder seat's quota, not correctness.
|
|
77
|
+
*/
|
|
78
|
+
const extractConcurrency = () => positiveEnvInt("LLM4TS_EXTRACT_CONCURRENCY", 1);
|
|
66
79
|
/** `cobol/ACCTXFR.cbl` → `ACCTXFR`: the program name keying every per-program artifact. */
|
|
67
80
|
const programName = (relativePath) => {
|
|
68
81
|
const base = relativePath.slice(relativePath.lastIndexOf("/") + 1);
|
|
@@ -243,33 +256,44 @@ const program = Effect.gen(function* () {
|
|
|
243
256
|
...(pack.exclude === undefined ? {} : { exclude: pack.exclude })
|
|
244
257
|
}));
|
|
245
258
|
// One structured analyst call per program, resumable per program: a rerun
|
|
246
|
-
// skips every program whose spec exists, and each program gets its own
|
|
259
|
+
// skips every program whose spec exists, and each program gets its own
|
|
260
|
+
// commit scoped to its four files. LLM4TS_EXTRACT_CONCURRENCY runs that
|
|
261
|
+
// many programs at once; the commit is the one step serialised.
|
|
262
|
+
const concurrency = extractConcurrency();
|
|
247
263
|
yield* stage(context.events, "extract", Effect.gen(function* () {
|
|
248
|
-
|
|
249
|
-
|
|
264
|
+
const started = yield* Ref.make(0);
|
|
265
|
+
const commitLock = yield* Semaphore.make(1);
|
|
266
|
+
if (concurrency > 1) {
|
|
267
|
+
yield* context.events.publish(Info.make({
|
|
268
|
+
message: `extracting up to ${concurrency} program(s) at once (LLM4TS_EXTRACT_CONCURRENCY)`
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
const summary = yield* extractProgramsResumably(files, units, (target) => Effect.gen(function* () {
|
|
272
|
+
const ordinal = yield* Ref.updateAndGet(started, (count) => count + 1);
|
|
273
|
+
yield* context.events.publish(Info.make({
|
|
274
|
+
message: `extracting ${target.sourcePath} (${ordinal}/${units.length})`
|
|
275
|
+
}));
|
|
276
|
+
return yield* structuredAndPublish(context.coder, context.events, `${system}\n\n${programAsk(pack, target.sourcePath, closureFor(graph, target.name, maxClosureFiles()))}`, ProgramArtifacts, programArtifactsJsonSchema, "coder").pipe(
|
|
277
|
+
// A turn-limit trip is the wedged-agent tail, not a
|
|
278
|
+
// failure: keep whatever the analyst already produced.
|
|
279
|
+
Effect.catchIf((error) => error.cause?._tag === "TurnLimitError", (error) => Effect.gen(function* () {
|
|
280
|
+
const existing = yield* files.read(join(modDirAbs, "specs", `${target.name}.md`));
|
|
281
|
+
if (existing === undefined) {
|
|
282
|
+
return yield* Effect.fail(error);
|
|
283
|
+
}
|
|
250
284
|
yield* context.events.publish(Info.make({
|
|
251
|
-
message: `
|
|
285
|
+
message: `turn limit hit on ${target.sourcePath} after its spec was written — keeping the work`
|
|
252
286
|
}));
|
|
253
|
-
return
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}));
|
|
264
|
-
return ProgramArtifacts.make({
|
|
265
|
-
spec: existing,
|
|
266
|
-
feature: "",
|
|
267
|
-
traceability: "",
|
|
268
|
-
mapping: ""
|
|
269
|
-
});
|
|
270
|
-
})));
|
|
271
|
-
}), modDirAbs);
|
|
272
|
-
if (summary.created.length > 0) {
|
|
287
|
+
return ProgramArtifacts.make({
|
|
288
|
+
spec: existing,
|
|
289
|
+
feature: "",
|
|
290
|
+
traceability: "",
|
|
291
|
+
mapping: ""
|
|
292
|
+
});
|
|
293
|
+
})));
|
|
294
|
+
}), modDirAbs, {
|
|
295
|
+
concurrency,
|
|
296
|
+
onCreated: (unit) => Effect.gen(function* () {
|
|
273
297
|
// Tag the traceability fragment with the cards this program's
|
|
274
298
|
// source matches — regex-decided, so implementation's playbook
|
|
275
299
|
// is reproducible.
|
|
@@ -280,15 +304,18 @@ const program = Effect.gen(function* () {
|
|
|
280
304
|
const fragment = (yield* files.read(fragmentPath)) ?? "";
|
|
281
305
|
yield* files.writeAtomic(fragmentPath, `${fragment.trimEnd()}\n\nPatterns: ${matched.map((card) => card.id).join(", ")}\n`);
|
|
282
306
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
307
|
+
// Only this program's files, under one permit: a concurrent
|
|
308
|
+
// sibling's half-written artifacts must never ride along.
|
|
309
|
+
yield* commitLock.withPermit(context.git
|
|
310
|
+
.commitPaths(`modernize(${pack.name}): spec ${unit.name}`, programArtifactPaths(unit, ModDir))
|
|
311
|
+
.pipe(Effect.asVoid));
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
if (summary.skipped.length > 0) {
|
|
315
|
+
yield* context.events.publish(Info.make({
|
|
316
|
+
message: `resume: ${summary.skipped.length} program(s) already have a spec — skipped ` +
|
|
317
|
+
summary.skipped.join(", ")
|
|
318
|
+
}));
|
|
292
319
|
}
|
|
293
320
|
}));
|
|
294
321
|
// traceability.md / mapping.md are regenerated from the fragments — fixes
|
|
@@ -379,10 +406,9 @@ const program = Effect.gen(function* () {
|
|
|
379
406
|
});
|
|
380
407
|
const covered = yield* coverage(repo, pack.coverage, trace);
|
|
381
408
|
const wellFormed = yield* features(repo, join(ModDir, "features"));
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
409
|
+
// Verdicts are per program and cached per program, so they judge
|
|
410
|
+
// under the same bound as extraction; the merge is order-stable.
|
|
411
|
+
const judged = yield* Effect.forEach(units, judgeProgram, { concurrency });
|
|
386
412
|
return mergeReviewResults([covered, wellFormed, docs, ...judged]);
|
|
387
413
|
});
|
|
388
414
|
// One bounded fix turn per sub-bar program (own commit) plus one residual
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@effect/platform-node": "4.0.0-beta.102",
|
|
52
|
-
"@llm4ts/
|
|
53
|
-
"@llm4ts/
|
|
54
|
-
"@llm4ts/
|
|
55
|
-
"@llm4ts/
|
|
52
|
+
"@llm4ts/core": "0.15.0",
|
|
53
|
+
"@llm4ts/flow": "0.15.0",
|
|
54
|
+
"@llm4ts/modernize": "0.15.0",
|
|
55
|
+
"@llm4ts/runner": "0.15.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"effect": "4.0.0-beta.102"
|