@llm4ts/flow 0.17.0 → 0.18.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.
@@ -0,0 +1,27 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ import type { PlainFileStoreShape } from "./Persistence.ts";
4
+ declare const ApprovalRequired_base: Schema.Class<ApprovalRequired, Schema.TaggedStruct<"ApprovalRequired", {
5
+ readonly path: Schema.String;
6
+ readonly marker: Schema.String;
7
+ }>, import("effect/Cause").YieldableError>;
8
+ /**
9
+ * The human gates of the modernization phases: a markdown document ends
10
+ * with a draft marker the flow writes, and the next phase refuses to start
11
+ * until a person has ticked it. (Moved from `@llm4ts/modernize` in 0.18.0.)
12
+ */
13
+ export declare class ApprovalRequired extends ApprovalRequired_base {
14
+ get message(): string;
15
+ }
16
+ declare const MissingModernizeArtifact_base: Schema.Class<MissingModernizeArtifact, Schema.TaggedStruct<"MissingModernizeArtifact", {
17
+ readonly path: Schema.String;
18
+ readonly message: Schema.String;
19
+ }>, import("effect/Cause").YieldableError>;
20
+ export declare class MissingModernizeArtifact extends MissingModernizeArtifact_base {
21
+ }
22
+ export declare const ApprovedMarker = "- [x] Approved";
23
+ export declare const DraftApprovalMarker = "- [ ] Approved";
24
+ export declare const withDraftApproval: (markdown: string) => string;
25
+ export declare const requireApproval: (files: PlainFileStoreShape, path: string) => Effect.Effect<void, import("./FlowError.ts").PersistenceError | import("./FlowError.ts").PlanParseError | import("./FlowError.ts").UnsupportedSchemaVersion | import("./FlowError.ts").WorkspacePathError | import("./FlowError.ts").WorkspaceLimitError | import("./FlowError.ts").WorkspaceIoError | import("./FlowError.ts").FlowAborted | import("./FlowError.ts").ProcessError | import("./FlowError.ts").FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | import("./FlowError.ts").PerimeterViolation | import("./FlowError.ts").MissingDependency | import("./FlowError.ts").MergeConflict | import("./FlowError.ts").StoryFailed | ApprovalRequired | MissingModernizeArtifact, never>;
26
+ export {};
27
+ //# sourceMappingURL=Approval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Approval.d.ts","sourceRoot":"","sources":["../src/Approval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;;;;;AAE3D;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,qBAMrC;IACC,IAAI,OAAO,IAAI,MAAM,CAEpB;CACF;;;;;AAED,qBAAa,wBAAyB,SAAQ,6BAM7C;CAAG;AAEJ,eAAO,MAAM,cAAc,mBAAmB,CAAA;AAC9C,eAAO,MAAM,mBAAmB,mBAAmB,CAAA;AAEnD,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,MACA,CAAA;AAErD,eAAO,MAAM,eAAe,41BAc1B,CAAA"}
@@ -0,0 +1,36 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ /**
4
+ * The human gates of the modernization phases: a markdown document ends
5
+ * with a draft marker the flow writes, and the next phase refuses to start
6
+ * until a person has ticked it. (Moved from `@llm4ts/modernize` in 0.18.0.)
7
+ */
8
+ export class ApprovalRequired extends Schema.TaggedErrorClass()("ApprovalRequired", {
9
+ path: Schema.String,
10
+ marker: Schema.String
11
+ }) {
12
+ get message() {
13
+ return `approval required in ${this.path}: set '${this.marker}'`;
14
+ }
15
+ }
16
+ export class MissingModernizeArtifact extends Schema.TaggedErrorClass()("MissingModernizeArtifact", {
17
+ path: Schema.String,
18
+ message: Schema.String
19
+ }) {
20
+ }
21
+ export const ApprovedMarker = "- [x] Approved";
22
+ export const DraftApprovalMarker = "- [ ] Approved";
23
+ export const withDraftApproval = (markdown) => `${markdown.trimEnd()}\n\n${DraftApprovalMarker}\n`;
24
+ export const requireApproval = Effect.fn("@llm4ts/flow/Approval.requireApproval")(function* (files, path) {
25
+ const contents = yield* files.read(path);
26
+ if (contents === undefined) {
27
+ return yield* MissingModernizeArtifact.make({
28
+ path,
29
+ message: `approval document does not exist: ${path}`
30
+ });
31
+ }
32
+ if (!contents.includes(ApprovedMarker)) {
33
+ return yield* ApprovalRequired.make({ path, marker: ApprovedMarker });
34
+ }
35
+ });
36
+ //# sourceMappingURL=Approval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Approval.js","sourceRoot":"","sources":["../src/Approval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAIvC;;;;GAIG;AACH,MAAM,OAAO,gBAAiB,SAAQ,MAAM,CAAC,gBAAgB,EAAoB,CAC/E,kBAAkB,EAClB;IACE,IAAI,EAAE,MAAM,CAAC,MAAM;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM;CACtB,CACF;IACC,IAAI,OAAO;QACT,OAAO,wBAAwB,IAAI,CAAC,IAAI,UAAU,IAAI,CAAC,MAAM,GAAG,CAAA;IAClE,CAAC;CACF;AAED,MAAM,OAAO,wBAAyB,SAAQ,MAAM,CAAC,gBAAgB,EAA4B,CAC/F,0BAA0B,EAC1B;IACE,IAAI,EAAE,MAAM,CAAC,MAAM;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM;CACvB,CACF;CAAG;AAEJ,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAA;AAC9C,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAgB,CAAA;AAEnD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,QAAgB,EAAU,EAAE,CAC5D,GAAG,QAAQ,CAAC,OAAO,EAAE,OAAO,mBAAmB,IAAI,CAAA;AAErD,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC,uCAAuC,CAAC,CAAC,QAAQ,CAAC,EACzF,KAA0B,EAC1B,IAAY;IAEZ,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACxC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,CAAC,wBAAwB,CAAC,IAAI,CAAC;YAC1C,IAAI;YACJ,OAAO,EAAE,qCAAqC,IAAI,EAAE;SACrD,CAAC,CAAA;IACJ,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAA;IACvE,CAAC;AACH,CAAC,CAAC,CAAA"}
@@ -0,0 +1,57 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ import type { PlainFileStoreShape } from "./Persistence.ts";
4
+ declare const ProgramUnit_base: Schema.Class<ProgramUnit, Schema.Struct<{
5
+ readonly name: Schema.String;
6
+ readonly sourcePath: Schema.String;
7
+ }>, {}>;
8
+ export declare class ProgramUnit extends ProgramUnit_base {
9
+ }
10
+ declare const ProgramArtifacts_base: Schema.Class<ProgramArtifacts, Schema.Struct<{
11
+ readonly spec: Schema.String;
12
+ readonly feature: Schema.String;
13
+ readonly traceability: Schema.String;
14
+ readonly mapping: Schema.String;
15
+ }>, {}>;
16
+ export declare class ProgramArtifacts extends ProgramArtifacts_base {
17
+ }
18
+ declare const ResumeSummary_base: Schema.Class<ResumeSummary, Schema.Struct<{
19
+ readonly created: Schema.$Array<Schema.String>;
20
+ readonly skipped: Schema.$Array<Schema.String>;
21
+ }>, {}>;
22
+ export declare class ResumeSummary extends ResumeSummary_base {
23
+ }
24
+ /** The four per-program artifact paths under `directory`, relative to the repository. */
25
+ export declare const programArtifactPaths: (unit: ProgramUnit, directory?: string) => ReadonlyArray<string>;
26
+ export interface ExtractProgramsOptions<E2, R2> {
27
+ /**
28
+ * How many programs extract at once (default 1: the sequential order the
29
+ * units were given in). The programs of a wave are independent — each
30
+ * analyst reads only its source and resolved closure and writes only its
31
+ * own four files — so the bound is about the model seat's quota, not about
32
+ * correctness.
33
+ */
34
+ readonly concurrency?: number;
35
+ /**
36
+ * Runs after a program's four artifacts are on disk and before it counts as
37
+ * created — the seam for per-program post-processing (pattern tagging, the
38
+ * program's own commit). Runs under the same concurrency as extraction:
39
+ * anything in it that must not interleave (git) serialises itself.
40
+ */
41
+ readonly onCreated?: (unit: ProgramUnit) => Effect.Effect<void, E2, R2>;
42
+ }
43
+ /**
44
+ * Extracts every unit whose spec is missing, writes its four artifacts, and
45
+ * reports what was created and what was skipped. Resumable by construction:
46
+ * a rerun skips every program whose spec exists.
47
+ *
48
+ * Failure keeps finished work: a program that fails stops NEW programs from
49
+ * starting, but programs already in flight run to completion and land on
50
+ * disk (and through `onCreated`) before the first failure, in unit order, is
51
+ * re-raised. With `concurrency` 1 that is exactly fail-fast; above 1 a quota
52
+ * death costs only the programs it interrupted, and a rerun resumes the rest.
53
+ */
54
+ export declare const extractProgramsResumably: <E, R, E2 = never, R2 = never>(files: PlainFileStoreShape, units: readonly ProgramUnit[], extract: (unit: ProgramUnit) => Effect.Effect<ProgramArtifacts, E, R>, directory?: string | undefined, options?: ExtractProgramsOptions<E2, R2> | undefined) => Effect.Effect<ResumeSummary, import("./FlowError.ts").PersistenceError | import("./FlowError.ts").PlanParseError | import("./FlowError.ts").UnsupportedSchemaVersion | import("./FlowError.ts").WorkspacePathError | import("./FlowError.ts").WorkspaceLimitError | import("./FlowError.ts").WorkspaceIoError | import("./FlowError.ts").FlowAborted | import("./FlowError.ts").ProcessError | import("./FlowError.ts").FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | import("./FlowError.ts").PerimeterViolation | import("./FlowError.ts").MissingDependency | import("./FlowError.ts").MergeConflict | import("./FlowError.ts").StoryFailed | E | E2, R | R2>;
55
+ export declare const generateVectorsResumably: <E, R>(files: PlainFileStoreShape, programs: readonly string[], generate: (program: string) => Effect.Effect<string, E, R>, directory?: string | undefined) => Effect.Effect<ResumeSummary, import("./FlowError.ts").PersistenceError | import("./FlowError.ts").PlanParseError | import("./FlowError.ts").UnsupportedSchemaVersion | import("./FlowError.ts").WorkspacePathError | import("./FlowError.ts").WorkspaceLimitError | import("./FlowError.ts").WorkspaceIoError | import("./FlowError.ts").FlowAborted | import("./FlowError.ts").ProcessError | import("./FlowError.ts").FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | import("./FlowError.ts").PerimeterViolation | import("./FlowError.ts").MissingDependency | import("./FlowError.ts").MergeConflict | import("./FlowError.ts").StoryFailed | E, R>;
56
+ export {};
57
+ //# sourceMappingURL=Artifacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Artifacts.d.ts","sourceRoot":"","sources":["../src/Artifacts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;;;;;AAE3D,qBAAa,WAAY,SAAQ,gBAG/B;CAAG;;;;;;;AAEL,qBAAa,gBAAiB,SAAQ,qBAKpC;CAAG;;;;;AAEL,qBAAa,aAAc,SAAQ,kBAGjC;CAAG;AAIL,yFAAyF;AACzF,eAAO,MAAM,oBAAoB,GAC/B,MAAM,WAAW,EACjB,kBAAgC,KAC/B,aAAa,CAAC,MAAM,CAKtB,CAAA;AAED,MAAM,WAAW,sBAAsB,CAAC,EAAE,EAAE,EAAE;IAC5C;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;CACxE;AAID;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,GAExB,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,qFAGf,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC,42BAiErE,CAAA;AAEF,eAAO,MAAM,wBAAwB,GAExB,CAAC,EAAE,CAAC,+EAGK,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,4yBAe1D,CAAA"}
@@ -0,0 +1,103 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Ref from "effect/Ref";
3
+ import * as Result from "effect/Result";
4
+ import * as Schema from "effect/Schema";
5
+ export class ProgramUnit extends Schema.Class("ProgramUnit")({
6
+ name: Schema.String,
7
+ sourcePath: Schema.String
8
+ }) {
9
+ }
10
+ export class ProgramArtifacts extends Schema.Class("ProgramArtifacts")({
11
+ spec: Schema.String,
12
+ feature: Schema.String,
13
+ traceability: Schema.String,
14
+ mapping: Schema.String
15
+ }) {
16
+ }
17
+ export class ResumeSummary extends Schema.Class("ResumeSummary")({
18
+ created: Schema.Array(Schema.String),
19
+ skipped: Schema.Array(Schema.String)
20
+ }) {
21
+ }
22
+ const programName = (name) => name.toLowerCase();
23
+ /** The four per-program artifact paths under `directory`, relative to the repository. */
24
+ export const programArtifactPaths = (unit, directory = "docs/modernization") => [
25
+ `${directory}/specs/${unit.name}.md`,
26
+ `${directory}/features/${programName(unit.name)}.feature`,
27
+ `${directory}/traceability/${unit.name}.md`,
28
+ `${directory}/mapping/${unit.name}.md`
29
+ ];
30
+ /**
31
+ * Extracts every unit whose spec is missing, writes its four artifacts, and
32
+ * reports what was created and what was skipped. Resumable by construction:
33
+ * a rerun skips every program whose spec exists.
34
+ *
35
+ * Failure keeps finished work: a program that fails stops NEW programs from
36
+ * starting, but programs already in flight run to completion and land on
37
+ * disk (and through `onCreated`) before the first failure, in unit order, is
38
+ * re-raised. With `concurrency` 1 that is exactly fail-fast; above 1 a quota
39
+ * death costs only the programs it interrupted, and a rerun resumes the rest.
40
+ */
41
+ export const extractProgramsResumably = Effect.fn("@llm4ts/flow/Artifacts.extractProgramsResumably")(function* (files, units, extract, directory = "docs/modernization", options = {}) {
42
+ const concurrency = Math.max(1, Math.floor(options.concurrency ?? 1));
43
+ const halted = yield* Ref.make(false);
44
+ const extractOne = (unit) => Effect.gen(function* () {
45
+ if (yield* Ref.get(halted)) {
46
+ return "not-started";
47
+ }
48
+ const [specPath, featurePath, traceabilityPath, mappingPath] = programArtifactPaths(unit, directory);
49
+ if (specPath === undefined ||
50
+ featurePath === undefined ||
51
+ traceabilityPath === undefined ||
52
+ mappingPath === undefined) {
53
+ return "not-started";
54
+ }
55
+ if ((yield* files.read(specPath)) !== undefined) {
56
+ return "skipped";
57
+ }
58
+ const artifacts = yield* extract(unit);
59
+ yield* files.writeAtomic(specPath, artifacts.spec);
60
+ yield* files.writeAtomic(featurePath, artifacts.feature);
61
+ yield* files.writeAtomic(traceabilityPath, artifacts.traceability);
62
+ yield* files.writeAtomic(mappingPath, artifacts.mapping);
63
+ if (options.onCreated !== undefined) {
64
+ yield* options.onCreated(unit);
65
+ }
66
+ return "created";
67
+ });
68
+ const outcomes = yield* Effect.forEach(units, (unit) => Effect.result(extractOne(unit)).pipe(Effect.tap((outcome) => (Result.isFailure(outcome) ? Ref.set(halted, true) : Effect.void))), { concurrency });
69
+ const firstFailure = outcomes.find(Result.isFailure);
70
+ if (firstFailure !== undefined) {
71
+ return yield* Effect.fail(firstFailure.failure);
72
+ }
73
+ const created = [];
74
+ const skipped = [];
75
+ for (const [index, outcome] of outcomes.entries()) {
76
+ const unit = units[index];
77
+ if (unit === undefined || Result.isFailure(outcome)) {
78
+ continue;
79
+ }
80
+ if (outcome.success === "created") {
81
+ created.push(unit.name);
82
+ }
83
+ else if (outcome.success === "skipped") {
84
+ skipped.push(unit.name);
85
+ }
86
+ }
87
+ return ResumeSummary.make({ created, skipped });
88
+ });
89
+ export const generateVectorsResumably = Effect.fn("@llm4ts/flow/Artifacts.generateVectorsResumably")(function* (files, programs, generate, directory = "docs/modernization/vectors") {
90
+ const created = [];
91
+ const skipped = [];
92
+ for (const program of programs) {
93
+ const path = `${directory}/${program}.jsonl`;
94
+ if ((yield* files.read(path)) !== undefined) {
95
+ skipped.push(program);
96
+ continue;
97
+ }
98
+ yield* files.writeAtomic(path, yield* generate(program));
99
+ created.push(program);
100
+ }
101
+ return ResumeSummary.make({ created, skipped });
102
+ });
103
+ //# sourceMappingURL=Artifacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Artifacts.js","sourceRoot":"","sources":["../src/Artifacts.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llm4ts/flow",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Effect-native LLM workflow, persistence, review, and repository automation",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -13,6 +13,8 @@
13
13
  "src"
