@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,333 +0,0 @@
1
- // Legacy modernization phase 3: implement the seeded plan behind the pack's gates.
2
- //
3
- // Runs rooted at the TARGET repository (`--repo <target>`), from the specs
4
- // alone. The clean-room wall is ENFORCED, not advised: the flow refuses to
5
- // start when anything matching the pack's legacy `sources:` regex sits in the
6
- // target workspace, so the coder provably never reads legacy source.
7
- //
8
- // Per plan task: a shared chat implements it, the pack's reviewer lenses plus
9
- // the minimal roster review the diff behind a gate (build for the first,
10
- // tests-encoding task; test for the rest), and the task is committed. The
11
- // first task must leave the acceptance tests RED — tests that pass before any
12
- // implementation encode nothing, so the flow aborts. Pattern cards cited by
13
- // the seeded specs are injected into the coder's brief as an advisory
14
- // translation playbook (the specs still win).
15
- //
16
- // After the loop the verify gate must be green, then a spec-compliance judge
17
- // scores the whole branch against the committed specs and feeds sub-bar
18
- // reasoning back to the coder, bounded by LLM4TS_JUDGE_ROUNDS (default 2).
19
- //
20
- // Run: modernize-implement --repo ~/services/meridian-transfers
21
- import { join } from "node:path"
22
- import * as Effect from "effect/Effect"
23
- import { Dimension, Sample } from "@llm4ts/core/eval/Eval"
24
- import { judge } from "@llm4ts/core/eval/Judge"
25
- import { makeChat } from "@llm4ts/flow/Chat"
26
- import { FlowAborted, FlowLlmError, type FlowError } from "@llm4ts/flow/FlowError"
27
- import { Info } from "@llm4ts/flow/FlowEvents"
28
- import { loadPack } from "@llm4ts/flow/Pack"
29
- import { loadPatternCards, taggedPatternIds } from "@llm4ts/flow/Patterns"
30
- import { makePlanStore } from "@llm4ts/flow/Persistence"
31
- import { implementTaskLoop, stage } from "@llm4ts/flow/PlanExecution"
32
- import {
33
- lintCommand,
34
- minimalReviewers,
35
- reviewAndFixLoop,
36
- type ReviewIssue,
37
- type ReviewResult
38
- } from "@llm4ts/flow/Review"
39
- import { checkWall, wallBreachMessage } from "@llm4ts/flow/Wall"
40
- import type { WorkspaceShape } from "@llm4ts/flow/Workspace"
41
- import { asReadOnly, coderFromEnv } from "@llm4ts/runner/Connectors"
42
- import { resolveFlowInput } from "@llm4ts/runner/FlowArgs"
43
- import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner"
44
- import { nodePlainFileStore } from "@llm4ts/runner/NodePlainFileStore"
45
- import { nodeProcessExecutor } from "@llm4ts/runner/NodeProcessExecutor"
46
- import { makeNodeWorkspace } from "@llm4ts/runner/NodeWorkspace"
47
-
48
- const ModDir = "docs/modernization"
49
-
50
- const judgeRounds = (): number => {
51
- const raw = Number.parseInt(process.env.LLM4TS_JUDGE_ROUNDS ?? "", 10)
52
- return Number.isFinite(raw) && raw > 0 ? raw : 2
53
- }
54
-
55
- const complianceDimensions = [
56
- Dimension.make({
57
- name: "spec-compliance",
58
- rubric:
59
- "Does the implementation satisfy every rule in the committed specs — exact values, " +
60
- "validation order, error paths — without weakening, deleting, or loosening any test or scenario?"
61
- }),
62
- Dimension.make({
63
- name: "scenario-coverage",
64
- rubric:
65
- "Is every BDD scenario in the seeded feature files exercised by an acceptance test in this diff?"
66
- })
67
- ]
68
-
69
- /** Concatenates the committed specs — the judge's contract text. */
70
- const gatherSpecs = Effect.fn("modernize-implement.gatherSpecs")(function* (
71
- target: WorkspaceShape,
72
- specsDir: string
73
- ) {
74
- const paths = yield* target.discover(`${specsDir}/**`).pipe(Effect.orElseSucceed(() => []))
75
- const parts: Array<string> = []
76
- for (const path of [...paths].sort()) {
77
- const text = yield* target.read(path).pipe(Effect.orElseSucceed(() => ""))
78
- if (text.trim().length > 0) {
79
- parts.push(`===== ${path} =====\n${text}`)
80
- }
81
- }
82
- return parts.join("\n\n")
83
- })
84
-
85
- const issueText = (issues: ReadonlyArray<ReviewIssue>): string =>
86
- issues.map((issue) => `${issue.title}\n${issue.description}`.trim()).join("\n\n")
87
-
88
- const program = Effect.gen(function* () {
89
- const input = yield* resolveFlowInput("Implement the seeded modernization plan")
90
- const packDir = process.env.LLM4TS_PACK ?? "packs/cobol-springboot"
91
- const coder = coderFromEnv(process.env)
92
- const files = nodePlainFileStore
93
- const planPath = join(input.workDir, ModDir, "plan.md")
94
-
95
- yield* runNode(
96
- {
97
- workDir: input.workDir,
98
- workspace: input.workspace,
99
- userPrompt: input.prompt,
100
- coder,
101
- reasoning: asReadOnly(coder),
102
- reviewers: [asReadOnly(coder)],
103
- environment: process.env
104
- },
105
- (context) =>
106
- Effect.gen(function* () {
107
- const launchWorkspace = yield* makeNodeWorkspace(input.workspace)
108
- const target = yield* makeNodeWorkspace(input.workDir)
109
- const pack = yield* stage(context.events, "pack", loadPack(launchWorkspace, packDir))
110
-
111
- yield* stage(
112
- context.events,
113
- "wall",
114
- Effect.gen(function* () {
115
- if (pack.sources === undefined) {
116
- return yield* context.events.publish(
117
- Info.make({ message: "pack has no sources regex — wall check skipped" })
118
- )
119
- }
120
- const result = yield* checkWall(target, pack.sources)
121
- if (result._tag === "Breached") {
122
- return yield* FlowAborted.make({
123
- message: wallBreachMessage(
124
- result,
125
- "The implementation must be driven by the specs alone; remove the files and rerun."
126
- )
127
- })
128
- }
129
- yield* context.events.publish(
130
- Info.make({ message: "clean-room wall: no legacy source in the target workspace" })
131
- )
132
- })
133
- )
134
-
135
- const store = makePlanStore(files)
136
- const plan = yield* store.load(planPath)
137
- if (plan === undefined) {
138
- return yield* FlowAborted.make({
139
- message: `no plan at ${planPath} — run modernize-seed first`
140
- })
141
- }
142
-
143
- const gate = (name: string): Effect.Effect<ReviewResult, FlowError> | undefined => {
144
- const command = pack.gate(name)
145
- return command === undefined
146
- ? undefined
147
- : lintCommand(nodeProcessExecutor, context.events, command, input.workDir)
148
- }
149
- const buildGate = gate("build")
150
- const testGate = gate("test")
151
- const verifyGate = gate("verify") ?? testGate
152
-
153
- yield* stage(
154
- context.events,
155
- "branch",
156
- context.git.checkoutOrCreate(plan.epicId).pipe(Effect.asVoid)
157
- )
158
-
159
- // Pattern selection is deterministic: extraction tagged each program's
160
- // fragment with the cards its SOURCE matched, the specs carry those
161
- // ids, and only the cited cards reach the brief.
162
- const specText = yield* gatherSpecs(target, pack.specsDir)
163
- const cards = [
164
- ...(yield* loadPatternCards(launchWorkspace, `${packDir}/patterns`)),
165
- ...(yield* loadPatternCards(launchWorkspace, "patterns"))
166
- ]
167
- const cited = new Set(taggedPatternIds(specText))
168
- const playbook = cards.filter((card) => cited.has(card.id))
169
- const system = [
170
- pack.prompt("implement"),
171
- pack.lessons === undefined
172
- ? undefined
173
- : `Lessons from previous modernization runs — apply them:\n${pack.lessons}`,
174
- playbook.length === 0
175
- ? undefined
176
- : "Pattern cards cited by the specs — the translation playbook (advisory, the specs win):\n\n" +
177
- playbook.map((card) => `### ${card.id}\n${card.body}`).join("\n\n")
178
- ]
179
- .filter((part) => part !== undefined)
180
- .join("\n\n")
181
- if (playbook.length > 0) {
182
- yield* context.events.publish(
183
- Info.make({ message: `${playbook.length} pattern card(s) cited by the specs` })
184
- )
185
- }
186
-
187
- const coderChat = yield* makeChat(context.coder, { system })
188
- const firstTitle = plan.tasks[0]?.title
189
-
190
- yield* implementTaskLoop(store, context.events, planPath, plan, (task) =>
191
- Effect.gen(function* () {
192
- const testsTask = task.title === firstTitle
193
- yield* coderChat.ask(plan.taskPrompt(task))
194
- yield* reviewAndFixLoop({
195
- reviewers: [...minimalReviewers, ...pack.lenses],
196
- reviewerService: context.reviewers[0] ?? context.reasoning,
197
- coder: coderChat,
198
- taskTitle: task.title,
199
- currentDiff: context.git.diffAll,
200
- changedFiles: context.git.defaultBase.pipe(
201
- Effect.flatMap((base) => context.git.changedFilesVsBase(base))
202
- ),
203
- events: context.events,
204
- ...(testsTask
205
- ? buildGate === undefined
206
- ? {}
207
- : { lint: buildGate }
208
- : testGate === undefined
209
- ? {}
210
- : { lint: testGate }),
211
- parallelism: 1
212
- })
213
- if (testsTask && testGate !== undefined) {
214
- const red = yield* testGate
215
- if (red.isClean) {
216
- return yield* FlowAborted.make({
217
- message:
218
- "the new acceptance tests pass before any implementation — they encode nothing"
219
- })
220
- }
221
- }
222
- yield* context.git.commitAll(`${plan.epicId}: ${task.title}`).pipe(Effect.asVoid)
223
- })
224
- )
225
-
226
- // The task loop marks each task complete AFTER its per-task commit, so
227
- // the final task's plan update would otherwise be left uncommitted.
228
- // A no-op when the loop already committed everything.
229
- yield* context.git.commitAll(`${plan.epicId}: plan state`).pipe(Effect.asVoid)
230
-
231
- if (verifyGate !== undefined) {
232
- yield* stage(
233
- context.events,
234
- "verify",
235
- Effect.gen(function* () {
236
- const result = yield* verifyGate
237
- if (!result.isClean) {
238
- return yield* FlowAborted.make({
239
- message: `verify gate failed:\n${issueText(result.issues)}`
240
- })
241
- }
242
- })
243
- )
244
- }
245
-
246
- // The branch-level judge: bounded rounds of feedback, each re-gated and
247
- // committed, failing the flow if the bar is never cleared.
248
- yield* stage(
249
- context.events,
250
- "judge",
251
- Effect.gen(function* () {
252
- const contractText = yield* gatherSpecs(target, pack.specsDir)
253
- const complianceJudge = judge(context.reasoning, complianceDimensions)
254
- const rounds = judgeRounds()
255
- for (let round = 1; round <= rounds; round += 1) {
256
- const base = yield* context.git.defaultBase
257
- const diff = yield* context.git.diffVsBase(base)
258
- const scored = yield* complianceJudge
259
- .evaluate(
260
- Sample.make({
261
- response: diff,
262
- context: contractText,
263
- query: input.prompt
264
- })
265
- )
266
- .pipe(Effect.mapError(FlowLlmError.from))
267
- const below = scored.scores.filter((score) => {
268
- const max = complianceDimensions.find((d) => d.name === score.name)?.maxScore ?? 2
269
- return score.score < max
270
- })
271
- if (below.length === 0) {
272
- return yield* context.events.publish(
273
- Info.make({ message: "spec-compliance judge: branch cleared the bar" })
274
- )
275
- }
276
- if (round >= rounds) {
277
- return yield* FlowAborted.make({
278
- message:
279
- `spec-compliance judge not cleared after ${rounds} round(s):\n` +
280
- below.map((d) => `- ${d.name} ${d.score}: ${d.reasoning}`).join("\n")
281
- })
282
- }
283
- yield* coderChat.ask(
284
- [
285
- "The final spec-compliance review scored the branch below the bar. Close these gaps",
286
- "without weakening any test, then stop:",
287
- ...below.map((d) => `- ${d.name} (${d.score}): ${d.reasoning}`)
288
- ].join("\n")
289
- )
290
- if (verifyGate !== undefined) {
291
- const regated = yield* verifyGate
292
- if (!regated.isClean) {
293
- return yield* FlowAborted.make({
294
- message: "verify gate broke while addressing judge feedback"
295
- })
296
- }
297
- }
298
- yield* context.git
299
- .commitAll(`${plan.epicId}: address spec-compliance feedback`)
300
- .pipe(Effect.asVoid)
301
- }
302
- })
303
- )
304
-
305
- // Publishing is best-effort: a repository with no remote or forge is a
306
- // normal local run, not a failure.
307
- yield* stage(
308
- context.events,
309
- "publish",
310
- Effect.gen(function* () {
311
- const base = yield* context.git.defaultBase
312
- yield* context.git.push("origin", plan.epicId)
313
- const pr = yield* context.hosting.createPr(
314
- `modernize: ${plan.epicId}`,
315
- `Implements the approved spec pack. Plan: ${ModDir}/plan.md — all gates green.`,
316
- base
317
- )
318
- yield* context.events.publish(Info.make({ message: `PR: ${pr.url}` }))
319
- }).pipe(
320
- Effect.catch((error) =>
321
- context.events.publish(
322
- Info.make({
323
- message: `publish skipped (no remote/forge configured): ${error.message}`
324
- })
325
- )
326
- )
327
- )
328
- )
329
- })
330
- )
331
- })
332
-
333
- runFlowMain(program)
@@ -1,343 +0,0 @@
1
- // Legacy modernization phase 5: review the increment against its spec pack and distil lessons.
2
- //
3
- // Runs rooted at the TARGET repository (`--repo <target>`) behind the enforced
4
- // clean-room wall — the review judges spec-driven work only.
5
- //
6
- // 1. The full reviewer roster plus the pack's own lenses (filtered to the
7
- // files this branch touched) review the diff against the committed specs.
8
- // 2. A spec-compliance judge scores the branch on the same two dimensions
9
- // modernize-implement gates on.
10
- // 3. Findings and scores are distilled into: fixes (spec VIOLATIONS, which
11
- // become fix specs plus plan tasks for another implement pass),
12
- // improvements (worthwhile but not violations), and lessons — rules of
13
- // thumb that generalize, appended to the pack's lessons.md so the next
14
- // estate starts smarter.
15
- //
16
- // Run: modernize-review --repo ~/services/meridian-transfers
17
- import { join } from "node:path"
18
- import * as Effect from "effect/Effect"
19
- import * as Schema from "effect/Schema"
20
- import { Dimension, Sample, type EvalResult } from "@llm4ts/core/eval/Eval"
21
- import { judge } from "@llm4ts/core/eval/Judge"
22
- import type { JsonSchema } from "@llm4ts/core/Models"
23
- import { FlowAborted, FlowLlmError } from "@llm4ts/flow/FlowError"
24
- import { Info } from "@llm4ts/flow/FlowEvents"
25
- import { appendPackLesson, loadPack, type Pack } from "@llm4ts/flow/Pack"
26
- import { makePlanStore } from "@llm4ts/flow/Persistence"
27
- import { Plan, Task } from "@llm4ts/flow/Plan"
28
- import { stage } from "@llm4ts/flow/PlanExecution"
29
- import {
30
- allReviewers,
31
- mergeReviewResults,
32
- reviewJsonSchema,
33
- reviewPrompt,
34
- ReviewResult
35
- } from "@llm4ts/flow/Review"
36
- import { checkWall, wallBreachMessage } from "@llm4ts/flow/Wall"
37
- import type { WorkspaceShape } from "@llm4ts/flow/Workspace"
38
- import { asReadOnly, coderFromEnv } from "@llm4ts/runner/Connectors"
39
- import { resolveFlowInput } from "@llm4ts/runner/FlowArgs"
40
- import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner"
41
- import { nodePlainFileStore } from "@llm4ts/runner/NodePlainFileStore"
42
- import { makeNodeWorkspace } from "@llm4ts/runner/NodeWorkspace"
43
-
44
- const ModDir = "docs/modernization"
45
-
46
- class FixSpec extends Schema.Class<FixSpec>("FixSpec")({
47
- title: Schema.String,
48
- spec: Schema.String,
49
- taskTitle: Schema.String,
50
- taskDescription: Schema.String
51
- }) {}
52
-
53
- class ReviewOutcome extends Schema.Class<ReviewOutcome>("ReviewOutcome")({
54
- fixes: Schema.Array(FixSpec),
55
- improvements: Schema.Array(FixSpec),
56
- lessons: Schema.Array(Schema.String)
57
- }) {}
58
-
59
- const fixSpecJsonSchema = {
60
- type: "object",
61
- properties: {
62
- title: { type: "string" },
63
- spec: { type: "string" },
64
- taskTitle: { type: "string" },
65
- taskDescription: { type: "string" }
66
- },
67
- required: ["title", "spec", "taskTitle", "taskDescription"]
68
- } as const
69
-
70
- const reviewOutcomeJsonSchema: JsonSchema = {
71
- type: "object",
72
- properties: {
73
- fixes: { type: "array", items: { ...fixSpecJsonSchema } },
74
- improvements: { type: "array", items: { ...fixSpecJsonSchema } },
75
- lessons: { type: "array", items: { type: "string" } }
76
- },
77
- required: ["fixes", "improvements", "lessons"]
78
- }
79
-
80
- const complianceDimensions = [
81
- Dimension.make({
82
- name: "spec-compliance",
83
- rubric:
84
- "Does the implementation satisfy every rule in the committed specs — exact values, " +
85
- "validation order, error paths — without weakening, deleting, or loosening any test or scenario?"
86
- }),
87
- Dimension.make({
88
- name: "scenario-coverage",
89
- rubric:
90
- "Is every BDD scenario in the seeded feature files exercised by an acceptance test on this branch?"
91
- })
92
- ]
93
-
94
- const slug = (title: string): string =>
95
- title
96
- .toLowerCase()
97
- .replace(/[^a-z0-9]+/g, "-")
98
- .replace(/^-|-$/g, "")
99
- .slice(0, 60)
100
-
101
- const gatherDir = Effect.fn("modernize-review.gatherDir")(function* (
102
- workspace: WorkspaceShape,
103
- directory: string
104
- ) {
105
- const paths = yield* workspace.discover(`${directory}/**`).pipe(Effect.orElseSucceed(() => []))
106
- const parts: Array<string> = []
107
- for (const path of [...paths].sort()) {
108
- const text = yield* workspace.read(path).pipe(Effect.orElseSucceed(() => ""))
109
- if (text.trim().length > 0) {
110
- parts.push(`===== ${path} =====\n${text}`)
111
- }
112
- }
113
- return parts.join("\n\n")
114
- })
115
-
116
- const distillPrompt = (
117
- pack: Pack,
118
- findings: ReviewResult,
119
- scored: EvalResult,
120
- diff: string
121
- ): string =>
122
- [
123
- pack.prompt("review") ?? "",
124
- "",
125
- "Below are the raw reviewer findings and judge scores for a modernization increment.",
126
- "Distill them:",
127
- '- "fixes": findings where the implementation VIOLATES the committed specs. Each gets a',
128
- " short spec document (Markdown: what is wrong, the spec rule it violates, the expected",
129
- " behaviour) and a plan task (title + description naming the spec rules/scenarios).",
130
- '- "improvements": worthwhile follow-ups that do NOT violate the specs.',
131
- '- "lessons": rules of thumb that would help FUTURE modernizations of this kind — phrased',
132
- " generally (no file paths from this repo), one sentence each. Only include lessons that",
133
- " generalize; an empty list is a fine answer.",
134
- "",
135
- "Reviewer findings:",
136
- findings.issues
137
- .map((issue) => `- [${issue.severity}] ${issue.title}: ${issue.description}`)
138
- .join("\n"),
139
- "",
140
- "Judge scores:",
141
- scored.scores.map((score) => `- ${score.name}: ${score.score} — ${score.reasoning}`).join("\n"),
142
- "",
143
- "Diff under review:",
144
- diff
145
- ].join("\n")
146
-
147
- const program = Effect.gen(function* () {
148
- const input = yield* resolveFlowInput("Review the modernization increment against its spec pack")
149
- const packDir = process.env.LLM4TS_PACK ?? "packs/cobol-springboot"
150
- const coder = coderFromEnv(process.env)
151
- const files = nodePlainFileStore
152
- const planPath = join(input.workDir, ModDir, "plan.md")
153
-
154
- yield* runNode(
155
- {
156
- workDir: input.workDir,
157
- workspace: input.workspace,
158
- userPrompt: input.prompt,
159
- coder,
160
- reasoning: asReadOnly(coder),
161
- reviewers: [asReadOnly(coder)],
162
- environment: process.env
163
- },
164
- (context) =>
165
- Effect.gen(function* () {
166
- const launchWorkspace = yield* makeNodeWorkspace(input.workspace)
167
- const target = yield* makeNodeWorkspace(input.workDir)
168
- const pack = yield* stage(context.events, "pack", loadPack(launchWorkspace, packDir))
169
- const reviewService = context.reviewers[0] ?? context.reasoning
170
-
171
- yield* stage(
172
- context.events,
173
- "wall",
174
- Effect.gen(function* () {
175
- if (pack.sources === undefined) {
176
- return yield* context.events.publish(
177
- Info.make({ message: "pack has no sources regex — wall check skipped" })
178
- )
179
- }
180
- const result = yield* checkWall(target, pack.sources)
181
- if (result._tag === "Breached") {
182
- return yield* FlowAborted.make({
183
- message: wallBreachMessage(
184
- result,
185
- "The review must judge spec-driven work only; remove the files and rerun."
186
- )
187
- })
188
- }
189
- yield* context.events.publish(
190
- Info.make({ message: "clean-room wall: no legacy source in the target workspace" })
191
- )
192
- })
193
- )
194
-
195
- const base = yield* context.git.defaultBase
196
- const diff = yield* context.git.diffVsBase(base)
197
- if (diff.trim().length === 0) {
198
- return yield* FlowAborted.make({
199
- message: `nothing to review: no diff vs ${base} on this branch`
200
- })
201
- }
202
- const changedFiles = yield* context.git.changedFilesVsBase(base)
203
- const specText = yield* gatherDir(target, pack.specsDir)
204
-
205
- const findings = yield* stage(
206
- context.events,
207
- "review",
208
- Effect.gen(function* () {
209
- // Sequential on purpose: free provider tiers rate-limit concurrent
210
- // reviewers, and the roster is small.
211
- const roster = [...allReviewers, ...pack.lenses].filter((lens) =>
212
- lens.matches(changedFiles)
213
- )
214
- const results: Array<ReviewResult> = []
215
- for (const lens of roster) {
216
- const prompt = `${lens.systemPrompt}\n\n${reviewPrompt(
217
- `modernization increment vs committed specs\n\n${specText}`,
218
- diff
219
- )}`
220
- results.push(
221
- yield* reviewService
222
- .executeStructured(prompt, ReviewResult, reviewJsonSchema)
223
- .pipe(Effect.mapError(FlowLlmError.from))
224
- )
225
- }
226
- return mergeReviewResults(results)
227
- })
228
- )
229
-
230
- const scored = yield* stage(
231
- context.events,
232
- "judge",
233
- judge(context.reasoning, complianceDimensions)
234
- .evaluate(Sample.make({ response: diff, context: specText, query: input.prompt }))
235
- .pipe(Effect.mapError(FlowLlmError.from))
236
- )
237
-
238
- const outcome = yield* stage(
239
- context.events,
240
- "distill",
241
- context.reasoning
242
- .executeStructured(
243
- distillPrompt(pack, findings, scored, diff),
244
- ReviewOutcome,
245
- reviewOutcomeJsonSchema
246
- )
247
- .pipe(Effect.mapError(FlowLlmError.from))
248
- )
249
-
250
- yield* stage(
251
- context.events,
252
- "fix specs",
253
- Effect.gen(function* () {
254
- const documents: ReadonlyArray<readonly [string, FixSpec]> = [
255
- ...outcome.fixes.map((fix) => ["fix", fix] as const),
256
- ...outcome.improvements.map((fix) => ["improvement", fix] as const)
257
- ]
258
- for (const [kind, fix] of documents) {
259
- yield* files.writeAtomic(
260
- join(input.workDir, pack.specsDir, "fixes", `${kind}-${slug(fix.title)}.md`),
261
- `# ${fix.title}\n\n${fix.spec}\n`
262
- )
263
- }
264
- if (outcome.fixes.length === 0) {
265
- return yield* context.events.publish(
266
- Info.make({ message: "no spec violations — no plan increment" })
267
- )
268
- }
269
- const store = makePlanStore(files)
270
- const plan = yield* store.load(planPath)
271
- if (plan === undefined) {
272
- return yield* FlowAborted.make({
273
- message: `no plan at ${planPath} — run modernize-seed first`
274
- })
275
- }
276
- yield* store.save(
277
- planPath,
278
- Plan.make({
279
- ...plan,
280
- tasks: [
281
- ...plan.tasks,
282
- ...outcome.fixes.map((fix) =>
283
- Task.make({
284
- title: fix.taskTitle,
285
- description: fix.taskDescription,
286
- completed: false
287
- })
288
- )
289
- ]
290
- })
291
- )
292
- yield* context.events.publish(
293
- Info.make({
294
- message: `${outcome.fixes.length} fix task(s) appended — rerun modernize-implement`
295
- })
296
- )
297
- })
298
- )
299
-
300
- yield* stage(
301
- context.events,
302
- "lessons",
303
- Effect.gen(function* () {
304
- if (outcome.lessons.length === 0) {
305
- return yield* context.events.publish(
306
- Info.make({ message: "no generalizable lessons this round" })
307
- )
308
- }
309
- for (const lesson of outcome.lessons) {
310
- yield* appendPackLesson(launchWorkspace, packDir, lesson)
311
- }
312
- yield* context.events.publish(
313
- Info.make({
314
- message:
315
- `${outcome.lessons.length} lesson(s) appended to ${packDir}/lessons.md — ` +
316
- "review and commit the pack change"
317
- })
318
- )
319
- })
320
- )
321
-
322
- yield* stage(
323
- context.events,
324
- "commit",
325
- context.git
326
- .commitAll(
327
- `modernize(${pack.name}): review — ${outcome.fixes.length} fix(es), ` +
328
- `${outcome.improvements.length} improvement(s)`
329
- )
330
- .pipe(Effect.asVoid)
331
- )
332
- yield* context.events.publish(
333
- Info.make({
334
- message:
335
- `fixes=${outcome.fixes.length} improvements=${outcome.improvements.length} ` +
336
- `lessons=${outcome.lessons.length}`
337
- })
338
- )
339
- })
340
- )
341
- })
342
-
343
- runFlowMain(program)