@llm4ts/shell 0.5.0 → 0.6.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.
Files changed (168) hide show
  1. package/dist/Cli.d.ts +2 -2
  2. package/dist/Cli.d.ts.map +1 -1
  3. package/dist/Cli.js +8 -4
  4. package/dist/Cli.js.map +1 -1
  5. package/dist/FlowCatalog.d.ts.map +1 -1
  6. package/dist/FlowCatalog.js +6 -2
  7. package/dist/FlowCatalog.js.map +1 -1
  8. package/flows/fixtures/scaffolds/kafka-streams-service/README.md +12 -0
  9. package/flows/fixtures/scaffolds/kafka-streams-service/pom.xml +49 -0
  10. package/flows/fixtures/scaffolds/kafka-streams-service/scripts/replay.sh +10 -0
  11. package/flows/fixtures/scaffolds/kafka-streams-service/src/main/java/com/meridian/scaffold/Application.java +29 -0
  12. package/flows/fixtures/scaffolds/kafka-streams-service/src/test/java/com/meridian/scaffold/ApplicationTests.java +21 -0
  13. package/flows/fixtures/scaffolds/kafka-streams-service/src/test/resources/features/.gitkeep +0 -0
  14. package/flows/fixtures/scaffolds/nextjs-spa/README.md +6 -0
  15. package/flows/fixtures/scaffolds/nextjs-spa/app/layout.js +11 -0
  16. package/flows/fixtures/scaffolds/nextjs-spa/app/page.js +7 -0
  17. package/flows/fixtures/scaffolds/nextjs-spa/docs/specs/.gitkeep +0 -0
  18. package/flows/fixtures/scaffolds/nextjs-spa/features/.gitkeep +0 -0
  19. package/flows/fixtures/scaffolds/nextjs-spa/next.config.js +7 -0
  20. package/flows/fixtures/scaffolds/nextjs-spa/package.json +15 -0
  21. package/flows/fixtures/scaffolds/nextjs-spa/tests/smoke.test.js +6 -0
  22. package/flows/fixtures/scaffolds/spring-bff/README.md +9 -0
  23. package/flows/fixtures/scaffolds/spring-bff/frontend/app/layout.js +11 -0
  24. package/flows/fixtures/scaffolds/spring-bff/frontend/app/page.js +7 -0
  25. package/flows/fixtures/scaffolds/spring-bff/frontend/next.config.js +7 -0
  26. package/flows/fixtures/scaffolds/spring-bff/frontend/package.json +15 -0
  27. package/flows/fixtures/scaffolds/spring-bff/frontend/tests/smoke.test.js +6 -0
  28. package/flows/fixtures/scaffolds/spring-bff/pom.xml +57 -0
  29. package/flows/fixtures/scaffolds/spring-bff/scripts/test.sh +6 -0
  30. package/flows/fixtures/scaffolds/spring-bff/src/main/java/com/meridian/bff/Application.java +12 -0
  31. package/flows/fixtures/scaffolds/spring-bff/src/main/resources/application.yaml +12 -0
  32. package/flows/fixtures/scaffolds/spring-bff/src/test/java/com/meridian/bff/ApplicationTests.java +12 -0
  33. package/flows/fixtures/scaffolds/spring-bff/src/test/resources/features/.gitkeep +0 -0
  34. package/flows/fixtures/scaffolds/spring-boot-service/README.md +7 -0
  35. package/flows/fixtures/scaffolds/spring-boot-service/pom.xml +53 -0
  36. package/flows/fixtures/scaffolds/spring-boot-service/scripts/replay.sh +9 -0
  37. package/flows/fixtures/scaffolds/spring-boot-service/src/main/java/com/meridian/scaffold/Application.java +12 -0
  38. package/flows/fixtures/scaffolds/spring-boot-service/src/main/resources/application.yaml +12 -0
  39. package/flows/fixtures/scaffolds/spring-boot-service/src/test/java/com/meridian/scaffold/ApplicationTests.java +12 -0
  40. package/flows/fixtures/scaffolds/spring-boot-service/src/test/resources/features/.gitkeep +0 -0
  41. package/flows/implement.js +29 -0
  42. package/flows/issue-pr.js +71 -0
  43. package/flows/judge-suite.js +48 -0
  44. package/flows/local.js +40 -0
  45. package/flows/modernize-bench.js +263 -0
  46. package/flows/modernize-extract.js +454 -0
  47. package/flows/modernize-implement.js +249 -0
  48. package/flows/modernize-review.js +237 -0
  49. package/flows/modernize-seed.js +189 -0
  50. package/flows/modernize-survey.js +0 -0
  51. package/flows/modernize-verify.js +435 -0
  52. package/flows/packs/ace-integration/lessons.md +6 -0
  53. package/flows/packs/ace-integration/pack.md +29 -0
  54. package/flows/packs/ace-integration/prompts/analysis.md +21 -0
  55. package/flows/packs/ace-integration/prompts/bdd.md +13 -0
  56. package/flows/packs/ace-integration/prompts/implement.md +17 -0
  57. package/flows/packs/ace-integration/prompts/plan.md +12 -0
  58. package/flows/packs/ace-integration/prompts/review.md +17 -0
  59. package/flows/packs/ace-integration/prompts/spec.md +29 -0
  60. package/flows/packs/ace-integration/reviewers/routing-fidelity.md +19 -0
  61. package/flows/packs/ace-integration/reviewers/traceability.md +13 -0
  62. package/flows/packs/ace-kafka/lessons.md +6 -0
  63. package/flows/packs/ace-kafka/pack.md +36 -0
  64. package/flows/packs/ace-kafka/prompts/analysis.md +21 -0
  65. package/flows/packs/ace-kafka/prompts/bdd.md +13 -0
  66. package/flows/packs/ace-kafka/prompts/implement.md +34 -0
  67. package/flows/packs/ace-kafka/prompts/plan.md +12 -0
  68. package/flows/packs/ace-kafka/prompts/review.md +17 -0
  69. package/flows/packs/ace-kafka/prompts/spec.md +31 -0
  70. package/flows/packs/ace-kafka/prompts/vectors.md +21 -0
  71. package/flows/packs/ace-kafka/reviewers/event-contracts.md +9 -0
  72. package/flows/packs/ace-kafka/reviewers/routing-fidelity.md +19 -0
  73. package/flows/packs/ace-kafka/reviewers/traceability.md +13 -0
  74. package/flows/packs/cobol-kafka/lessons.md +3 -0
  75. package/flows/packs/cobol-kafka/pack.md +51 -0
  76. package/flows/packs/cobol-kafka/patterns/PAT-STREAM-001-record-to-event.md +7 -0
  77. package/flows/packs/cobol-kafka/patterns/PAT-STREAM-002-job-step-topology.md +6 -0
  78. package/flows/packs/cobol-kafka/patterns/PAT-STREAM-003-checkpoint-idempotence.md +7 -0
  79. package/flows/packs/cobol-kafka/patterns/PAT-STREAM-004-eod-totals-window.md +7 -0
  80. package/flows/packs/cobol-kafka/patterns/PAT-STREAM-005-reject-topic.md +6 -0
  81. package/flows/packs/cobol-kafka/patterns/PAT-STREAM-006-keyed-state-store.md +7 -0
  82. package/flows/packs/cobol-kafka/patterns/PAT-STREAM-007-outbox.md +7 -0
  83. package/flows/packs/cobol-kafka/patterns/PAT-STREAM-008-partition-key.md +7 -0
  84. package/flows/packs/cobol-kafka/patterns/PAT-STREAM-009-batch-window-cutoff.md +7 -0
  85. package/flows/packs/cobol-kafka/patterns/PAT-STREAM-010-replay-backfill.md +7 -0
  86. package/flows/packs/cobol-kafka/prompts/analysis.md +23 -0
  87. package/flows/packs/cobol-kafka/prompts/bdd.md +15 -0
  88. package/flows/packs/cobol-kafka/prompts/implement.md +47 -0
  89. package/flows/packs/cobol-kafka/prompts/plan.md +13 -0
  90. package/flows/packs/cobol-kafka/prompts/review.md +17 -0
  91. package/flows/packs/cobol-kafka/prompts/spec.md +28 -0
  92. package/flows/packs/cobol-kafka/prompts/vectors.md +21 -0
  93. package/flows/packs/cobol-kafka/reviewers/cobol-fidelity.md +16 -0
  94. package/flows/packs/cobol-kafka/reviewers/pattern-conformance.md +10 -0
  95. package/flows/packs/cobol-kafka/reviewers/traceability.md +13 -0
  96. package/flows/packs/cobol-springboot/lessons.md +3 -0
  97. package/flows/packs/cobol-springboot/pack.md +51 -0
  98. package/flows/packs/cobol-springboot/prompts/analysis.md +23 -0
  99. package/flows/packs/cobol-springboot/prompts/bdd.md +15 -0
  100. package/flows/packs/cobol-springboot/prompts/implement.md +31 -0
  101. package/flows/packs/cobol-springboot/prompts/plan.md +13 -0
  102. package/flows/packs/cobol-springboot/prompts/review.md +17 -0
  103. package/flows/packs/cobol-springboot/prompts/spec.md +28 -0
  104. package/flows/packs/cobol-springboot/prompts/vectors.md +22 -0
  105. package/flows/packs/cobol-springboot/reviewers/cobol-fidelity.md +16 -0
  106. package/flows/packs/cobol-springboot/reviewers/pattern-conformance.md +10 -0
  107. package/flows/packs/cobol-springboot/reviewers/traceability.md +13 -0
  108. package/flows/packs/jsp-bff-nextjs/lessons.md +6 -0
  109. package/flows/packs/jsp-bff-nextjs/pack.md +30 -0
  110. package/flows/packs/jsp-bff-nextjs/prompts/analysis.md +20 -0
  111. package/flows/packs/jsp-bff-nextjs/prompts/bdd.md +10 -0
  112. package/flows/packs/jsp-bff-nextjs/prompts/implement.md +17 -0
  113. package/flows/packs/jsp-bff-nextjs/prompts/plan.md +12 -0
  114. package/flows/packs/jsp-bff-nextjs/prompts/review.md +16 -0
  115. package/flows/packs/jsp-bff-nextjs/prompts/spec.md +27 -0
  116. package/flows/packs/jsp-bff-nextjs/reviewers/bff-boundary.md +18 -0
  117. package/flows/packs/jsp-bff-nextjs/reviewers/traceability.md +13 -0
  118. package/flows/packs/jsp-nextjs/lessons.md +6 -0
  119. package/flows/packs/jsp-nextjs/pack.md +29 -0
  120. package/flows/packs/jsp-nextjs/prompts/analysis.md +21 -0
  121. package/flows/packs/jsp-nextjs/prompts/bdd.md +12 -0
  122. package/flows/packs/jsp-nextjs/prompts/implement.md +17 -0
  123. package/flows/packs/jsp-nextjs/prompts/plan.md +14 -0
  124. package/flows/packs/jsp-nextjs/prompts/review.md +16 -0
  125. package/flows/packs/jsp-nextjs/prompts/spec.md +26 -0
  126. package/flows/packs/jsp-nextjs/reviewers/traceability.md +13 -0
  127. package/flows/packs/jsp-nextjs/reviewers/ux-fidelity.md +16 -0
  128. package/flows/patterns/PAT-COBOL-001-comp3-money.md +7 -0
  129. package/flows/patterns/PAT-COBOL-002-redefines.md +7 -0
  130. package/flows/patterns/PAT-COBOL-003-88-levels.md +7 -0
  131. package/flows/patterns/PAT-COBOL-004-perform-thru.md +7 -0
  132. package/flows/patterns/PAT-COBOL-005-file-status.md +6 -0
  133. package/flows/patterns/PAT-COBOL-006-sqlcode.md +7 -0
  134. package/flows/patterns/PAT-COBOL-007-validation-order.md +7 -0
  135. package/flows/patterns/PAT-COBOL-008-zoned-decimal.md +6 -0
  136. package/flows/patterns/PAT-COBOL-009-occurs-table.md +6 -0
  137. package/flows/patterns/PAT-COBOL-010-copybook-record.md +7 -0
  138. package/flows/patterns/PAT-COBOL-011-batch-counters.md +6 -0
  139. package/flows/patterns/PAT-COBOL-012-commit-frequency.md +7 -0
  140. package/flows/patterns/PAT-COBOL-013-abend.md +6 -0
  141. package/flows/patterns/PAT-COBOL-014-reject-file.md +6 -0
  142. package/flows/patterns/PAT-COBOL-015-accept-date.md +6 -0
  143. package/flows/patterns/PAT-COBOL-016-string-building.md +6 -0
  144. package/flows/patterns/PAT-COBOL-017-cursor-loop.md +6 -0
  145. package/flows/patterns/PAT-COBOL-018-yn-switches.md +6 -0
  146. package/flows/patterns/PAT-COBOL-019-shared-accumulators.md +6 -0
  147. package/flows/patterns/PAT-COBOL-020-tiered-fees.md +7 -0
  148. package/flows/patterns/PAT-COBOL-021-overdraft-floor.md +7 -0
  149. package/flows/patterns/PAT-COBOL-022-jcl-job-net.md +6 -0
  150. package/flows/patterns/PAT-COBOL-023-sort-step.md +6 -0
  151. package/flows/patterns/PAT-COBOL-024-fixed-width-records.md +6 -0
  152. package/flows/patterns/PAT-COBOL-025-audit-rows.md +6 -0
  153. package/flows/sdd.js +114 -0
  154. package/package.json +5 -4
  155. package/src/Cli.ts +13 -4
  156. package/src/FlowCatalog.ts +8 -2
  157. package/flows/implement.ts +0 -38
  158. package/flows/issue-pr.ts +0 -108
  159. package/flows/judge-suite.ts +0 -63
  160. package/flows/local.ts +0 -59
  161. package/flows/modernize-bench.ts +0 -362
  162. package/flows/modernize-extract.ts +0 -633
  163. package/flows/modernize-implement.ts +0 -333
  164. package/flows/modernize-review.ts +0 -343
  165. package/flows/modernize-seed.ts +0 -259
  166. package/flows/modernize-survey.ts +0 -0
  167. package/flows/modernize-verify.ts +0 -611
  168. package/flows/sdd.ts +0 -181
