@llm4ts/flow 0.17.0 → 0.18.1

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.
@@ -1,6 +1,6 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Schema from "effect/Schema";
3
- import { ReviewResult } from "./Review.ts";
3
+ import { ReviewIssue, ReviewResult } from "./Review.ts";
4
4
  import type { WorkspaceError, WorkspaceShape } from "./Workspace.ts";
5
5
  declare const CoverageRule_base: Schema.Class<CoverageRule, Schema.Struct<{
6
6
  readonly name: Schema.String;
@@ -15,8 +15,48 @@ export declare class CoverageRule extends CoverageRule_base {
15
15
  * candidate units, not every file that shares the tree with them.
16
16
  */
17
17
  export declare const matchingFiles: (workspace: WorkspaceShape, regex: string, exclude?: string) => Effect.Effect<ReadonlyArray<string>, WorkspaceError>;
18
+ /** A unit a coverage rule captured, with every file it was captured from. */
19
+ export interface CapturedUnit {
20
+ readonly rule: string;
21
+ readonly unit: string;
22
+ readonly paths: ReadonlyArray<string>;
23
+ }
24
+ /**
25
+ * Every unit each rule captures across the workspace, in first-seen order
26
+ * per rule, each with the files it was found in — the provenance a
27
+ * wave-scoped gate needs to tell a unit of this wave from one of another.
28
+ */
29
+ export declare const capturedUnits: (workspace: WorkspaceShape, rules: readonly CoverageRule[]) => Effect.Effect<readonly CapturedUnit[], WorkspaceError, never>;
18
30
  export declare const coverageUnits: (workspace: WorkspaceShape, rules: readonly CoverageRule[]) => Effect.Effect<Readonly<Record<string, readonly string[]>>, WorkspaceError, never>;
19
- export declare const coverage: (workspace: WorkspaceShape, rules: readonly CoverageRule[], traceability: string) => Effect.Effect<ReviewResult, WorkspaceError, never>;
31
+ export interface CoverageOptions {
32
+ /**
33
+ * Restricts the gate to units captured from at least one file this
34
+ * predicate accepts. A wave-scoped extraction passes the wave's program
35
+ * files here: a unit that lives only in another wave's sources (or in an
36
+ * estate-wide descriptor such as web.xml) is reported in `outOfScope`
37
+ * rather than failing this wave's gate. Absent: every unit gates.
38
+ */
39
+ readonly inScope?: (path: string) => boolean;
40
+ }
41
+ export interface CoverageReport {
42
+ readonly result: ReviewResult;
43
+ /** Uncovered units the scope excluded from the gate, as `rule: unit`. */
44
+ readonly outOfScope: ReadonlyArray<string>;
45
+ }
46
+ /** Coverage with the scope split: what gates, and what was left to a later wave. */
47
+ export declare const coverageReport: (workspace: WorkspaceShape, rules: readonly CoverageRule[], traceability: string, options?: CoverageOptions | undefined) => Effect.Effect<CoverageReport, WorkspaceError, never>;
48
+ export declare const coverage: (workspace: WorkspaceShape, rules: readonly CoverageRule[], traceability: string, options?: CoverageOptions | undefined) => Effect.Effect<ReviewResult, WorkspaceError, never>;
20
49
  export declare const features: (workspace: WorkspaceShape, directory: string) => Effect.Effect<ReviewResult, WorkspaceError, never>;
50
+ /**
51
+ * Deterministic schema check of the specs a pack declares `spec-schema:`
52
+ * for: each program's spec must embed a block the schema decodes. A failure
53
+ * is a per-program Critical finding (titled like a judge finding, so the
54
+ * extraction gate's fix turn repairs that program), never a guess in a
55
+ * later phase — convert-page would otherwise be the first to notice.
56
+ */
57
+ export declare const specSchemaIssues: (schema: string | undefined, specs: readonly {
58
+ readonly name: string;
59
+ readonly markdown: string | undefined;
60
+ }[]) => Effect.Effect<readonly ReviewIssue[], never, never>;
21
61
  export {};
22
62
  //# sourceMappingURL=SpecChecks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpecChecks.d.ts","sourceRoot":"","sources":["../src/SpecChecks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAe,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;;;;;;AAEpE,qBAAa,YAAa,SAAQ,iBAIhC;CAAG;AAcL;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,WAAW,cAAc,EACzB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,cAAc,CAMH,CAAA;AAEnD,eAAO,MAAM,aAAa,kJAuBxB,CAAA;AAEF,eAAO,MAAM,QAAQ,yIAyBnB,CAAA;AA2BF,eAAO,MAAM,QAAQ,sGA+BnB,CAAA"}
1
+ {"version":3,"file":"SpecChecks.d.ts","sourceRoot":"","sources":["../src/SpecChecks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;;;;;;AAEpE,qBAAa,YAAa,SAAQ,iBAIhC;CAAG;AAcL;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,WAAW,cAAc,EACzB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,cAAc,CAMH,CAAA;AAEnD,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACtC;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,8HAwBxB,CAAA;AAEF,eAAO,MAAM,aAAa,kJAYxB,CAAA;AAEF,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CAC7C;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAC3C;AAUD,oFAAoF;AACpF,eAAO,MAAM,cAAc,kLA0BzB,CAAA;AAEF,eAAO,MAAM,QAAQ,gLAOnB,CAAA;AA2BF,eAAO,MAAM,QAAQ,sGA+BnB,CAAA;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;mBAEW,MAAM;uBAAqB,MAAM,GAAG,SAAS;2DAyBnF,CAAA"}
@@ -1,5 +1,6 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Schema from "effect/Schema";
3
+ import { pageSpecShapeHint, parsePageSpec } from "./PageSpec.js";
3
4
  import { ReviewIssue, ReviewResult } from "./Review.js";
4
5
  export class CoverageRule extends Schema.Class("CoverageRule")({
5
6
  name: Schema.String,
@@ -26,43 +27,75 @@ export const matchingFiles = (workspace, regex, exclude) => workspace
26
27
  ...(exclude === undefined ? {} : { excluding: new RegExp(exclude) })
27
28
  })
28
29
  .pipe(Effect.map((paths) => [...paths].sort()));
29
- export const coverageUnits = Effect.fn("@llm4ts/flow/SpecChecks.coverageUnits")(function* (workspace, rules) {
30
+ /**
31
+ * Every unit each rule captures across the workspace, in first-seen order
32
+ * per rule, each with the files it was found in — the provenance a
33
+ * wave-scoped gate needs to tell a unit of this wave from one of another.
34
+ */
35
+ export const capturedUnits = Effect.fn("@llm4ts/flow/SpecChecks.capturedUnits")(function* (workspace, rules) {
30
36
  const paths = yield* workspace.discover();
31
- const result = {};
37
+ const result = [];
32
38
  for (const rule of rules) {
33
39
  const filePattern = new RegExp(rule.files);
34
40
  const unitPattern = new RegExp(rule.unit);
35
- const units = [];
36
41
  for (const path of paths.filter((path) => filePattern.test(path))) {
37
42
  const contents = yield* workspace.read(path);
38
43
  for (const line of contents.split(/\r?\n/)) {
39
44
  for (const unit of capture(unitPattern, line)) {
40
- if (!units.includes(unit)) {
41
- units.push(unit);
45
+ const existing = result.find((entry) => entry.rule === rule.name && entry.unit === unit);
46
+ if (existing === undefined) {
47
+ result.push({ rule: rule.name, unit, paths: [path] });
48
+ }
49
+ else if (!existing.paths.includes(path)) {
50
+ existing.paths.push(path);
42
51
  }
43
52
  }
44
53
  }
45
54
  }
46
- result[rule.name] = units;
47
55
  }
48
56
  return result;
49
57
  });
50
- export const coverage = Effect.fn("@llm4ts/flow/SpecChecks.coverage")(function* (workspace, rules, traceability) {
51
- const units = yield* coverageUnits(workspace, rules);
52
- const issues = rules.flatMap((rule) => (units[rule.name] ?? []).flatMap((unit) => traceability.includes(unit)
53
- ? []
54
- : [
55
- ReviewIssue.make({
56
- severity: "Critical",
57
- title: `uncovered ${rule.name}: ${unit}`,
58
- description: `'${unit}' exists in the legacy source but does not ` +
59
- "appear in the traceability matrix."
60
- })
61
- ]));
62
- return ReviewResult.make({
63
- issues,
64
- summary: issues.length === 0 ? "coverage complete" : `${issues.length} unit(s) uncovered`
65
- });
58
+ export const coverageUnits = Effect.fn("@llm4ts/flow/SpecChecks.coverageUnits")(function* (workspace, rules) {
59
+ const captured = yield* capturedUnits(workspace, rules);
60
+ const result = {};
61
+ for (const rule of rules) {
62
+ result[rule.name] = captured
63
+ .filter((entry) => entry.rule === rule.name)
64
+ .map((entry) => entry.unit);
65
+ }
66
+ return result;
67
+ });
68
+ const uncoveredIssue = (rule, unit) => ReviewIssue.make({
69
+ severity: "Critical",
70
+ title: `uncovered ${rule}: ${unit}`,
71
+ description: `'${unit}' exists in the legacy source but does not ` + "appear in the traceability matrix."
72
+ });
73
+ /** Coverage with the scope split: what gates, and what was left to a later wave. */
74
+ export const coverageReport = Effect.fn("@llm4ts/flow/SpecChecks.coverageReport")(function* (workspace, rules, traceability, options = {}) {
75
+ const captured = yield* capturedUnits(workspace, rules);
76
+ const issues = [];
77
+ const outOfScope = [];
78
+ for (const entry of captured) {
79
+ if (traceability.includes(entry.unit)) {
80
+ continue;
81
+ }
82
+ if (options.inScope === undefined || entry.paths.some(options.inScope)) {
83
+ issues.push(uncoveredIssue(entry.rule, entry.unit));
84
+ }
85
+ else {
86
+ outOfScope.push(`${entry.rule}: ${entry.unit}`);
87
+ }
88
+ }
89
+ return {
90
+ result: ReviewResult.make({
91
+ issues,
92
+ summary: issues.length === 0 ? "coverage complete" : `${issues.length} unit(s) uncovered`
93
+ }),
94
+ outOfScope
95
+ };
96
+ });
97
+ export const coverage = Effect.fn("@llm4ts/flow/SpecChecks.coverage")(function* (workspace, rules, traceability, options = {}) {
98
+ return (yield* coverageReport(workspace, rules, traceability, options)).result;
66
99
  });
67
100
  const featureIssue = (path, contents) => {
68
101
  const lines = contents.split(/\r?\n/).map((line) => line.trim());
@@ -111,4 +144,30 @@ export const features = Effect.fn("@llm4ts/flow/SpecChecks.features")(function*
111
144
  summary: issues.length === 0 ? "features well-formed" : `${issues.length} malformed`
112
145
  });
113
146
  });
147
+ /**
148
+ * Deterministic schema check of the specs a pack declares `spec-schema:`
149
+ * for: each program's spec must embed a block the schema decodes. A failure
150
+ * is a per-program Critical finding (titled like a judge finding, so the
151
+ * extraction gate's fix turn repairs that program), never a guess in a
152
+ * later phase — convert-page would otherwise be the first to notice.
153
+ */
154
+ export const specSchemaIssues = Effect.fn("@llm4ts/flow/SpecChecks.specSchemaIssues")(function* (schema, specs) {
155
+ if (schema !== "pagespec") {
156
+ return [];
157
+ }
158
+ const issues = [];
159
+ for (const spec of specs) {
160
+ const problem = spec.markdown === undefined
161
+ ? "spec file is missing"
162
+ : yield* parsePageSpec(spec.markdown).pipe(Effect.map(() => undefined), Effect.catch((error) => Effect.succeed(error.message)));
163
+ if (problem !== undefined) {
164
+ issues.push(ReviewIssue.make({
165
+ severity: "Critical",
166
+ title: `judge[${spec.name}]: invalid pagespec block`,
167
+ description: `${problem}. ${pageSpecShapeHint}`
168
+ }));
169
+ }
170
+ }
171
+ return issues;
172
+ });
114
173
  //# sourceMappingURL=SpecChecks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpecChecks.js","sourceRoot":"","sources":["../src/SpecChecks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SpecChecks.js","sourceRoot":"","sources":["../src/SpecChecks.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.1",
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.1"
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
+ })
package/src/Pack.ts CHANGED
@@ -41,6 +41,12 @@ export interface Pack {
41
41
  // paths), `<NAME>` substituted with the program name. The seam that makes
42
42
  // per-program judging possible.
43
43
  readonly programFiles: string | undefined
44
+ /**
45
+ * The schema every program spec must embed, validated deterministically
46
+ * by the extraction gate: `pagespec` (a ```json pagespec block decodable
47
+ * as `PageSpec`) is the only one today; absent means prose-only specs.
48
+ */
49
+ readonly specSchema: string | undefined
44
50
  readonly dir: string
45
51
  readonly gate: (name: string) => ReadonlyArray<string> | undefined
46
52
  readonly prompt: (name: string) => string | undefined
@@ -206,6 +212,12 @@ export const loadPack = Effect.fn("@llm4ts/flow/Pack.load")(function* (
206
212
  message: `pack manifest 'programFiles:' is not a valid regex template: ${programFiles}`
207
213
  })
208
214
  }
215
+ const specSchema = fields["spec-schema"]
216
+ if (specSchema !== undefined && specSchema !== "pagespec") {
217
+ return yield* PlanParseError.make({
218
+ message: `pack manifest 'spec-schema:' must be 'pagespec' when set, got: ${specSchema}`
219
+ })
220
+ }
209
221
  const exclude = fields.exclude
210
222
  if (exclude !== undefined && !isValidRegExp(exclude)) {
211
223
  return yield* PlanParseError.make({
@@ -239,6 +251,7 @@ export const loadPack = Effect.fn("@llm4ts/flow/Pack.load")(function* (
239
251
  lenses,
240
252
  lessons: lessons === undefined || lessons.length === 0 ? undefined : lessons,
241
253
  programFiles,
254
+ specSchema,
242
255
  dir: directory,
243
256
  gate: (name) => gates[name],
244
257
  prompt: (name) => prompts[name],