@llm4ts/shell 0.13.5 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/flows/convert-all.js +152 -0
- package/flows/convert-page.js +56 -0
- package/flows/modernize-bench.js +1 -1
- package/flows/modernize-extract.js +68 -40
- package/flows/modernize-survey.js +0 -0
- package/flows/packs/cobol-kafka/prompts/survey-refine.md +7 -0
- package/flows/packs/cobol-kafka/prompts/survey-triage.md +8 -0
- package/flows/packs/cobol-springboot/prompts/survey-refine.md +7 -0
- package/flows/packs/cobol-springboot/prompts/survey-triage.md +8 -0
- package/flows/packs/j2ee-nextjs-spa/pack.md +48 -0
- package/flows/packs/j2ee-nextjs-spa/patterns/PAT-J2EE-001-jstl-table.md +8 -0
- package/flows/packs/j2ee-nextjs-spa/patterns/PAT-J2EE-002-jquery-validation.md +8 -0
- package/flows/packs/j2ee-nextjs-spa/patterns/PAT-J2EE-003-session-stepper.md +9 -0
- package/flows/packs/j2ee-nextjs-spa/patterns/PAT-J2EE-004-esb-port.md +10 -0
- package/flows/packs/j2ee-nextjs-spa/patterns/PAT-J2EE-005-include-shell.md +8 -0
- package/flows/packs/j2ee-nextjs-spa/prompts/analysis.md +27 -0
- package/flows/packs/j2ee-nextjs-spa/prompts/bdd.md +13 -0
- package/flows/packs/j2ee-nextjs-spa/prompts/implement.md +26 -0
- package/flows/packs/j2ee-nextjs-spa/prompts/plan.md +17 -0
- package/flows/packs/j2ee-nextjs-spa/prompts/review.md +17 -0
- package/flows/packs/j2ee-nextjs-spa/prompts/spec.md +34 -0
- package/flows/packs/j2ee-nextjs-spa/prompts/survey-refine.md +22 -0
- package/flows/packs/j2ee-nextjs-spa/prompts/survey-triage.md +20 -0
- package/flows/packs/j2ee-nextjs-spa/reviewers/acl-purity.md +12 -0
- package/flows/packs/j2ee-nextjs-spa/reviewers/house-style.md +13 -0
- package/flows/packs/jsp-bff-nextjs/prompts/survey-refine.md +22 -0
- package/flows/packs/jsp-bff-nextjs/prompts/survey-triage.md +20 -0
- package/flows/packs/jsp-nextjs/prompts/survey-refine.md +22 -0
- package/flows/packs/jsp-nextjs/prompts/survey-triage.md +20 -0
- package/package.json +5 -5
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Convert the whole legacy estate: walk the survey inventory in wave order, one branch per page, progress board, estimated-cost migration report.
|
|
2
|
+
//
|
|
3
|
+
// Runs rooted at the TARGET repository (`--repo <nextjs>`), with
|
|
4
|
+
// LLM4TS_LEGACY_REPO pointing at the extracted legacy repository:
|
|
5
|
+
//
|
|
6
|
+
// LLM4TS_LEGACY_REPO=~/estates/demo-bank-legacy \
|
|
7
|
+
// llm4ts run convert-all --repo ~/estates/demo-bank-nextjs
|
|
8
|
+
//
|
|
9
|
+
// Order comes from the approved docs/modernization/wave-plan.md when present,
|
|
10
|
+
// otherwise every extracted spec alphabetically. The board (a BoardSync port)
|
|
11
|
+
// always writes the local files at .llm4ts/convert/board.{json,md}; when
|
|
12
|
+
// LLM4TS_ADO_ORG_URL and LLM4TS_ADO_PROJECT are set, an Azure DevOps
|
|
13
|
+
// work-item mirror is added via the az CLI (LLM4TS_ADO_REPO defaults to the
|
|
14
|
+
// project; auth belongs to az itself — `az devops login` or
|
|
15
|
+
// AZURE_DEVOPS_EXT_PAT, never an llm4ts variable, per ADR 0011).
|
|
16
|
+
// A failing page is marked failed and the walk continues
|
|
17
|
+
// (LLM4TS_FAIL_FAST=1 stops instead); pages already done on the board are
|
|
18
|
+
// skipped, so re-running resumes. Every token/cost figure — including the
|
|
19
|
+
// closing whole-estate projection in docs/conversion/migration-report.md —
|
|
20
|
+
// is an ESTIMATE, and the report says so.
|
|
21
|
+
import { basename, join } from "node:path";
|
|
22
|
+
import * as Effect from "effect/Effect";
|
|
23
|
+
import { AdoConfig, makeAzureDevOpsTool } from "@llm4ts/flow/AzureDevOpsTool";
|
|
24
|
+
import { BoardItem, composeBoardSync, makeAdoBoardSync, makeLocalBoardSync } from "@llm4ts/flow/BoardSync";
|
|
25
|
+
import { describeFlowError } from "@llm4ts/flow/FlowError";
|
|
26
|
+
import { Info } from "@llm4ts/flow/FlowEvents";
|
|
27
|
+
import { stage } from "@llm4ts/flow/PlanExecution";
|
|
28
|
+
import { asReadOnly, coderFromEnv } from "@llm4ts/runner/Connectors";
|
|
29
|
+
import { resolveFlowInput } from "@llm4ts/runner/FlowArgs";
|
|
30
|
+
import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner";
|
|
31
|
+
import { nodePlainFileStore } from "@llm4ts/runner/NodePlainFileStore";
|
|
32
|
+
import { nodeProcessExecutor } from "@llm4ts/runner/NodeProcessExecutor";
|
|
33
|
+
import { conversionInventory, convertPage, migrationReport, setupConversion } from "./lib/convert.ts";
|
|
34
|
+
const program = Effect.gen(function* () {
|
|
35
|
+
const input = yield* resolveFlowInput("Convert the legacy estate into the destination SPA");
|
|
36
|
+
const coder = coderFromEnv(process.env);
|
|
37
|
+
yield* runNode({
|
|
38
|
+
workDir: input.workDir,
|
|
39
|
+
workspace: input.workspace,
|
|
40
|
+
userPrompt: input.prompt,
|
|
41
|
+
coder,
|
|
42
|
+
reasoning: asReadOnly(coder),
|
|
43
|
+
reviewers: [asReadOnly(coder)],
|
|
44
|
+
environment: process.env
|
|
45
|
+
}, (context) => Effect.gen(function* () {
|
|
46
|
+
const deps = yield* setupConversion(context, input, process.env, import.meta.dirname);
|
|
47
|
+
const files = nodePlainFileStore;
|
|
48
|
+
const boardTitle = `Conversion: ${basename(input.workDir)}`;
|
|
49
|
+
const boards = [
|
|
50
|
+
makeLocalBoardSync(files, join(input.workDir, ".llm4ts", "convert"), boardTitle)
|
|
51
|
+
];
|
|
52
|
+
const orgUrl = process.env.LLM4TS_ADO_ORG_URL?.trim();
|
|
53
|
+
const project = process.env.LLM4TS_ADO_PROJECT?.trim();
|
|
54
|
+
if (orgUrl !== undefined && orgUrl.length > 0 && project !== undefined) {
|
|
55
|
+
// az CLI owns the credentials (`az devops login` / AZURE_DEVOPS_EXT_PAT);
|
|
56
|
+
// no PAT ever passes through llm4ts configuration.
|
|
57
|
+
const ado = makeAzureDevOpsTool(AdoConfig.make({
|
|
58
|
+
orgUrl,
|
|
59
|
+
project,
|
|
60
|
+
repository: process.env.LLM4TS_ADO_REPO?.trim() || project
|
|
61
|
+
}), nodeProcessExecutor, input.workDir, context.events);
|
|
62
|
+
boards.push(yield* makeAdoBoardSync(ado, boardTitle));
|
|
63
|
+
yield* context.events.publish(Info.make({ message: `ADO board mirror enabled: ${orgUrl}/${project}` }));
|
|
64
|
+
}
|
|
65
|
+
const board = composeBoardSync(boards);
|
|
66
|
+
const inventory = yield* stage(context.events, "inventory", conversionInventory(files, deps.legacy, deps.legacyDir, deps.pack));
|
|
67
|
+
if (inventory.length === 0) {
|
|
68
|
+
yield* context.events.publish(Info.make({ message: "inventory is empty — extract the legacy estate first" }));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// The whole estate lands on the board as planned up front — the
|
|
72
|
+
// breadth view exists from minute one.
|
|
73
|
+
yield* stage(context.events, "board", board.plan(inventory.map(({ page, wave }) => BoardItem.make({
|
|
74
|
+
id: page,
|
|
75
|
+
title: page,
|
|
76
|
+
status: "planned",
|
|
77
|
+
...(wave === undefined ? {} : { wave })
|
|
78
|
+
}))));
|
|
79
|
+
const baseBranch = yield* context.git.currentBranch;
|
|
80
|
+
const failFast = process.env.LLM4TS_FAIL_FAST === "1";
|
|
81
|
+
for (const { page } of inventory) {
|
|
82
|
+
const snapshot = yield* board.snapshot;
|
|
83
|
+
const known = snapshot.items.find((item) => item.id === page);
|
|
84
|
+
if (known !== undefined && known.status !== "planned" && known.status !== "failed") {
|
|
85
|
+
yield* context.events.publish(Info.make({ message: `resume: ${page} is already ${known.status} — skipping` }));
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const specPath = join(deps.legacyDir, deps.pack.specsDir, `${page}.md`);
|
|
89
|
+
if ((yield* files.read(specPath)) === undefined) {
|
|
90
|
+
yield* board.skip(page, "no extracted spec");
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const checkpoint = yield* context.git.checkpoint;
|
|
94
|
+
yield* board.start(page);
|
|
95
|
+
const result = yield* Effect.result(convertPage(deps, page));
|
|
96
|
+
if (result._tag === "Success") {
|
|
97
|
+
const outcome = result.success;
|
|
98
|
+
yield* board.complete(page, {
|
|
99
|
+
branch: outcome.branch,
|
|
100
|
+
reportPath: outcome.reportPath,
|
|
101
|
+
...(outcome.estimatedTokens === undefined
|
|
102
|
+
? {}
|
|
103
|
+
: { estimatedTokens: outcome.estimatedTokens }),
|
|
104
|
+
...(outcome.estimatedCostUsd === undefined
|
|
105
|
+
? {}
|
|
106
|
+
: { estimatedCostUsd: outcome.estimatedCostUsd })
|
|
107
|
+
});
|
|
108
|
+
yield* context.git.checkout(baseBranch);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const reason = describeFlowError(result.failure);
|
|
112
|
+
// A stuck page must not sink the walk: reset the working tree,
|
|
113
|
+
// mark the failure, keep going (LLM4TS_FAIL_FAST=1 to stop).
|
|
114
|
+
yield* context.git.rollback(checkpoint);
|
|
115
|
+
yield* context.git.checkout(baseBranch);
|
|
116
|
+
yield* board.fail(page, reason);
|
|
117
|
+
if (failFast) {
|
|
118
|
+
return yield* Effect.fromResult(result);
|
|
119
|
+
}
|
|
120
|
+
yield* context.events.publish(Info.make({ message: `page ${page} failed — continuing: ${reason}` }));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const finalBoard = yield* board.snapshot;
|
|
124
|
+
const rows = finalBoard.items.flatMap((item) => item.status === "done" || item.status === "failed" || item.status === "skipped"
|
|
125
|
+
? [
|
|
126
|
+
{
|
|
127
|
+
page: item.id,
|
|
128
|
+
outcome: item.status,
|
|
129
|
+
...(item.detail === undefined ? {} : { detail: item.detail }),
|
|
130
|
+
...(item.estimatedTokens === undefined
|
|
131
|
+
? {}
|
|
132
|
+
: { estimatedTokens: item.estimatedTokens }),
|
|
133
|
+
...(item.estimatedCostUsd === undefined
|
|
134
|
+
? {}
|
|
135
|
+
: { estimatedCostUsd: item.estimatedCostUsd })
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
: []);
|
|
139
|
+
const remaining = finalBoard.items
|
|
140
|
+
.filter((item) => item.status === "planned" || item.status === "failed")
|
|
141
|
+
.map((item) => item.id);
|
|
142
|
+
yield* stage(context.events, "report", files
|
|
143
|
+
.writeAtomic(join(input.workDir, "docs", "conversion", "migration-report.md"), migrationReport(rows, remaining))
|
|
144
|
+
.pipe(Effect.andThen(context.git.commitAll("convert: migration report and board"))));
|
|
145
|
+
yield* context.events.publish(Info.make({
|
|
146
|
+
message: `estate walk complete — ${rows.filter((row) => row.outcome === "done").length} ` +
|
|
147
|
+
`converted, ${remaining.length} remaining; ` +
|
|
148
|
+
"report: docs/conversion/migration-report.md (all figures estimated)"
|
|
149
|
+
}));
|
|
150
|
+
}));
|
|
151
|
+
});
|
|
152
|
+
runFlowMain(program);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Convert ONE legacy J2EE page into the destination Next.js SPA: branch per page, contract-first mocked ACL, judged gates, estimated costs.
|
|
2
|
+
//
|
|
3
|
+
// Runs rooted at the TARGET repository (`--repo <nextjs>`), with
|
|
4
|
+
// LLM4TS_LEGACY_REPO pointing at the extracted legacy repository. The task
|
|
5
|
+
// text is the page name (the program name of its extracted spec):
|
|
6
|
+
//
|
|
7
|
+
// LLM4TS_LEGACY_REPO=~/estates/demo-bank-legacy \
|
|
8
|
+
// llm4ts run convert-page --repo ~/estates/demo-bank-nextjs accountOverview
|
|
9
|
+
//
|
|
10
|
+
// Not clean-room (ADR 0012): the coder gets the schema-validated Page Spec as
|
|
11
|
+
// contract plus bounded legacy source evidence, the destination's own pages as
|
|
12
|
+
// style guide, and a deterministically generated OpenAPI anti-corruption
|
|
13
|
+
// contract. Gates: typecheck+lint+test per task, test+build to finish, then a
|
|
14
|
+
// per-page spec-compliance judge with bounded feedback rounds
|
|
15
|
+
// (LLM4TS_JUDGE_ROUNDS). One branch `convert/<page>` per run, no PR — the
|
|
16
|
+
// branch awaits human review; the conversion report is committed with it.
|
|
17
|
+
// Token/cost figures are ESTIMATES (LLM4TS_ESTIMATE_MODEL,
|
|
18
|
+
// LLM4TS_ESTIMATE_CHARS_PER_TOKEN). Pack: LLM4TS_PACK (default
|
|
19
|
+
// packs/j2ee-nextjs-spa).
|
|
20
|
+
import * as Effect from "effect/Effect";
|
|
21
|
+
import { FlowAborted } from "@llm4ts/flow/FlowError";
|
|
22
|
+
import { Info } from "@llm4ts/flow/FlowEvents";
|
|
23
|
+
import { asReadOnly, coderFromEnv } from "@llm4ts/runner/Connectors";
|
|
24
|
+
import { resolveFlowInput } from "@llm4ts/runner/FlowArgs";
|
|
25
|
+
import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner";
|
|
26
|
+
import { convertPage, setupConversion } from "./lib/convert.ts";
|
|
27
|
+
const program = Effect.gen(function* () {
|
|
28
|
+
const input = yield* resolveFlowInput("Convert one legacy page into the destination SPA");
|
|
29
|
+
const page = input.prompt.trim().split(/\s+/)[0] ?? "";
|
|
30
|
+
const coder = coderFromEnv(process.env);
|
|
31
|
+
yield* runNode({
|
|
32
|
+
workDir: input.workDir,
|
|
33
|
+
workspace: input.workspace,
|
|
34
|
+
userPrompt: input.prompt,
|
|
35
|
+
coder,
|
|
36
|
+
reasoning: asReadOnly(coder),
|
|
37
|
+
reviewers: [asReadOnly(coder)],
|
|
38
|
+
environment: process.env
|
|
39
|
+
}, (context) => Effect.gen(function* () {
|
|
40
|
+
if (page.length === 0) {
|
|
41
|
+
return yield* FlowAborted.make({
|
|
42
|
+
message: "pass the page name to convert, e.g.: llm4ts run convert-page accountOverview"
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const deps = yield* setupConversion(context, input, process.env, import.meta.dirname);
|
|
46
|
+
const outcome = yield* convertPage(deps, page);
|
|
47
|
+
yield* context.events.publish(Info.make({
|
|
48
|
+
message: `converted ${outcome.page} on branch ${outcome.branch} — ` +
|
|
49
|
+
`report at ${outcome.reportPath}` +
|
|
50
|
+
(outcome.estimatedTokens === undefined
|
|
51
|
+
? ""
|
|
52
|
+
: ` (~${outcome.estimatedTokens} tokens estimated)`)
|
|
53
|
+
}));
|
|
54
|
+
}));
|
|
55
|
+
});
|
|
56
|
+
runFlowMain(program);
|
package/flows/modernize-bench.js
CHANGED
|
@@ -99,7 +99,7 @@ const program = Effect.gen(function* () {
|
|
|
99
99
|
phases.push(makeBenchPhase(observation, name, Date.now() - began));
|
|
100
100
|
return value;
|
|
101
101
|
});
|
|
102
|
-
const programs = yield* measured("inventory", matchingFiles(estate, pack.programs ?? pack.sources ?? ".*"));
|
|
102
|
+
const programs = yield* measured("inventory", matchingFiles(estate, pack.programs ?? pack.sources ?? ".*", pack.exclude));
|
|
103
103
|
if (programs.length === 0) {
|
|
104
104
|
return yield* FlowAborted.make({
|
|
105
105
|
message: `no source units matched the pack's programs/sources regex under ${input.workDir}`
|
|
@@ -24,8 +24,13 @@
|
|
|
24
24
|
// context is bounded by LLM4TS_CONTEXT_BUDGET (chars;
|
|
25
25
|
// LLM4TS_JUDGE_SOURCES_LIMIT is the deprecated alias). The analyst is bounded
|
|
26
26
|
// by LLM4TS_ANALYST_TURNS and LLM4TS_MAX_CLOSURE_FILES.
|
|
27
|
+
// LLM4TS_EXTRACT_CONCURRENCY=<n> extracts and judges n programs at once (default
|
|
28
|
+
// 1): the programs of a wave are independent, each gets a commit scoped to its
|
|
29
|
+
// own files, and a failure lets in-flight programs finish before it surfaces.
|
|
27
30
|
import { join } from "node:path";
|
|
28
31
|
import * as Effect from "effect/Effect";
|
|
32
|
+
import * as Ref from "effect/Ref";
|
|
33
|
+
import * as Semaphore from "effect/Semaphore";
|
|
29
34
|
import { Sample } from "@llm4ts/core/eval/Eval";
|
|
30
35
|
import { judge } from "@llm4ts/core/eval/Judge";
|
|
31
36
|
import { budget, capped, withShrink } from "@llm4ts/flow/Context";
|
|
@@ -42,7 +47,7 @@ import { cachedReview } from "@llm4ts/flow/ReviewCache";
|
|
|
42
47
|
import { coverage, coverageUnits, features, matchingFiles } from "@llm4ts/flow/SpecChecks";
|
|
43
48
|
import { SurveyGraph, closureFor, surveyGraph } from "@llm4ts/flow/Survey";
|
|
44
49
|
import { withDraftApproval, requireApproval } from "@llm4ts/modernize/Approval";
|
|
45
|
-
import { ProgramArtifacts, ProgramUnit, extractProgramsResumably } from "@llm4ts/modernize/Artifacts";
|
|
50
|
+
import { ProgramArtifacts, ProgramUnit, extractProgramsResumably, programArtifactPaths } from "@llm4ts/modernize/Artifacts";
|
|
46
51
|
import { asReadOnly, coderFromEnv, withTurnLimit } from "@llm4ts/runner/Connectors";
|
|
47
52
|
import { resolveFlowInput } from "@llm4ts/runner/FlowArgs";
|
|
48
53
|
import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner";
|
|
@@ -63,6 +68,14 @@ const analystTurns = () => positiveEnvInt("LLM4TS_ANALYST_TURNS", 48);
|
|
|
63
68
|
* than this gets a bounded, visible subset rather than an unbounded read.
|
|
64
69
|
*/
|
|
65
70
|
const maxClosureFiles = () => positiveEnvInt("LLM4TS_MAX_CLOSURE_FILES", 40);
|
|
71
|
+
/**
|
|
72
|
+
* Programs extracted (and judged) at once. The programs of a wave are
|
|
73
|
+
* independent — each analyst reads its source and resolved closure, writes
|
|
74
|
+
* its own four files, and gets its own scoped commit — so this divides wall
|
|
75
|
+
* time without changing tokens or cost. Default 1 keeps the sequential
|
|
76
|
+
* narration; the bound is about the coder seat's quota, not correctness.
|
|
77
|
+
*/
|
|
78
|
+
const extractConcurrency = () => positiveEnvInt("LLM4TS_EXTRACT_CONCURRENCY", 1);
|
|
66
79
|
/** `cobol/ACCTXFR.cbl` → `ACCTXFR`: the program name keying every per-program artifact. */
|
|
67
80
|
const programName = (relativePath) => {
|
|
68
81
|
const base = relativePath.slice(relativePath.lastIndexOf("/") + 1);
|
|
@@ -203,7 +216,7 @@ const program = Effect.gen(function* () {
|
|
|
203
216
|
]
|
|
204
217
|
.filter((part) => part !== undefined)
|
|
205
218
|
.join("\n\n");
|
|
206
|
-
const all = yield* stage(context.events, "inventory", matchingFiles(repo, pack.programs ?? pack.sources ?? ".*"));
|
|
219
|
+
const all = yield* stage(context.events, "inventory", matchingFiles(repo, pack.programs ?? pack.sources ?? ".*", pack.exclude));
|
|
207
220
|
const wave = process.env.LLM4TS_WAVE?.trim();
|
|
208
221
|
const programs = wave === undefined || wave.length === 0
|
|
209
222
|
? all
|
|
@@ -239,35 +252,48 @@ const program = Effect.gen(function* () {
|
|
|
239
252
|
message: "pack has no '## Survey:' edge regexes — the analyst gets no resolved closure"
|
|
240
253
|
}))
|
|
241
254
|
.pipe(Effect.as(SurveyGraph.make({ nodes: [], edges: [] })))
|
|
242
|
-
: surveyGraph(repo, pack.sources ?? ".*", pack.coverage, pack.survey
|
|
255
|
+
: surveyGraph(repo, pack.sources ?? ".*", pack.coverage, pack.survey, {
|
|
256
|
+
...(pack.exclude === undefined ? {} : { exclude: pack.exclude })
|
|
257
|
+
}));
|
|
243
258
|
// One structured analyst call per program, resumable per program: a rerun
|
|
244
|
-
// skips every program whose spec exists, and each program gets its own
|
|
259
|
+
// skips every program whose spec exists, and each program gets its own
|
|
260
|
+
// commit scoped to its four files. LLM4TS_EXTRACT_CONCURRENCY runs that
|
|
261
|
+
// many programs at once; the commit is the one step serialised.
|
|
262
|
+
const concurrency = extractConcurrency();
|
|
245
263
|
yield* stage(context.events, "extract", Effect.gen(function* () {
|
|
246
|
-
|
|
247
|
-
|
|
264
|
+
const started = yield* Ref.make(0);
|
|
265
|
+
const commitLock = yield* Semaphore.make(1);
|
|
266
|
+
if (concurrency > 1) {
|
|
267
|
+
yield* context.events.publish(Info.make({
|
|
268
|
+
message: `extracting up to ${concurrency} program(s) at once (LLM4TS_EXTRACT_CONCURRENCY)`
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
const summary = yield* extractProgramsResumably(files, units, (target) => Effect.gen(function* () {
|
|
272
|
+
const ordinal = yield* Ref.updateAndGet(started, (count) => count + 1);
|
|
273
|
+
yield* context.events.publish(Info.make({
|
|
274
|
+
message: `extracting ${target.sourcePath} (${ordinal}/${units.length})`
|
|
275
|
+
}));
|
|
276
|
+
return yield* structuredAndPublish(context.coder, context.events, `${system}\n\n${programAsk(pack, target.sourcePath, closureFor(graph, target.name, maxClosureFiles()))}`, ProgramArtifacts, programArtifactsJsonSchema, "coder").pipe(
|
|
277
|
+
// A turn-limit trip is the wedged-agent tail, not a
|
|
278
|
+
// failure: keep whatever the analyst already produced.
|
|
279
|
+
Effect.catchIf((error) => error.cause?._tag === "TurnLimitError", (error) => Effect.gen(function* () {
|
|
280
|
+
const existing = yield* files.read(join(modDirAbs, "specs", `${target.name}.md`));
|
|
281
|
+
if (existing === undefined) {
|
|
282
|
+
return yield* Effect.fail(error);
|
|
283
|
+
}
|
|
248
284
|
yield* context.events.publish(Info.make({
|
|
249
|
-
message: `
|
|
285
|
+
message: `turn limit hit on ${target.sourcePath} after its spec was written — keeping the work`
|
|
250
286
|
}));
|
|
251
|
-
return
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}));
|
|
262
|
-
return ProgramArtifacts.make({
|
|
263
|
-
spec: existing,
|
|
264
|
-
feature: "",
|
|
265
|
-
traceability: "",
|
|
266
|
-
mapping: ""
|
|
267
|
-
});
|
|
268
|
-
})));
|
|
269
|
-
}), modDirAbs);
|
|
270
|
-
if (summary.created.length > 0) {
|
|
287
|
+
return ProgramArtifacts.make({
|
|
288
|
+
spec: existing,
|
|
289
|
+
feature: "",
|
|
290
|
+
traceability: "",
|
|
291
|
+
mapping: ""
|
|
292
|
+
});
|
|
293
|
+
})));
|
|
294
|
+
}), modDirAbs, {
|
|
295
|
+
concurrency,
|
|
296
|
+
onCreated: (unit) => Effect.gen(function* () {
|
|
271
297
|
// Tag the traceability fragment with the cards this program's
|
|
272
298
|
// source matches — regex-decided, so implementation's playbook
|
|
273
299
|
// is reproducible.
|
|
@@ -278,15 +304,18 @@ const program = Effect.gen(function* () {
|
|
|
278
304
|
const fragment = (yield* files.read(fragmentPath)) ?? "";
|
|
279
305
|
yield* files.writeAtomic(fragmentPath, `${fragment.trimEnd()}\n\nPatterns: ${matched.map((card) => card.id).join(", ")}\n`);
|
|
280
306
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
307
|
+
// Only this program's files, under one permit: a concurrent
|
|
308
|
+
// sibling's half-written artifacts must never ride along.
|
|
309
|
+
yield* commitLock.withPermit(context.git
|
|
310
|
+
.commitPaths(`modernize(${pack.name}): spec ${unit.name}`, programArtifactPaths(unit, ModDir))
|
|
311
|
+
.pipe(Effect.asVoid));
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
if (summary.skipped.length > 0) {
|
|
315
|
+
yield* context.events.publish(Info.make({
|
|
316
|
+
message: `resume: ${summary.skipped.length} program(s) already have a spec — skipped ` +
|
|
317
|
+
summary.skipped.join(", ")
|
|
318
|
+
}));
|
|
290
319
|
}
|
|
291
320
|
}));
|
|
292
321
|
// traceability.md / mapping.md are regenerated from the fragments — fixes
|
|
@@ -377,10 +406,9 @@ const program = Effect.gen(function* () {
|
|
|
377
406
|
});
|
|
378
407
|
const covered = yield* coverage(repo, pack.coverage, trace);
|
|
379
408
|
const wellFormed = yield* features(repo, join(ModDir, "features"));
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
409
|
+
// Verdicts are per program and cached per program, so they judge
|
|
410
|
+
// under the same bound as extraction; the merge is order-stable.
|
|
411
|
+
const judged = yield* Effect.forEach(units, judgeProgram, { concurrency });
|
|
384
412
|
return mergeReviewResults([covered, wellFormed, docs, ...judged]);
|
|
385
413
|
});
|
|
386
414
|
// One bounded fix turn per sub-bar program (own commit) plus one residual
|
|
Binary file
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Regexes miss links in complex COBOL/JCL sources: dynamic CALLs (CALL WS-PROGRAM
|
|
2
|
+
where the target sits in WORKING-STORAGE), JCL symbolic parameters (EXEC
|
|
3
|
+
PGM=&PGM set earlier in the job or in a PROC), PROC expansions, control cards
|
|
4
|
+
and SYSIN members naming programs, and COPY REPLACING variants. Prioritise jobs
|
|
5
|
+
with fewer outgoing edges than steps, programs nothing references, and copybooks
|
|
6
|
+
with degree 0. Calls to system or runtime services (CEE*, DFH*, IGZ*, SQL
|
|
7
|
+
preprocessor stubs) are not estate edges — note them.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Units are COBOL programs, copybooks, and JCL jobs. Copybooks with many callers
|
|
2
|
+
are shared data contracts — migrate them with their first consumer or wrap them.
|
|
3
|
+
Jobs are scheduler entry points: a job nothing calls is still live if a
|
|
4
|
+
scheduler runs it, so it is a "wrap" (keep on the platform, front with an API)
|
|
5
|
+
until every program it steps through has been rewritten, never a "retire" on
|
|
6
|
+
graph evidence alone. Programs with no callers and no job step are the retire
|
|
7
|
+
candidates; give the degree evidence. Batch windows and EOD chains make good
|
|
8
|
+
wave boundaries: keep a job's step programs in the same or an earlier wave.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Regexes miss links in complex COBOL/JCL sources: dynamic CALLs (CALL WS-PROGRAM
|
|
2
|
+
where the target sits in WORKING-STORAGE), JCL symbolic parameters (EXEC
|
|
3
|
+
PGM=&PGM set earlier in the job or in a PROC), PROC expansions, control cards
|
|
4
|
+
and SYSIN members naming programs, and COPY REPLACING variants. Prioritise jobs
|
|
5
|
+
with fewer outgoing edges than steps, programs nothing references, and copybooks
|
|
6
|
+
with degree 0. Calls to system or runtime services (CEE*, DFH*, IGZ*, SQL
|
|
7
|
+
preprocessor stubs) are not estate edges — note them.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Units are COBOL programs, copybooks, and JCL jobs. Copybooks with many callers
|
|
2
|
+
are shared data contracts — migrate them with their first consumer or wrap them.
|
|
3
|
+
Jobs are scheduler entry points: a job nothing calls is still live if a
|
|
4
|
+
scheduler runs it, so it is a "wrap" (keep on the platform, front with an API)
|
|
5
|
+
until every program it steps through has been rewritten, never a "retire" on
|
|
6
|
+
graph evidence alone. Programs with no callers and no job step are the retire
|
|
7
|
+
candidates; give the degree evidence. Batch windows and EOD chains make good
|
|
8
|
+
wave boundaries: keep a job's step programs in the same or an earlier wave.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Pack: j2ee-nextjs-spa
|
|
2
|
+
|
|
3
|
+
source: jsp
|
|
4
|
+
scaffold: ../../fixtures/scaffolds/nextjs-spa
|
|
5
|
+
sources: .*\.(jsp|java|xml)
|
|
6
|
+
programs: .*\.jsp
|
|
7
|
+
specs-dir: docs/modernization/specs
|
|
8
|
+
features-dir: docs/modernization/features
|
|
9
|
+
programFiles: (?:src/app/<NAME>(?:/.*)?|src/services/<NAME>(?:/.*)?|contracts/<NAME>\.openapi\.yaml|tests/<NAME>\..*)
|
|
10
|
+
|
|
11
|
+
## Gates
|
|
12
|
+
|
|
13
|
+
- typecheck: pnpm typecheck
|
|
14
|
+
- lint: pnpm lint
|
|
15
|
+
- test: pnpm test
|
|
16
|
+
- build: pnpm build
|
|
17
|
+
|
|
18
|
+
## Judge
|
|
19
|
+
|
|
20
|
+
- completeness (0..2): Every screen, servlet mapping (web.xml url-pattern), form field, navigation path, validation rule (client AND server side), user-facing message, session attribute, and API/ESB call in the JSP/servlet source is captured in the spec and BDD scenarios. Score 2 only if nothing material is missing.
|
|
21
|
+
- faithfulness (0..2): Every statement is grounded in the source: validation thresholds, exact message texts, redirect targets, session behaviour, DTO field names, and which ESB service each endpoint wraps match the code, and nothing is invented. Score 2 only if fully source-grounded.
|
|
22
|
+
- testability (0..2): Scenarios are concrete user journeys — specific accounts, amounts, and the exact message or destination screen expected; no vague language ("shows an error", "handled gracefully"). Score 2 only if every scenario is directly encodable as a test.
|
|
23
|
+
- pagespec (0..2): The spec contains exactly one ```json pagespec fenced block; its forms, apiCalls (with esbService), dtos (legacy→domain rename table), navigation, and sessionState agree with the prose and with the source; domain names are business language, never legacy abbreviations. Score 2 only if the block is present, consistent, and complete.
|
|
24
|
+
|
|
25
|
+
## Coverage: servlet-url
|
|
26
|
+
|
|
27
|
+
files: .*web\.xml
|
|
28
|
+
unit: <url-pattern>([^<]+)</url-pattern>
|
|
29
|
+
|
|
30
|
+
## Coverage: jsp-form
|
|
31
|
+
|
|
32
|
+
files: .*\.jsp
|
|
33
|
+
unit: action="([^"]+)"
|
|
34
|
+
|
|
35
|
+
## Coverage: jsp-ajax
|
|
36
|
+
|
|
37
|
+
files: .*\.jsp
|
|
38
|
+
unit: url:\s*['"]([^'"]+)['"]
|
|
39
|
+
|
|
40
|
+
## Survey: jsp-include
|
|
41
|
+
|
|
42
|
+
files: .*\.jsp
|
|
43
|
+
unit: <jsp:include page="([^"]+)"
|
|
44
|
+
|
|
45
|
+
## Survey: servlet-class
|
|
46
|
+
|
|
47
|
+
files: .*web\.xml
|
|
48
|
+
unit: <servlet-class>[a-z.]*\.([A-Za-z0-9]+)</servlet-class>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
match: <c:forEach|<%[^=]*for\s*\(
|
|
3
|
+
---
|
|
4
|
+
A JSTL/scriptlet row loop becomes the destination DataTable component with
|
|
5
|
+
declarative columns: the loop body's cells map to column render functions, and
|
|
6
|
+
status-code-to-label mappings move into a typed lookup beside the page. The
|
|
7
|
+
data comes from a port call in an effect on mount — never fetched in the
|
|
8
|
+
component body, never re-sorted client-side unless the legacy page sorted.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
match: \$\(['"]#|validate\.js|onsubmit=
|
|
3
|
+
---
|
|
4
|
+
jQuery/inline validation becomes the house Form component's validation map:
|
|
5
|
+
one entry per spec rule, message text VERBATIM, evaluated in the spec's order
|
|
6
|
+
(first-failure-wins). Where the legacy client and server rules diverged, the
|
|
7
|
+
spec's per-rule enforcedAt says which set is contract — implement the union,
|
|
8
|
+
flag any contradiction as an open question rather than silently choosing.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
match: HttpSession|session\.setAttribute|session\.getAttribute
|
|
3
|
+
---
|
|
4
|
+
An HttpSession-carried multi-step flow becomes explicit client state driven by
|
|
5
|
+
the destination Stepper: one state object typed after the spec's sessionState
|
|
6
|
+
(the legacy draft DTO, domain-renamed), owned by the flow's top-level page,
|
|
7
|
+
passed down per step. Refresh/back semantics are explicit — the legacy app got
|
|
8
|
+
them free from the session; the SPA must decide and the spec's navigation
|
|
9
|
+
section says what to preserve. Never scatter the draft across components.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
match: Esb[A-Za-z]+Service|ESB_[A-Z_]+
|
|
3
|
+
---
|
|
4
|
+
Every EsbXxxService wrapper the servlet called becomes one port operation in
|
|
5
|
+
the page's anti-corruption contract: the OpenAPI operation carries the domain
|
|
6
|
+
name, the port interface mirrors it, and the mock adapter returns
|
|
7
|
+
contract-shaped fixtures. The ESB routine name lands in the contract's
|
|
8
|
+
description (the future B4F team's pointer), never in code identifiers. If the
|
|
9
|
+
servlet combined two ESB calls, the port exposes the page's need (one
|
|
10
|
+
operation), and the composition note goes in the contract description.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
match: <jsp:include|<%@\s*include
|
|
3
|
+
---
|
|
4
|
+
header/nav/footer jsp:include shells are ALREADY provided by the destination
|
|
5
|
+
app shell (layout + PageLayout) — a converted page never re-renders chrome.
|
|
6
|
+
Map the legacy page body into PageLayout's slots, and turn nav.jsp links into
|
|
7
|
+
the routes of pages that exist (converted or exemplar); links to not-yet-
|
|
8
|
+
converted pages stay out of nav rather than dangling.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
You are a legacy-web reverse-engineering analyst on a bank modernization. Extract
|
|
2
|
+
the COMPLETE observable behaviour of one J2EE/JSP page into a Page Spec precise
|
|
3
|
+
enough that a converter who prefers the spec (but may consult the source) can
|
|
4
|
+
rebuild it inside an existing Next.js SPA.
|
|
5
|
+
|
|
6
|
+
How to read the app:
|
|
7
|
+
|
|
8
|
+
- Start from WEB-INF/web.xml: every url-pattern is an entry point and must be
|
|
9
|
+
accounted for.
|
|
10
|
+
- Servlets carry the behaviour: request-parameter validation (exact rules AND
|
|
11
|
+
exact error message texts), session reads/writes, redirects vs forwards, and
|
|
12
|
+
every ESB service call behind each endpoint (the EsbXxxService wrappers name
|
|
13
|
+
the routine — record it; ALL business logic lives behind those services and
|
|
14
|
+
must NEVER be reimplemented client-side).
|
|
15
|
+
- JSPs carry the screens: what is displayed per row, status-code-to-label
|
|
16
|
+
mappings, forms (fields, hidden fields, where they submit), links between
|
|
17
|
+
screens, and every jQuery $.ajax call.
|
|
18
|
+
- Client-side jQuery validation and server-side servlet validation often
|
|
19
|
+
DIVERGE — record each rule with where it is enforced (client, server, both);
|
|
20
|
+
the divergences are findings, not noise.
|
|
21
|
+
- Hidden fields and HttpSession attributes are STATE the SPA must own
|
|
22
|
+
explicitly — document what flows through them.
|
|
23
|
+
- DTO classes use abbreviated legacy names (acctNo, curBal): propose a domain
|
|
24
|
+
rename for every field — the anti-corruption table.
|
|
25
|
+
|
|
26
|
+
Never invent behaviour; message texts and thresholds are contract — record them
|
|
27
|
+
verbatim. Genuinely ambiguous behaviour goes in "Open questions", not guesses.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Write Gherkin .feature files encoding the spec as executable user journeys —
|
|
2
|
+
these become the component tests of the converted page.
|
|
3
|
+
|
|
4
|
+
- One feature per screen/flow.
|
|
5
|
+
- User vocabulary, not servlet vocabulary: "When she submits a transfer of
|
|
6
|
+
3000.00, Then she is asked to confirm", not "doPost forwards to confirm.jsp".
|
|
7
|
+
- Error messages are asserted VERBATIM — they are contract.
|
|
8
|
+
- Concrete values everywhere: real account numbers, amounts, statuses from the
|
|
9
|
+
fixture data.
|
|
10
|
+
- Cover: the happy path, EVERY validation rule and its message (noting whether
|
|
11
|
+
the legacy app enforced it client-side, server-side, or both), threshold
|
|
12
|
+
boundaries, confirmation flows, and the state that must survive navigation
|
|
13
|
+
(what the session or hidden fields carried).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
You convert one legacy J2EE page into an existing client-only Next.js SPA, one
|
|
2
|
+
task at a time. The Page Spec is the contract; the legacy source excerpts you
|
|
3
|
+
are given are evidence for disambiguation — prefer the spec, and when you must
|
|
4
|
+
lean on the source, keep domain names, never legacy abbreviations.
|
|
5
|
+
|
|
6
|
+
Non-negotiables:
|
|
7
|
+
|
|
8
|
+
- Read the destination repo's CONTRIBUTING.md and the existing pages under
|
|
9
|
+
src/app/ FIRST and imitate them: same design-system components, same form
|
|
10
|
+
handling, same port/adapter shape, same test style. "Code the new like what
|
|
11
|
+
we have."
|
|
12
|
+
- ALL business logic stays behind the service port — the mock adapter fakes
|
|
13
|
+
transport, never rules. If the legacy page computed something server-side
|
|
14
|
+
(fees, limits, validation of business state), that is a port operation, not
|
|
15
|
+
client code.
|
|
16
|
+
- Components never call fetch; only adapters touch transport. Pages depend on
|
|
17
|
+
the port through the registry, so the mock swaps for the real gateway later
|
|
18
|
+
without touching the page.
|
|
19
|
+
- Validation error messages match the spec VERBATIM, in the spec's order.
|
|
20
|
+
- Session attributes and hidden-field flows become explicit client state
|
|
21
|
+
(the Stepper pattern for multi-step flows) — never re-derive a value the
|
|
22
|
+
legacy app carried through the session.
|
|
23
|
+
- Legacy DTO names must not appear anywhere in the new code — use the spec's
|
|
24
|
+
domain renames. Money is exact decimals, never floats.
|
|
25
|
+
- TypeScript strict, no `any`, no type assertions; typecheck, lint, test, and
|
|
26
|
+
build must all stay green.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Derive the conversion task list for ONE page from its Page Spec. The
|
|
2
|
+
destination is an existing Next.js SPA with a design system, an AuthProvider,
|
|
3
|
+
and a port/adapter service convention — imitate it, never fight it.
|
|
4
|
+
|
|
5
|
+
- Task 1: the anti-corruption service layer — the typed port interface under
|
|
6
|
+
src/services/<page>/port.ts matching the OpenAPI contract at
|
|
7
|
+
contracts/<page>.openapi.yaml (domain names only), a mock adapter under
|
|
8
|
+
src/services/<page>/mock.ts returning contract-shaped fixture data, and the
|
|
9
|
+
registry wiring. No page code yet.
|
|
10
|
+
- Task 2: the page component(s) under src/app/<page>/ using ONLY the
|
|
11
|
+
destination design-system components and the port — forms, validation with
|
|
12
|
+
VERBATIM messages, navigation, and explicit state for anything the legacy
|
|
13
|
+
app carried in the session or hidden fields.
|
|
14
|
+
- Task 3: component tests under tests/<page>.page.test.tsx in the house test
|
|
15
|
+
style — spec'd fields render, spec'd validations fire with their exact
|
|
16
|
+
messages, the port is called with contract-shaped payloads. Nothing else.
|
|
17
|
+
- Each task names the spec rules and scenarios it covers.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
You review a finished conversion increment: a page ported from a J2EE/JSP app
|
|
2
|
+
into an existing Next.js SPA with a design system and a port/adapter service
|
|
3
|
+
convention. Judge the implementation against the Page Spec and the destination
|
|
4
|
+
house rules, not your own taste.
|
|
5
|
+
|
|
6
|
+
Look specifically for:
|
|
7
|
+
|
|
8
|
+
- Message drift: validation texts that differ from the spec's verbatim
|
|
9
|
+
messages, or rules evaluated out of the spec's order.
|
|
10
|
+
- ACL leaks: fetch outside adapters, legacy DTO names in new code, business
|
|
11
|
+
logic implemented client-side, a mock adapter embedding rules.
|
|
12
|
+
- Contract drift: port methods that do not match the generated OpenAPI
|
|
13
|
+
contract's operations and shapes.
|
|
14
|
+
- State regressions: session-carried or hidden-field state the legacy app had
|
|
15
|
+
that the SPA lost (multi-step drafts, confirmation data across steps).
|
|
16
|
+
- House drift: hand-rolled UI where a design-system component exists, ad-hoc
|
|
17
|
+
styling, tests outside the house style.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
The spec markdown for a page has two mandatory parts:
|
|
2
|
+
|
|
3
|
+
1. Prose sections: Screen (layout, displayed data, status label mappings),
|
|
4
|
+
Forms (each field: name, label, type, required; each validation rule with
|
|
5
|
+
its VERBATIM message and where it is enforced — client, server, or both),
|
|
6
|
+
Navigation (inbound links, outbound links/redirects, step order for
|
|
7
|
+
multi-step flows), API (each endpoint: method, path, request/response
|
|
8
|
+
fields, and the ESB service it wraps), Session state, Anti-corruption
|
|
9
|
+
renames (every legacy DTO field → proposed domain name), Open questions.
|
|
10
|
+
|
|
11
|
+
2. EXACTLY ONE fenced block starting with ```json pagespec containing a JSON
|
|
12
|
+
object with this shape (all names in domain language where marked):
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
"page": "<program name, e.g. accountOverview>",
|
|
16
|
+
"route": "<legacy url-pattern>",
|
|
17
|
+
"title": "<screen title>",
|
|
18
|
+
"complexity": "low" | "medium" | "high",
|
|
19
|
+
"forms": [{ "name", "action", "fields": [{ "name", "label", "type",
|
|
20
|
+
"required", "validations": [{ "rule", "message", "enforcedAt":
|
|
21
|
+
"client"|"server"|"both" }] }] }],
|
|
22
|
+
"dtos": [{ "legacyName", "domainName", "fields": [{ "legacyName",
|
|
23
|
+
"domainName", "type" }] }],
|
|
24
|
+
"apiCalls": [{ "operation": "<domain verb, e.g. listAccounts>",
|
|
25
|
+
"method", "path", "esbService", "request": [{ "legacyName",
|
|
26
|
+
"domainName", "type" }], "response": [{ ... }] }],
|
|
27
|
+
"navigation": { "inbound": [], "outbound": [], "steps": [] },
|
|
28
|
+
"sessionState": ["<what the session carries and why>"],
|
|
29
|
+
"openQuestions": []
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
The block is machine-validated downstream: malformed JSON or a missing block
|
|
33
|
+
fails the page. The JSON and the prose must agree — the JSON is the contract,
|
|
34
|
+
the prose is the evidence.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
This is a J2EE web application: units are JSP pages and fragments, servlets and
|
|
2
|
+
supporting Java classes, and deployment descriptors. Regexes miss most of the
|
|
3
|
+
links such an application establishes indirectly:
|
|
4
|
+
|
|
5
|
+
- WEB-INF/web.xml: every servlet-mapping ties a url-pattern to a servlet; every
|
|
6
|
+
servlet-class names a Java unit; welcome-file and error-page entries name
|
|
7
|
+
JSPs. A JSP form action, link, or redirect that targets a url-pattern is an
|
|
8
|
+
edge from the JSP to the servlet behind it.
|
|
9
|
+
- Servlets: request.getRequestDispatcher(...).forward/include and
|
|
10
|
+
response.sendRedirect(...) name the JSP or url-pattern they hand off to;
|
|
11
|
+
new/injected service and DAO classes are outgoing edges to those units.
|
|
12
|
+
- JSPs: static includes (<%@ include file="…" %>), dynamic includes
|
|
13
|
+
(<jsp:include page="…">), <jsp:forward>, <a href="…">, <form action="…">,
|
|
14
|
+
jQuery $.ajax / $.get / $.post url targets, and JSTL <c:import>/<c:url>.
|
|
15
|
+
Resolve a path to the unit whose file name (without extension) matches.
|
|
16
|
+
- Tag libraries and shared layout fragments (header, footer, navigation) are
|
|
17
|
+
units only when they are files in the inventory.
|
|
18
|
+
|
|
19
|
+
Prioritise JSPs with zero incoming edges (are they reached through web.xml or
|
|
20
|
+
a redirect?), servlets nothing maps to, and fragments nothing includes. Calls to
|
|
21
|
+
the ESB, application-server services, and third-party libraries are not estate
|
|
22
|
+
edges — note them.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Units are JSP pages, JSP fragments (headers, footers, navigation, included
|
|
2
|
+
partials), servlets, DTO/service classes, and web.xml. Judge them as a web
|
|
3
|
+
estate, not a batch one:
|
|
4
|
+
|
|
5
|
+
- A page reached through a web.xml url-pattern, a link, a form action, or a
|
|
6
|
+
redirect is live even when the regex graph shows few callers — check the
|
|
7
|
+
refine notes before calling it dead. A page nothing reaches and nothing
|
|
8
|
+
includes is the retire candidate; give the evidence.
|
|
9
|
+
- Fragments with many includers are layout, not business logic: they are
|
|
10
|
+
migrated with the first page that needs them (same wave) rather than as a
|
|
11
|
+
wave of their own, and never "retire" while a live page includes them.
|
|
12
|
+
- Servlets and service classes that only front an ESB or backend service are
|
|
13
|
+
"wrap" candidates: the modernized application keeps calling the same
|
|
14
|
+
service through a port, and their business rules must not be re-implemented.
|
|
15
|
+
- web.xml and other descriptors are configuration, not migration units:
|
|
16
|
+
disposition "wrap" with a one-line rationale.
|
|
17
|
+
|
|
18
|
+
Waves are user journeys: keep a page, the servlet it posts to, and the
|
|
19
|
+
fragments it includes together; simple read-only screens first, then CRUD
|
|
20
|
+
screens, then multi-step session-backed flows last.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
You review one conversion increment for anti-corruption discipline. Judge the
|
|
2
|
+
diff against the Page Spec, not your taste. Flag as Critical:
|
|
3
|
+
|
|
4
|
+
- Any fetch/XMLHttpRequest/axios call outside a service adapter module.
|
|
5
|
+
- Any legacy DTO field name (acctNo-style abbreviations) in page code, port
|
|
6
|
+
types, or tests — the spec's domain renames are mandatory.
|
|
7
|
+
- Business logic in the client: fee/limit/eligibility calculations, rules the
|
|
8
|
+
legacy servlet or ESB service owned, or a mock adapter that embeds rules
|
|
9
|
+
instead of returning contract-shaped data.
|
|
10
|
+
- A page importing a mock adapter directly instead of going through the
|
|
11
|
+
registry/port seam.
|
|
12
|
+
- Port methods that do not match the OpenAPI contract's operations and shapes.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
You review one conversion increment for destination-repo fidelity — the new
|
|
2
|
+
page must read as if the resident team wrote it. Judge the diff against the
|
|
3
|
+
destination's CONTRIBUTING.md and its existing pages. Flag as Major:
|
|
4
|
+
|
|
5
|
+
- Hand-rolled UI where a design-system component exists (raw <table> instead
|
|
6
|
+
of DataTable, raw <input>+<label> instead of Field, ad-hoc wizard state
|
|
7
|
+
instead of Stepper).
|
|
8
|
+
- Styling outside the token/component classes: inline style objects, new CSS
|
|
9
|
+
files, hard-coded colors.
|
|
10
|
+
- Form handling that bypasses the house Form component's validation map.
|
|
11
|
+
- Tests that diverge from the house style (different render helpers, missing
|
|
12
|
+
AuthProvider wrapper, snapshot tests where the exemplars assert behaviour).
|
|
13
|
+
- Auth handled ad hoc instead of through useAuth()/AuthProvider.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
This is a J2EE web application: units are JSP pages and fragments, servlets and
|
|
2
|
+
supporting Java classes, and deployment descriptors. Regexes miss most of the
|
|
3
|
+
links such an application establishes indirectly:
|
|
4
|
+
|
|
5
|
+
- WEB-INF/web.xml: every servlet-mapping ties a url-pattern to a servlet; every
|
|
6
|
+
servlet-class names a Java unit; welcome-file and error-page entries name
|
|
7
|
+
JSPs. A JSP form action, link, or redirect that targets a url-pattern is an
|
|
8
|
+
edge from the JSP to the servlet behind it.
|
|
9
|
+
- Servlets: request.getRequestDispatcher(...).forward/include and
|
|
10
|
+
response.sendRedirect(...) name the JSP or url-pattern they hand off to;
|
|
11
|
+
new/injected service and DAO classes are outgoing edges to those units.
|
|
12
|
+
- JSPs: static includes (<%@ include file="…" %>), dynamic includes
|
|
13
|
+
(<jsp:include page="…">), <jsp:forward>, <a href="…">, <form action="…">,
|
|
14
|
+
jQuery $.ajax / $.get / $.post url targets, and JSTL <c:import>/<c:url>.
|
|
15
|
+
Resolve a path to the unit whose file name (without extension) matches.
|
|
16
|
+
- Tag libraries and shared layout fragments (header, footer, navigation) are
|
|
17
|
+
units only when they are files in the inventory.
|
|
18
|
+
|
|
19
|
+
Prioritise JSPs with zero incoming edges (are they reached through web.xml or
|
|
20
|
+
a redirect?), servlets nothing maps to, and fragments nothing includes. Calls to
|
|
21
|
+
the ESB, application-server services, and third-party libraries are not estate
|
|
22
|
+
edges — note them.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Units are JSP pages, JSP fragments (headers, footers, navigation, included
|
|
2
|
+
partials), servlets, DTO/service classes, and web.xml. Judge them as a web
|
|
3
|
+
estate, not a batch one:
|
|
4
|
+
|
|
5
|
+
- A page reached through a web.xml url-pattern, a link, a form action, or a
|
|
6
|
+
redirect is live even when the regex graph shows few callers — check the
|
|
7
|
+
refine notes before calling it dead. A page nothing reaches and nothing
|
|
8
|
+
includes is the retire candidate; give the evidence.
|
|
9
|
+
- Fragments with many includers are layout, not business logic: they are
|
|
10
|
+
migrated with the first page that needs them (same wave) rather than as a
|
|
11
|
+
wave of their own, and never "retire" while a live page includes them.
|
|
12
|
+
- Servlets and service classes that only front an ESB or backend service are
|
|
13
|
+
"wrap" candidates: the modernized application keeps calling the same
|
|
14
|
+
service through a port, and their business rules must not be re-implemented.
|
|
15
|
+
- web.xml and other descriptors are configuration, not migration units:
|
|
16
|
+
disposition "wrap" with a one-line rationale.
|
|
17
|
+
|
|
18
|
+
Waves are user journeys: keep a page, the servlet it posts to, and the
|
|
19
|
+
fragments it includes together; simple read-only screens first, then CRUD
|
|
20
|
+
screens, then multi-step session-backed flows last.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
This is a J2EE web application: units are JSP pages and fragments, servlets and
|
|
2
|
+
supporting Java classes, and deployment descriptors. Regexes miss most of the
|
|
3
|
+
links such an application establishes indirectly:
|
|
4
|
+
|
|
5
|
+
- WEB-INF/web.xml: every servlet-mapping ties a url-pattern to a servlet; every
|
|
6
|
+
servlet-class names a Java unit; welcome-file and error-page entries name
|
|
7
|
+
JSPs. A JSP form action, link, or redirect that targets a url-pattern is an
|
|
8
|
+
edge from the JSP to the servlet behind it.
|
|
9
|
+
- Servlets: request.getRequestDispatcher(...).forward/include and
|
|
10
|
+
response.sendRedirect(...) name the JSP or url-pattern they hand off to;
|
|
11
|
+
new/injected service and DAO classes are outgoing edges to those units.
|
|
12
|
+
- JSPs: static includes (<%@ include file="…" %>), dynamic includes
|
|
13
|
+
(<jsp:include page="…">), <jsp:forward>, <a href="…">, <form action="…">,
|
|
14
|
+
jQuery $.ajax / $.get / $.post url targets, and JSTL <c:import>/<c:url>.
|
|
15
|
+
Resolve a path to the unit whose file name (without extension) matches.
|
|
16
|
+
- Tag libraries and shared layout fragments (header, footer, navigation) are
|
|
17
|
+
units only when they are files in the inventory.
|
|
18
|
+
|
|
19
|
+
Prioritise JSPs with zero incoming edges (are they reached through web.xml or
|
|
20
|
+
a redirect?), servlets nothing maps to, and fragments nothing includes. Calls to
|
|
21
|
+
the ESB, application-server services, and third-party libraries are not estate
|
|
22
|
+
edges — note them.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Units are JSP pages, JSP fragments (headers, footers, navigation, included
|
|
2
|
+
partials), servlets, DTO/service classes, and web.xml. Judge them as a web
|
|
3
|
+
estate, not a batch one:
|
|
4
|
+
|
|
5
|
+
- A page reached through a web.xml url-pattern, a link, a form action, or a
|
|
6
|
+
redirect is live even when the regex graph shows few callers — check the
|
|
7
|
+
refine notes before calling it dead. A page nothing reaches and nothing
|
|
8
|
+
includes is the retire candidate; give the evidence.
|
|
9
|
+
- Fragments with many includers are layout, not business logic: they are
|
|
10
|
+
migrated with the first page that needs them (same wave) rather than as a
|
|
11
|
+
wave of their own, and never "retire" while a live page includes them.
|
|
12
|
+
- Servlets and service classes that only front an ESB or backend service are
|
|
13
|
+
"wrap" candidates: the modernized application keeps calling the same
|
|
14
|
+
service through a port, and their business rules must not be re-implemented.
|
|
15
|
+
- web.xml and other descriptors are configuration, not migration units:
|
|
16
|
+
disposition "wrap" with a one-line rationale.
|
|
17
|
+
|
|
18
|
+
Waves are user journeys: keep a page, the servlet it posts to, and the
|
|
19
|
+
fragments it includes together; simple read-only screens first, then CRUD
|
|
20
|
+
screens, then multi-step session-backed flows last.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Interactive shell and CLI for llm4ts: flow discovery, run-a-flow, and view",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@effect/platform-node": "4.0.0-beta.102",
|
|
52
|
-
"@llm4ts/core": "0.
|
|
53
|
-
"@llm4ts/
|
|
54
|
-
"@llm4ts/
|
|
55
|
-
"@llm4ts/runner": "0.
|
|
52
|
+
"@llm4ts/core": "0.15.0",
|
|
53
|
+
"@llm4ts/flow": "0.15.0",
|
|
54
|
+
"@llm4ts/modernize": "0.15.0",
|
|
55
|
+
"@llm4ts/runner": "0.15.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"effect": "4.0.0-beta.102"
|