@@ -1,611 +0,0 @@
1
- // Legacy modernization phase 4: prove the implementation equivalent to its spec pack.
2
- //
3
- // Runs rooted at the TARGET repository (`--repo <target>`) behind the enforced
4
- // clean-room wall: equivalence is proven against specs and vectors, never
5
- // against the original code.
6
- //
7
- // 1. Per spec'd program, generate equivalence test vectors from the spec and
8
- // its BDD scenarios — resumable, one .jsonl per program (delete a file to
9
- // regenerate just that program).
10
- // 2. Replay every vector through the pack's `replay:` command (a vector JSON
11
- // on stdin, the resulting observations as a JSON array on stdout) and diff
12
- // the observations under the pack's comparison policy.
13
- // 3. Report rule-by-rule coverage against the frozen rules.txt — the rule
14
- // universe extraction wrote; the target side never re-enumerates it.
15
- // 4. Triage the failures into fix specs plus plan tasks appended for
16
- // modernize-implement, extend the provenance manifest with the report
17
- // hash, commit, and fail while any vector is red.
18
- //
19
- // Run: modernize-verify --repo ~/services/meridian-transfers
20
- import { join } from "node:path"
21
- import * as Effect from "effect/Effect"
22
- import * as Schema from "effect/Schema"
23
- import type { JsonSchema } from "@llm4ts/core/Models"
24
- import {
25
- CurrentEquivSchema,
26
- EquivVector,
27
- Observations,
28
- readEquivVectors,
29
- replayEquivVector,
30
- diffObservations,
31
- writeEquivVectors,
32
- type EquivObservation
33
- } from "@llm4ts/flow/Equiv"
34
- import { renderEquivReport, VectorVerdict } from "@llm4ts/flow/EquivReport"
35
- import { FlowAborted, FlowLlmError, PlanParseError } from "@llm4ts/flow/FlowError"
36
- import { Info } from "@llm4ts/flow/FlowEvents"
37
- import { loadPack, type Pack } from "@llm4ts/flow/Pack"
38
- import { makePlanStore } from "@llm4ts/flow/Persistence"
39
- import { stage } from "@llm4ts/flow/PlanExecution"
40
- import { Plan, Task } from "@llm4ts/flow/Plan"
41
- import { Provenance, makeProvenanceStore } from "@llm4ts/flow/Provenance"
42
- import { matchingFiles } from "@llm4ts/flow/SpecChecks"
43
- import { checkWall, wallBreachMessage } from "@llm4ts/flow/Wall"
44
- import type { WorkspaceShape } from "@llm4ts/flow/Workspace"
45
- import { generateVectorsResumably } from "@llm4ts/modernize/Artifacts"
46
- import { asReadOnly, coderFromEnv } from "@llm4ts/runner/Connectors"
47
- import { resolveFlowInput } from "@llm4ts/runner/FlowArgs"
48
- import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner"
49
- import { nodePlainFileStore } from "@llm4ts/runner/NodePlainFileStore"
50
- import { nodeProcessExecutor } from "@llm4ts/runner/NodeProcessExecutor"
51
- import { makeNodeWorkspace } from "@llm4ts/runner/NodeWorkspace"
52
-
53
- const ModDir = "docs/modernization"
54
-
55
- /**
56
- * The model-facing vector shape: flat maps only, so structured output stays
57
- * schema-simple. `kind` is "record" (an emitted record) or "db" (a mutation).
58
- */
59
- class GeneratedObservation extends Schema.Class<GeneratedObservation>("GeneratedObservation")({
60
- kind: Schema.String,
61
- channel: Schema.String,
62
- op: Schema.String,
63
- key: Schema.Record(Schema.String, Schema.String),
64
- fields: Schema.Record(Schema.String, Schema.String)
65
- }) {}
66
-
67
- class GeneratedVector extends Schema.Class<GeneratedVector>("GeneratedVector")({
68
- id: Schema.String,
69
- rules: Schema.Array(Schema.String),
70
- inputs: Schema.Record(Schema.String, Schema.String),
71
- observations: Schema.Array(GeneratedObservation)
72
- }) {}
73
-
74
- class GeneratedVectors extends Schema.Class<GeneratedVectors>("GeneratedVectors")({
75
- vectors: Schema.Array(GeneratedVector)
76
- }) {}
77
-
78
- const generatedVectorsJsonSchema: JsonSchema = {
79
- type: "object",
80
- properties: {
81
- vectors: {
82
- type: "array",
83
- items: {
84
- type: "object",
85
- properties: {
86
- id: { type: "string" },
87
- rules: { type: "array", items: { type: "string" } },
88
- inputs: { type: "object", additionalProperties: { type: "string" } },
89
- observations: {
90
- type: "array",
91
- items: {
92
- type: "object",
93
- properties: {
94
- kind: { type: "string", enum: ["record", "db"] },
95
- channel: { type: "string" },
96
- op: { type: "string" },
97
- key: { type: "object", additionalProperties: { type: "string" } },
98
- fields: { type: "object", additionalProperties: { type: "string" } }
99
- },
100
- required: ["kind", "channel", "op", "key", "fields"]
101
- }
102
- }
103
- },
104
- required: ["id", "rules", "inputs", "observations"]
105
- }
106
- }
107
- },
108
- required: ["vectors"]
109
- }
110
-
111
- class FixSpec extends Schema.Class<FixSpec>("FixSpec")({
112
- title: Schema.String,
113
- spec: Schema.String,
114
- taskTitle: Schema.String,
115
- taskDescription: Schema.String
116
- }) {}
117
-
118
- class VerifyOutcome extends Schema.Class<VerifyOutcome>("VerifyOutcome")({
119
- fixes: Schema.Array(FixSpec)
120
- }) {}
121
-
122
- const verifyOutcomeJsonSchema: JsonSchema = {
123
- type: "object",
124
- properties: {
125
- fixes: {
126
- type: "array",
127
- items: {
128
- type: "object",
129
- properties: {
130
- title: { type: "string" },
131
- spec: { type: "string" },
132
- taskTitle: { type: "string" },
133
- taskDescription: { type: "string" }
134
- },
135
- required: ["title", "spec", "taskTitle", "taskDescription"]
136
- }
137
- }
138
- },
139
- required: ["fixes"]
140
- }
141
-
142
- const slug = (title: string): string =>
143
- title
144
- .toLowerCase()
145
- .replace(/[^a-z0-9]+/g, "-")
146
- .replace(/^-|-$/g, "")
147
- .slice(0, 60)
148
-
149
- const toObservation = (
150
- vectorId: string,
151
- generated: GeneratedObservation
152
- ): Effect.Effect<EquivObservation, PlanParseError> => {
153
- switch (generated.kind.toLowerCase()) {
154
- case "record":
155
- return Effect.succeed(Observations.record(generated.channel, generated.fields))
156
- case "db":
157
- return Effect.succeed(
158
- Observations.dbMutation(generated.channel, generated.op, generated.key, generated.fields)
159
- )
160
- default:
161
- return Effect.fail(
162
- PlanParseError.make({
163
- message: `vector ${vectorId}: unknown observation kind '${generated.kind}' (expected record|db)`
164
- })
165
- )
166
- }
167
- }
168
-
169
- const toVector = Effect.fn("modernize-verify.toVector")(function* (
170
- program: string,
171
- generated: GeneratedVector
172
- ) {
173
- const observations: Array<EquivObservation> = []
174
- for (const observation of generated.observations) {
175
- observations.push(yield* toObservation(generated.id, observation))
176
- }
177
- return EquivVector.make({
178
- schemaVersion: CurrentEquivSchema,
179
- program,
180
- id: generated.id,
181
- tier: "generated",
182
- rules: generated.rules,
183
- inputs: generated.inputs,
184
- observations
185
- })
186
- })
187
-
188
- const generatePrompt = (
189
- pack: Pack,
190
- program: string,
191
- spec: string,
192
- feature: string,
193
- rules: ReadonlyArray<string>
194
- ): string =>
195
- [
196
- pack.prompt("vectors") ?? "",
197
- "",
198
- `Generate equivalence test vectors for the program ${program} from its behavioural spec and`,
199
- "BDD scenarios below. Vectors are the generated tier: they prove spec conformance, so cover",
200
- "every rule the spec states — normal paths, boundary values (thresholds exactly at/over/under),",
201
- "and error paths (rejects, insufficient funds, validation order).",
202
- "",
203
- "For each vector:",
204
- '- "id": short kebab-case name describing the case.',
205
- '- "rules": the source units it exercises, chosen ONLY from this list (use the exact names).',
206
- rules.join("\n"),
207
- '- "inputs": a flat string map — the fields the replay harness needs to drive one execution',
208
- " (amounts as plain decimal strings, dates ISO, codes verbatim from the spec).",
209
- '- "observations": the EXACT outcomes the spec promises, in order. kind "record" for emitted',
210
- ' records ("channel" names the output, "fields" carries values, "op" and "key" empty); kind',
211
- ' "db" for database mutations ("channel" = table, "op" = insert/update/delete, "key" addresses',
212
- ' the row, "fields" = the values written). Every amount, status, and reason code must come',
213
- " from the spec — never invent values.",
214
- "",
215
- "Aim for 5–8 vectors: the happy path, each boundary, each reject.",
216
- "",
217
- "Spec:",
218
- spec,
219
- "",
220
- "Scenarios:",
221
- feature
222
- ].join("\n")
223
-
224
- const triagePrompt = (
225
- pack: Pack,
226
- failing: ReadonlyArray<VectorVerdict>,
227
- specText: string
228
- ): string => {
229
- const details = failing
230
- .map((verdict) => {
231
- const mismatches = verdict.mismatches
232
- .map((mismatch) => {
233
- switch (mismatch._tag) {
234
- case "FieldDiff":
235
- return ` - ${mismatch.at}: ${mismatch.field} expected ${mismatch.expected}, actual ${mismatch.actual}`
236
- case "Missing":
237
- return ` - missing: ${JSON.stringify(mismatch.expected)}`
238
- case "Unexpected":
239
- return ` - unexpected: ${JSON.stringify(mismatch.actual)}`
240
- }
241
- })
242
- .join("\n")
243
- return `- ${verdict.vector.program} ${verdict.vector.id} (rules: ${verdict.vector.rules.join(", ")})\n${mismatches}`
244
- })
245
- .join("\n")
246
- return [
247
- pack.prompt("review") ?? "",
248
- "",
249
- "The equivalence harness replayed test vectors against the implementation and found the",
250
- "mismatches below. For each DISTINCT root cause produce one fix: a short spec document",
251
- "(Markdown: the rule violated, expected vs actual behaviour, the failing vector ids) and a",
252
- "plan task (title + description naming the spec rules). Group mismatches sharing a cause.",
253
- "If a mismatch reveals a wrong or ambiguous SPEC rather than wrong code, say so explicitly",
254
- "in that fix document — spec gaps go back to extraction, not to the coder.",
255
- "",
256
- "Mismatches:",
257
- details,
258
- "",
259
- "Specs under test:",
260
- specText
261
- ].join("\n")
262
- }
263
-
264
- /** The spec'd programs: top-level `<NAME>.md` files under the specs dir, indexes aside. */
265
- const specPrograms = Effect.fn("modernize-verify.specPrograms")(function* (
266
- target: WorkspaceShape,
267
- specsDir: string
268
- ) {
269
- const paths = yield* matchingFiles(target, `^${specsDir}/[^/]+\\.md$`).pipe(
270
- Effect.orElseSucceed(() => [])
271
- )
272
- const excluded = new Set(["traceability", "mapping", "README"])
273
- return paths
274
- .map((path) => (path.split("/").at(-1) ?? path).replace(/\.md$/, ""))
275
- .filter((name) => !excluded.has(name))
276
- .sort()
277
- })
278
-
279
- const program = Effect.gen(function* () {
280
- const input = yield* resolveFlowInput("Prove the implementation equivalent to its spec pack")
281
- const packDir = process.env.LLM4TS_PACK ?? "packs/cobol-springboot"
282
- const coder = coderFromEnv(process.env)
283
- const files = nodePlainFileStore
284
- const planPath = join(input.workDir, ModDir, "plan.md")
285
- const vectorsDir = join(input.workDir, ModDir, "vectors")
286
-
287
- yield* runNode(
288
- {
289
- workDir: input.workDir,
290
- workspace: input.workspace,
291
- userPrompt: input.prompt,
292
- coder,
293
- reasoning: asReadOnly(coder),
294
- environment: process.env
295
- },
296
- (context) =>
297
- Effect.gen(function* () {
298
- const launchWorkspace = yield* makeNodeWorkspace(input.workspace)
299
- const target = yield* makeNodeWorkspace(input.workDir)
300
- const pack = yield* stage(context.events, "pack", loadPack(launchWorkspace, packDir))
301
-
302
- yield* stage(
303
- context.events,
304
- "wall",
305
- Effect.gen(function* () {
306
- if (pack.sources === undefined) {
307
- return yield* context.events.publish(
308
- Info.make({ message: "pack has no sources regex — wall check skipped" })
309
- )
310
- }
311
- const result = yield* checkWall(target, pack.sources)
312
- if (result._tag === "Breached") {
313
- return yield* FlowAborted.make({
314
- message: wallBreachMessage(
315
- result,
316
- "Equivalence is proven against specs and vectors, never against the original code."
317
- )
318
- })
319
- }
320
- yield* context.events.publish(
321
- Info.make({ message: "clean-room wall: no legacy source in the target workspace" })
322
- )
323
- })
324
- )
325
-
326
- const rulesText = (yield* files.read(join(input.workDir, pack.specsDir, "rules.txt"))) ?? ""
327
- const universe = rulesText
328
- .split(/\r?\n/)
329
- .map((line) => line.trim())
330
- .filter((line) => line.length > 0)
331
- if (universe.length === 0) {
332
- yield* context.events.publish(
333
- Info.make({
334
- message:
335
- "no rules.txt in the seeded spec pack — the report will use the vectors' own " +
336
- "rules and cannot flag unexercised ones"
337
- })
338
- )
339
- }
340
-
341
- const programs = yield* stage(
342
- context.events,
343
- "programs",
344
- specPrograms(target, pack.specsDir)
345
- )
346
- if (programs.length === 0) {
347
- return yield* FlowAborted.make({
348
- message: `no specs under ${pack.specsDir} — run modernize-seed first`
349
- })
350
- }
351
-
352
- // Generated-first and resumable per program: an existing .jsonl is kept.
353
- yield* stage(
354
- context.events,
355
- "vectors",
356
- Effect.gen(function* () {
357
- const summary = yield* generateVectorsResumably(
358
- files,
359
- programs,
360
- (name) =>
361
- Effect.gen(function* () {
362
- const spec =
363
- (yield* files.read(join(input.workDir, pack.specsDir, `${name}.md`))) ?? ""
364
- const featurePaths = yield* matchingFiles(
365
- target,
366
- `^${pack.featuresDir}/.*\\.feature$`
367
- ).pipe(Effect.orElseSucceed(() => []))
368
- const featurePath = featurePaths.find(
369
- (path) =>
370
- (path.split("/").at(-1) ?? "").replace(/\.feature$/, "").toLowerCase() ===
371
- name.toLowerCase()
372
- )
373
- const feature =
374
- featurePath === undefined
375
- ? ""
376
- : yield* target.read(featurePath).pipe(Effect.orElseSucceed(() => ""))
377
- const generated = yield* context.reasoning
378
- .executeStructured(
379
- generatePrompt(pack, name, spec, feature, universe),
380
- GeneratedVectors,
381
- generatedVectorsJsonSchema
382
- )
383
- .pipe(Effect.mapError(FlowLlmError.from))
384
- if (generated.vectors.length === 0) {
385
- return yield* FlowAborted.make({
386
- message: `generator produced no vectors for ${name}`
387
- })
388
- }
389
- const vectors: Array<EquivVector> = []
390
- for (const candidate of generated.vectors) {
391
- vectors.push(yield* toVector(name, candidate))
392
- }
393
- // generateVectorsResumably persists the returned text, so the
394
- // encoder writes into a scratch path and hands back its body.
395
- const scratch = join(vectorsDir, `.${name}.tmp`)
396
- yield* writeEquivVectors(files, scratch, vectors)
397
- const encoded = (yield* files.read(scratch)) ?? ""
398
- yield* files.remove(scratch)
399
- yield* context.events.publish(
400
- Info.make({ message: `${vectors.length} vector(s) generated for ${name}` })
401
- )
402
- return encoded
403
- }),
404
- vectorsDir
405
- )
406
- if (summary.skipped.length > 0) {
407
- yield* context.events.publish(
408
- Info.make({
409
- message: `vectors exist for ${summary.skipped.join(", ")} — skipping`
410
- })
411
- )
412
- }
413
- })
414
- )
415
-
416
- if (pack.replay === undefined) {
417
- return yield* FlowAborted.make({
418
- message:
419
- `pack '${pack.name}' has no replay: command — add one (reads a vector JSON on ` +
420
- "stdin, prints the resulting observations as a JSON array on stdout)"
421
- })
422
- }
423
- const replayCommand = pack.replay
424
-
425
- const verdicts = yield* stage(
426
- context.events,
427
- "replay",
428
- Effect.gen(function* () {
429
- const vectorFiles = yield* matchingFiles(target, `^${ModDir}/vectors/.*\\.jsonl$`).pipe(
430
- Effect.orElseSucceed(() => [])
431
- )
432
- const vectors: Array<EquivVector> = []
433
- for (const path of [...vectorFiles].sort()) {
434
- vectors.push(...(yield* readEquivVectors(files, join(input.workDir, path))))
435
- }
436
- if (vectors.length === 0) {
437
- return yield* FlowAborted.make({
438
- message: `no vectors under ${vectorsDir} — nothing to replay`
439
- })
440
- }
441
- const results: Array<VectorVerdict> = []
442
- for (const vector of vectors) {
443
- const replayed = yield* replayEquivVector(
444
- nodeProcessExecutor,
445
- context.events,
446
- replayCommand,
447
- input.workDir,
448
- vector
449
- )
450
- if (replayed._tag === "Crashed") {
451
- yield* context.events.publish(
452
- Info.make({
453
- message:
454
- `replay failed for ${vector.program}/${vector.id} ` +
455
- `(exit ${replayed.exitCode}): ${replayed.problem.slice(0, 300)}`
456
- })
457
- )
458
- results.push(
459
- VectorVerdict.make({
460
- vector,
461
- mismatches: vector.observations.map((expected) => ({
462
- _tag: "Missing" as const,
463
- expected
464
- }))
465
- })
466
- )
467
- } else {
468
- results.push(
469
- VectorVerdict.make({
470
- vector,
471
- mismatches: diffObservations(
472
- vector.observations,
473
- replayed.actual,
474
- pack.equivalence
475
- )
476
- })
477
- )
478
- }
479
- }
480
- return results
481
- })
482
- )
483
-
484
- const allRules =
485
- universe.length > 0
486
- ? universe
487
- : [...new Set(verdicts.flatMap((verdict) => verdict.vector.rules))].sort()
488
- const failing = verdicts.filter((verdict) => !verdict.passed)
489
-
490
- yield* stage(
491
- context.events,
492
- "report",
493
- files.writeAtomic(
494
- join(input.workDir, ModDir, "equivalence.md"),
495
- renderEquivReport(verdicts, allRules)
496
- )
497
- )
498
-
499
- if (failing.length > 0) {
500
- yield* stage(
501
- context.events,
502
- "triage",
503
- Effect.gen(function* () {
504
- const specTexts: Array<string> = [
505
- (yield* files.read(join(input.workDir, pack.specsDir, "traceability.md"))) ?? ""
506
- ]
507
- for (const name of programs) {
508
- specTexts.push(
509
- (yield* files.read(join(input.workDir, pack.specsDir, `${name}.md`))) ?? ""
510
- )
511
- }
512
- const outcome = yield* context.reasoning
513
- .executeStructured(
514
- triagePrompt(pack, failing, specTexts.join("\n\n")),
515
- VerifyOutcome,
516
- verifyOutcomeJsonSchema
517
- )
518
- .pipe(Effect.mapError(FlowLlmError.from))
519
- for (const fix of outcome.fixes) {
520
- yield* files.writeAtomic(
521
- join(input.workDir, pack.specsDir, "fixes", `fix-${slug(fix.title)}.md`),
522
- `# ${fix.title}\n\n${fix.spec}\n`
523
- )
524
- }
525
- if (outcome.fixes.length > 0) {
526
- const store = makePlanStore(files)
527
- const plan = yield* store.load(planPath)
528
- if (plan === undefined) {
529
- return yield* FlowAborted.make({
530
- message: `no plan at ${planPath} — run modernize-seed first`
531
- })
532
- }
533
- yield* store.save(
534
- planPath,
535
- Plan.make({
536
- ...plan,
537
- tasks: [
538
- ...plan.tasks,
539
- ...outcome.fixes.map((fix) =>
540
- Task.make({
541
- title: fix.taskTitle,
542
- description: fix.taskDescription,
543
- completed: false
544
- })
545
- )
546
- ]
547
- })
548
- )
549
- yield* context.events.publish(
550
- Info.make({
551
- message: `${outcome.fixes.length} fix task(s) appended — rerun modernize-implement`
552
- })
553
- )
554
- }
555
- })
556
- )
557
- }
558
-
559
- yield* stage(
560
- context.events,
561
- "provenance",
562
- Effect.gen(function* () {
563
- const manifest = join(input.workDir, ModDir, "provenance.json")
564
- if ((yield* files.read(manifest)) === undefined) {
565
- return yield* context.events.publish(
566
- Info.make({ message: "no provenance.json — seeded by an older run; skipping" })
567
- )
568
- }
569
- const provenance = makeProvenanceStore(files)
570
- const hashes = yield* provenance.hashFiles(input.workDir, [`${ModDir}/equivalence.md`])
571
- const report = Object.values(hashes)[0]
572
- // Spreading into a plain object would not satisfy the schema's
573
- // encoder — the manifest must stay a Provenance instance.
574
- yield* provenance.extend(manifest, (current) =>
575
- report === undefined
576
- ? current
577
- : Provenance.make({ ...current, equivalenceReport: report })
578
- )
579
- })
580
- )
581
-
582
- const generated = verdicts.filter((verdict) => verdict.vector.tier === "generated").length
583
- const captured = verdicts.filter((verdict) => verdict.vector.tier === "captured").length
584
- const summary =
585
- `${verdicts.filter((verdict) => verdict.passed).length}/${verdicts.length} vectors green ` +
586
- `(${generated} generated, ${captured} captured)`
587
-
588
- yield* stage(
589
- context.events,
590
- "commit",
591
- context.git
592
- .commitAll(
593
- `modernize(${pack.name}): verify — ${summary}` +
594
- (failing.length > 0 ? `; ${failing.length} failing triaged` : "")
595
- )
596
- .pipe(Effect.asVoid)
597
- )
598
-
599
- if (failing.length > 0) {
600
- return yield* FlowAborted.make({
601
- message:
602
- `equivalence not proven: ${failing.length} failing vector(s) — see ` +
603
- `${ModDir}/equivalence.md; fix specs filed, rerun modernize-implement`
604
- })
605
- }
606
- yield* context.events.publish(Info.make({ message: summary }))
607
- })
608
- )
609
- })
610
-
611
- runFlowMain(program)