14
14
  ],
15
15
  "exports": {
16
+ "./Approval": "./dist/Approval.js",
17
+ "./Artifacts": "./dist/Artifacts.js",
16
18
  "./package.json": "./package.json",
17
19
  "./Activity": "./dist/Activity.js",
18
20
  "./AzureDevOpsTool": "./dist/AzureDevOpsTool.js",
@@ -89,7 +91,7 @@
89
91
  "typescript"
90
92
  ],
91
93
  "dependencies": {
92
- "@llm4ts/core": "0.17.0"
94
+ "@llm4ts/core": "0.18.0"
93
95
  },
94
96
  "peerDependencies": {
95
97
  "effect": "4.0.0-beta.102"
@@ -0,0 +1,51 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Schema from "effect/Schema"
3
+ import type { FlowError } from "./FlowError.ts"
4
+ import type { PlainFileStoreShape } from "./Persistence.ts"
5
+
6
+ /**
7
+ * The human gates of the modernization phases: a markdown document ends
8
+ * with a draft marker the flow writes, and the next phase refuses to start
9
+ * until a person has ticked it. (Moved from `@llm4ts/modernize` in 0.18.0.)
10
+ */
11
+ export class ApprovalRequired extends Schema.TaggedErrorClass<ApprovalRequired>()(
12
+ "ApprovalRequired",
13
+ {
14
+ path: Schema.String,
15
+ marker: Schema.String
16
+ }
17
+ ) {
18
+ get message(): string {
19
+ return `approval required in ${this.path}: set '${this.marker}'`
20
+ }
21
+ }
22
+
23
+ export class MissingModernizeArtifact extends Schema.TaggedErrorClass<MissingModernizeArtifact>()(
24
+ "MissingModernizeArtifact",
25
+ {
26
+ path: Schema.String,
27
+ message: Schema.String
28
+ }
29
+ ) {}
30
+
31
+ export const ApprovedMarker = "- [x] Approved"
32
+ export const DraftApprovalMarker = "- [ ] Approved"
33
+
34
+ export const withDraftApproval = (markdown: string): string =>
35
+ `${markdown.trimEnd()}\n\n${DraftApprovalMarker}\n`
36
+
37
+ export const requireApproval = Effect.fn("@llm4ts/flow/Approval.requireApproval")(function* (
38
+ files: PlainFileStoreShape,
39
+ path: string
40
+ ): Effect.fn.Return<void, FlowError | ApprovalRequired | MissingModernizeArtifact> {
41
+ const contents = yield* files.read(path)
42
+ if (contents === undefined) {
43
+ return yield* MissingModernizeArtifact.make({
44
+ path,
45
+ message: `approval document does not exist: ${path}`
46
+ })
47
+ }
48
+ if (!contents.includes(ApprovedMarker)) {
49
+ return yield* ApprovalRequired.make({ path, marker: ApprovedMarker })
50
+ }
51
+ })
@@ -0,0 +1,161 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Ref from "effect/Ref"
3
+ import * as Result from "effect/Result"
4
+ import * as Schema from "effect/Schema"
5
+ import type { FlowError } from "./FlowError.ts"
6
+ import type { PlainFileStoreShape } from "./Persistence.ts"
7
+
8
+ export class ProgramUnit extends Schema.Class<ProgramUnit>("ProgramUnit")({
9
+ name: Schema.String,
10
+ sourcePath: Schema.String
11
+ }) {}
12
+
13
+ export class ProgramArtifacts extends Schema.Class<ProgramArtifacts>("ProgramArtifacts")({
14
+ spec: Schema.String,
15
+ feature: Schema.String,
16
+ traceability: Schema.String,
17
+ mapping: Schema.String
18
+ }) {}
19
+
20
+ export class ResumeSummary extends Schema.Class<ResumeSummary>("ResumeSummary")({
21
+ created: Schema.Array(Schema.String),
22
+ skipped: Schema.Array(Schema.String)
23
+ }) {}
24
+
25
+ const programName = (name: string): string => name.toLowerCase()
26
+
27
+ /** The four per-program artifact paths under `directory`, relative to the repository. */
28
+ export const programArtifactPaths = (
29
+ unit: ProgramUnit,
30
+ directory = "docs/modernization"
31
+ ): ReadonlyArray<string> => [
32
+ `${directory}/specs/${unit.name}.md`,
33
+ `${directory}/features/${programName(unit.name)}.feature`,
34
+ `${directory}/traceability/${unit.name}.md`,
35
+ `${directory}/mapping/${unit.name}.md`
36
+ ]
37
+
38
+ export interface ExtractProgramsOptions<E2, R2> {
39
+ /**
40
+ * How many programs extract at once (default 1: the sequential order the
41
+ * units were given in). The programs of a wave are independent — each
42
+ * analyst reads only its source and resolved closure and writes only its
43
+ * own four files — so the bound is about the model seat's quota, not about
44
+ * correctness.
45
+ */
46
+ readonly concurrency?: number
47
+ /**
48
+ * Runs after a program's four artifacts are on disk and before it counts as
49
+ * created — the seam for per-program post-processing (pattern tagging, the
50
+ * program's own commit). Runs under the same concurrency as extraction:
51
+ * anything in it that must not interleave (git) serialises itself.
52
+ */
53
+ readonly onCreated?: (unit: ProgramUnit) => Effect.Effect<void, E2, R2>
54
+ }
55
+
56
+ type Outcome = "created" | "skipped" | "not-started"
57
+
58
+ /**
59
+ * Extracts every unit whose spec is missing, writes its four artifacts, and
60
+ * reports what was created and what was skipped. Resumable by construction:
61
+ * a rerun skips every program whose spec exists.
62
+ *
63
+ * Failure keeps finished work: a program that fails stops NEW programs from
64
+ * starting, but programs already in flight run to completion and land on
65
+ * disk (and through `onCreated`) before the first failure, in unit order, is
66
+ * re-raised. With `concurrency` 1 that is exactly fail-fast; above 1 a quota
67
+ * death costs only the programs it interrupted, and a rerun resumes the rest.
68
+ */
69
+ export const extractProgramsResumably = Effect.fn(
70
+ "@llm4ts/flow/Artifacts.extractProgramsResumably"
71
+ )(function* <E, R, E2 = never, R2 = never>(
72
+ files: PlainFileStoreShape,
73
+ units: ReadonlyArray<ProgramUnit>,
74
+ extract: (unit: ProgramUnit) => Effect.Effect<ProgramArtifacts, E, R>,
75
+ directory = "docs/modernization",
76
+ options: ExtractProgramsOptions<E2, R2> = {}
77
+ ): Effect.fn.Return<ResumeSummary, E | E2 | FlowError, R | R2> {
78
+ const concurrency = Math.max(1, Math.floor(options.concurrency ?? 1))
79
+ const halted = yield* Ref.make(false)
80
+
81
+ const extractOne = (unit: ProgramUnit): Effect.Effect<Outcome, E | E2 | FlowError, R | R2> =>
82
+ Effect.gen(function* () {
83
+ if (yield* Ref.get(halted)) {
84
+ return "not-started"
85
+ }
86
+ const [specPath, featurePath, traceabilityPath, mappingPath] = programArtifactPaths(
87
+ unit,
88
+ directory
89
+ )
90
+ if (
91
+ specPath === undefined ||
92
+ featurePath === undefined ||
93
+ traceabilityPath === undefined ||
94
+ mappingPath === undefined
95
+ ) {
96
+ return "not-started"
97
+ }
98
+ if ((yield* files.read(specPath)) !== undefined) {
99
+ return "skipped"
100
+ }
101
+ const artifacts = yield* extract(unit)
102
+ yield* files.writeAtomic(specPath, artifacts.spec)
103
+ yield* files.writeAtomic(featurePath, artifacts.feature)
104
+ yield* files.writeAtomic(traceabilityPath, artifacts.traceability)
105
+ yield* files.writeAtomic(mappingPath, artifacts.mapping)
106
+ if (options.onCreated !== undefined) {
107
+ yield* options.onCreated(unit)
108
+ }
109
+ return "created"
110
+ })
111
+
112
+ const outcomes = yield* Effect.forEach(
113
+ units,
114
+ (unit) =>
115
+ Effect.result(extractOne(unit)).pipe(
116
+ Effect.tap((outcome) => (Result.isFailure(outcome) ? Ref.set(halted, true) : Effect.void))
117
+ ),
118
+ { concurrency }
119
+ )
120
+
121
+ const firstFailure = outcomes.find(Result.isFailure)
122
+ if (firstFailure !== undefined) {
123
+ return yield* Effect.fail(firstFailure.failure)
124
+ }
125
+ const created: Array<string> = []
126
+ const skipped: Array<string> = []
127
+ for (const [index, outcome] of outcomes.entries()) {
128
+ const unit = units[index]
129
+ if (unit === undefined || Result.isFailure(outcome)) {
130
+ continue
131
+ }
132
+ if (outcome.success === "created") {
133
+ created.push(unit.name)
134
+ } else if (outcome.success === "skipped") {
135
+ skipped.push(unit.name)
136
+ }
137
+ }
138
+ return ResumeSummary.make({ created, skipped })
139
+ })
140
+
141
+ export const generateVectorsResumably = Effect.fn(
142
+ "@llm4ts/flow/Artifacts.generateVectorsResumably"
143
+ )(function* <E, R>(
144
+ files: PlainFileStoreShape,
145
+ programs: ReadonlyArray<string>,
146
+ generate: (program: string) => Effect.Effect<string, E, R>,
147
+ directory = "docs/modernization/vectors"
148
+ ): Effect.fn.Return<ResumeSummary, E | FlowError, R> {
149
+ const created: Array<string> = []
150
+ const skipped: Array<string> = []
151
+ for (const program of programs) {
152
+ const path = `${directory}/${program}.jsonl`
153
+ if ((yield* files.read(path)) !== undefined) {
154
+ skipped.push(program)
155
+ continue
156
+ }
157
+ yield* files.writeAtomic(path, yield* generate(program))
158
+ created.push(program)
159
+ }
160
+ return ResumeSummary.make({ created, skipped })
161
+ })