@pmelab/gtd 29.0.0 → 29.1.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +29 -19
- package/claude/hooks/handoff.ts +2 -3
- package/claude/hooks/ship.ts +762 -62
- package/dist/chunks/{reserved-DoHUII2D.mjs → reserved-fqf-c9dc.mjs} +34 -9
- package/dist/chunks/{tail-C6YBG5zh.mjs → tail-Do11DVcw.mjs} +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +127 -41
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +2 -2
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +159 -48
- package/package.json +1 -1
- package/src/workflows/feature/feature.ts +6 -5
- package/src/workflows/feature/packages.test.ts +299 -5
- package/src/workflows/feature/packages.ts +109 -26
- package/src/workflows/feature/refactor.ts +9 -2
- package/src/workflows/feature/review.ts +25 -7
- package/src/workflows/feature/seam.ts +28 -8
- package/src/workflows/shared/text.test.ts +44 -0
- package/src/workflows/shared/text.ts +57 -10
|
@@ -15,46 +15,129 @@ import { build, fixSuite } from "./steps.js"
|
|
|
15
15
|
export interface DeclaredTest {
|
|
16
16
|
readonly level: "unit" | "e2e"
|
|
17
17
|
readonly path: string
|
|
18
|
+
readonly unchanged: boolean
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
const TEST_LINE =
|
|
21
|
+
const TEST_LINE = /^-\s+(unit|e2e):\s+`([^`]+)`(.*)$/
|
|
22
|
+
const MARKER = /^[ \t]+\(unchanged\)(?=\s|$)/i
|
|
23
|
+
const ITEM = /^\s*-\s/
|
|
21
24
|
|
|
22
|
-
/** The `## Tests` entries of a package file; malformed
|
|
25
|
+
/** The `## Tests` entries of a package file; malformed entries are ignored. */
|
|
23
26
|
export const declaredTests = (packageText: string): readonly DeclaredTest[] => {
|
|
24
27
|
const found: DeclaredTest[] = []
|
|
25
28
|
let inTests = false
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
let entry: string | undefined
|
|
30
|
+
const flush = () => {
|
|
31
|
+
const match = entry === undefined ? null : TEST_LINE.exec(entry)
|
|
32
|
+
if (match)
|
|
33
|
+
found.push({
|
|
34
|
+
level: match[1] as DeclaredTest["level"],
|
|
35
|
+
path: match[2]!,
|
|
36
|
+
unchanged: MARKER.test(match[3]!),
|
|
37
|
+
})
|
|
38
|
+
entry = undefined
|
|
39
|
+
}
|
|
40
|
+
// An entry is a list line plus its indented continuation lines (oxfmt wraps
|
|
41
|
+
// long ones); a blank line, a new item or a heading ends it.
|
|
42
|
+
for (const line of packageText.replace(/\r\n?/g, "\n").split("\n")) {
|
|
43
|
+
if (/^##\s/.test(line)) {
|
|
44
|
+
flush()
|
|
45
|
+
inTests = /^##\s+Tests\s*$/.test(line)
|
|
46
|
+
} else if (!inTests) continue
|
|
47
|
+
else if (ITEM.test(line)) {
|
|
48
|
+
flush()
|
|
49
|
+
entry = line.trim()
|
|
50
|
+
} else if (entry !== undefined && /^\s/.test(line) && line.trim() !== "")
|
|
51
|
+
entry = `${entry} ${line.trim()}`
|
|
52
|
+
else flush()
|
|
32
53
|
}
|
|
54
|
+
flush()
|
|
33
55
|
return found
|
|
34
56
|
}
|
|
35
57
|
|
|
58
|
+
export type DeclaredTestsTurn = "build" | "fix"
|
|
59
|
+
|
|
60
|
+
type Unmet = "notInDiff" | "modifiedMarked" | "addedMarked" | "deletedMarked" | "notInTree"
|
|
61
|
+
|
|
62
|
+
type Declared = { path: string; unchanged: boolean }
|
|
63
|
+
|
|
64
|
+
const diffBucket = (
|
|
65
|
+
test: Declared,
|
|
66
|
+
status: string | undefined,
|
|
67
|
+
turn: DeclaredTestsTurn,
|
|
68
|
+
satisfied: boolean,
|
|
69
|
+
): Unmet | undefined => {
|
|
70
|
+
if (test.unchanged) {
|
|
71
|
+
if (turn !== "build") return undefined
|
|
72
|
+
if (status === "modified") return "modifiedMarked"
|
|
73
|
+
return status === "added" ? "addedMarked" : undefined
|
|
74
|
+
}
|
|
75
|
+
return !satisfied && (status === undefined || status === "deleted") ? "notInDiff" : undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const treeBucket = (test: Declared, status: string | undefined): Unmet | undefined => {
|
|
79
|
+
if (read(test.path) !== undefined) return undefined
|
|
80
|
+
return test.unchanged && status === "deleted" ? "deletedMarked" : "notInTree"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const unmetBucket = (
|
|
84
|
+
test: Declared,
|
|
85
|
+
change: { status: string } | undefined,
|
|
86
|
+
turn: DeclaredTestsTurn,
|
|
87
|
+
satisfied: boolean,
|
|
88
|
+
): Unmet | undefined =>
|
|
89
|
+
diffBucket(test, change?.status, turn, satisfied) ?? treeBucket(test, change?.status)
|
|
90
|
+
|
|
36
91
|
/**
|
|
37
92
|
* Refuse a build or fix turn unless every test `pkg` declares exists and is in the
|
|
38
93
|
* diff since `since`. The package text is read as of `since`, so a builder
|
|
39
|
-
* editing its own `## Tests` cannot dodge the guard;
|
|
40
|
-
* written,
|
|
94
|
+
* editing its own `## Tests` (or adding `(unchanged)`) cannot dodge the guard;
|
|
95
|
+
* with `.gtd/SATISFIED.md` written, or the line marked, existing suffices. The
|
|
96
|
+
* build turn must neither modify nor add a marked test; a fix turn may edit it.
|
|
41
97
|
*/
|
|
42
|
-
export const requireDeclaredTests = (
|
|
98
|
+
export const requireDeclaredTests = (
|
|
99
|
+
pkg: string,
|
|
100
|
+
since: string,
|
|
101
|
+
turn: DeclaredTestsTurn = "fix",
|
|
102
|
+
): void => {
|
|
43
103
|
const range = changesSince(since)
|
|
44
104
|
const text = range.get(pkg)?.before ?? read(pkg) ?? ""
|
|
45
105
|
const satisfied = read(".gtd/SATISFIED.md") !== undefined
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
106
|
+
const buckets = {
|
|
107
|
+
notInDiff: [] as string[],
|
|
108
|
+
modifiedMarked: [] as string[],
|
|
109
|
+
addedMarked: [] as string[],
|
|
110
|
+
deletedMarked: [] as string[],
|
|
111
|
+
notInTree: [] as string[],
|
|
112
|
+
}
|
|
113
|
+
const entries = declaredTests(text)
|
|
114
|
+
const seen = new Set<string>()
|
|
115
|
+
for (const test of entries) {
|
|
116
|
+
// A file listed both ways is written by the package: the unmarked entry wins.
|
|
117
|
+
if (test.unchanged && entries.some((e) => !e.unchanged && e.path === test.path)) continue
|
|
118
|
+
const bucket = unmetBucket(test, range.get(test.path), turn, satisfied)
|
|
119
|
+
if (bucket && !seen.has(test.path)) {
|
|
120
|
+
seen.add(test.path)
|
|
121
|
+
buckets[bucket].push(test.path)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const { notInDiff, modifiedMarked, addedMarked, deletedMarked, notInTree } = buckets
|
|
125
|
+
const groups = [
|
|
126
|
+
["not in the package's diff — write them", notInDiff],
|
|
127
|
+
["marked (unchanged) but modified — revert them", modifiedMarked],
|
|
128
|
+
[
|
|
129
|
+
"marked (unchanged) but added by this turn — the marker promises an existing test; stop and leave it to a person",
|
|
130
|
+
addedMarked,
|
|
131
|
+
],
|
|
132
|
+
["marked (unchanged) but deleted — restore them", deletedMarked],
|
|
133
|
+
["missing from the tree", notInTree],
|
|
134
|
+
] as const
|
|
135
|
+
const reasons = groups.filter(([, paths]) => paths.length > 0)
|
|
136
|
+
if (reasons.length === 0) return
|
|
56
137
|
refuse(
|
|
57
|
-
`gtd land: declared-tests: ${pkg} declares tests
|
|
138
|
+
`gtd land: declared-tests: ${pkg} declares tests this turn left unmet:\n${reasons
|
|
139
|
+
.map(([reason, paths]) => `${reason}:\n${paths.map((path) => ` - ${path}`).join("\n")}`)
|
|
140
|
+
.join("\n")}`,
|
|
58
141
|
)
|
|
59
142
|
}
|
|
60
143
|
|
|
@@ -66,12 +149,12 @@ export const packageItem = async (pkg: string): Promise<void> => {
|
|
|
66
149
|
const from = head()
|
|
67
150
|
// Rechecked after every fix too: a fix deleting a red declared test would
|
|
68
151
|
// otherwise green the suite.
|
|
69
|
-
const declared = (turn: () => Promise<void
|
|
152
|
+
const declared = (turn: () => Promise<void>, kind: DeclaredTestsTurn) => async () => {
|
|
70
153
|
await turn()
|
|
71
|
-
requireDeclaredTests(pkg, from)
|
|
154
|
+
requireDeclaredTests(pkg, from, kind)
|
|
72
155
|
}
|
|
73
|
-
await declared(() => build(pkg))()
|
|
74
|
-
await healthy(declared(fixSuite))
|
|
156
|
+
await declared(() => build(pkg), "build")()
|
|
157
|
+
await healthy(declared(fixSuite, "fix"))
|
|
75
158
|
const last = glob(".gtd/packages/*.md").length === 1
|
|
76
159
|
await run("closing", removeScript([pkg, ".gtd/SATISFIED.md", ...(last ? [ARCHITECTURE] : [])]), {
|
|
77
160
|
label: "Closing out the package",
|
|
@@ -216,7 +216,13 @@ const refactorLap = async (p: Plan, guard: Guard, feedback?: string): Promise<vo
|
|
|
216
216
|
)
|
|
217
217
|
await t.agentWithSkills(
|
|
218
218
|
"building",
|
|
219
|
-
t.refactorBuildingPrompt(
|
|
219
|
+
t.refactorBuildingPrompt({
|
|
220
|
+
pkg: REFACTOR_PACKAGE,
|
|
221
|
+
strays,
|
|
222
|
+
pattern,
|
|
223
|
+
reference,
|
|
224
|
+
feedback: feedback !== undefined,
|
|
225
|
+
}),
|
|
220
226
|
{ label: "Refactoring", model: coder(), system: t.builderSystem() },
|
|
221
227
|
)
|
|
222
228
|
guard()
|
|
@@ -275,7 +281,8 @@ export const reviewedRefactor = async (
|
|
|
275
281
|
for (;;) {
|
|
276
282
|
await refactorLap(p, guard, collected)
|
|
277
283
|
// A person's edit at an escalation rest is never refused, as in the lap.
|
|
278
|
-
const outcome = await buildTail(
|
|
284
|
+
const outcome = await buildTail(entry, {
|
|
285
|
+
green: true,
|
|
279
286
|
guard,
|
|
280
287
|
guardRests: false,
|
|
281
288
|
lenses: lensesOf(vars.refactorQualityReviews ?? ""),
|
|
@@ -256,6 +256,10 @@ export interface FeedbackTarget {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
export interface BuildTailOptions {
|
|
259
|
+
/** Fix first when entered red; default `false`. */
|
|
260
|
+
readonly fixFirst?: boolean
|
|
261
|
+
/** Skip keeping green; default `false`. */
|
|
262
|
+
readonly green?: boolean
|
|
259
263
|
/** Runs after every code-changing turn: fix-check, quality fix, risk fix, nit fix, escalation describe. */
|
|
260
264
|
readonly guard?: Guard
|
|
261
265
|
/** `false`: `guard` skips the escalation's human rests. */
|
|
@@ -274,22 +278,36 @@ export interface BuildTailOptions {
|
|
|
274
278
|
}
|
|
275
279
|
|
|
276
280
|
/** The build tail: fix (when entered red), keep green unless `green`, the quality lap, then human review since `base`. */
|
|
277
|
-
export
|
|
281
|
+
export function buildTail(base: string, options?: BuildTailOptions): Promise<kit.ReviewOutcome>
|
|
282
|
+
/** @deprecated Pass `buildTail(base, { fixFirst, green, ...options })`. */
|
|
283
|
+
export function buildTail(
|
|
278
284
|
fixFirst: boolean,
|
|
279
285
|
base: string,
|
|
280
|
-
green
|
|
281
|
-
options
|
|
282
|
-
): Promise<kit.ReviewOutcome>
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
green?: boolean,
|
|
287
|
+
options?: Omit<BuildTailOptions, "fixFirst" | "green">,
|
|
288
|
+
): Promise<kit.ReviewOutcome>
|
|
289
|
+
export function buildTail(
|
|
290
|
+
first: boolean | string,
|
|
291
|
+
second?: string | BuildTailOptions,
|
|
292
|
+
third = false,
|
|
293
|
+
fourth: Omit<BuildTailOptions, "fixFirst" | "green"> = {},
|
|
294
|
+
): Promise<kit.ReviewOutcome> {
|
|
295
|
+
const base = typeof first === "string" ? first : (second as string)
|
|
296
|
+
const options: BuildTailOptions =
|
|
297
|
+
typeof first === "string"
|
|
298
|
+
? ((second as BuildTailOptions | undefined) ?? {})
|
|
299
|
+
: { ...fourth, fixFirst: first, green: third }
|
|
300
|
+
return tail(
|
|
301
|
+
options.fixFirst ?? false,
|
|
285
302
|
(escalations) => review(base, escalations, options),
|
|
286
|
-
green,
|
|
303
|
+
options.green ?? false,
|
|
287
304
|
options.guard,
|
|
288
305
|
options.lenses,
|
|
289
306
|
options.guardRests,
|
|
290
307
|
options.lensBase,
|
|
291
308
|
options.rules,
|
|
292
309
|
)
|
|
310
|
+
}
|
|
293
311
|
|
|
294
312
|
/**
|
|
295
313
|
* Undo the human's review-round code edits. A path changed since the human
|
|
@@ -41,17 +41,36 @@ export const naivePass = (rejection?: t.SeamRejection): Promise<void> =>
|
|
|
41
41
|
system: t.builderSystem(),
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
export
|
|
44
|
+
export interface ImplementOptions {
|
|
45
|
+
readonly reference?: string | undefined
|
|
46
|
+
readonly tier?: RiskTier | undefined
|
|
47
|
+
readonly amend?: boolean | undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function implement(options?: ImplementOptions): Promise<void>
|
|
51
|
+
/** @deprecated Pass `implement({ reference, tier, amend })`. */
|
|
52
|
+
export function implement(
|
|
45
53
|
reference: string | undefined,
|
|
46
54
|
tier?: RiskTier,
|
|
47
55
|
amend?: boolean,
|
|
48
|
-
): Promise<void>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
): Promise<void>
|
|
57
|
+
export function implement(
|
|
58
|
+
first?: string | ImplementOptions,
|
|
59
|
+
tier?: RiskTier,
|
|
60
|
+
amend?: boolean,
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
const options: ImplementOptions =
|
|
63
|
+
typeof first === "object" && first !== null ? first : { reference: first, tier, amend }
|
|
64
|
+
return scope("implement", async () => {
|
|
65
|
+
await t.agentWithSkills(
|
|
66
|
+
"building",
|
|
67
|
+
t.implementBuildingPrompt(options.reference, options.tier, options.amend),
|
|
68
|
+
{
|
|
69
|
+
label: "Implementing",
|
|
70
|
+
model: coder(),
|
|
71
|
+
system: t.builderSystem(),
|
|
72
|
+
},
|
|
73
|
+
)
|
|
55
74
|
await healthy(() =>
|
|
56
75
|
t.agentWithSkills("fix.suite.fixing", t.implementFixPrompt(), {
|
|
57
76
|
label: "Fixing the check",
|
|
@@ -62,6 +81,7 @@ export const implement = (
|
|
|
62
81
|
)
|
|
63
82
|
await run("closing", removeScript([REQUIREMENTS]), { label: "Closing out the implementation" })
|
|
64
83
|
})
|
|
84
|
+
}
|
|
65
85
|
|
|
66
86
|
interface Decision {
|
|
67
87
|
readonly reference: string
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
buildReviewReviewingPrompt,
|
|
10
10
|
agentWithSkills,
|
|
11
11
|
architectureAuthorPrompt,
|
|
12
|
+
architectureDecomposePrompt,
|
|
13
|
+
packagesItemBuildingPrompt,
|
|
12
14
|
architectureGateAnswerMessage,
|
|
13
15
|
buildFixPrompt,
|
|
14
16
|
buildFixQualityPrompt,
|
|
@@ -1266,3 +1268,45 @@ describe("buildReviewReviewingPrompt lap wording", () => {
|
|
|
1266
1268
|
},
|
|
1267
1269
|
)
|
|
1268
1270
|
})
|
|
1271
|
+
|
|
1272
|
+
describe("architectureDecomposePrompt tests rule", () => {
|
|
1273
|
+
it("states one rule: write-or-change, relied-on marked (unchanged), no other untouched", () => {
|
|
1274
|
+
const prompt = renderText(() => architectureDecomposePrompt()).replace(/\s+/g, " ")
|
|
1275
|
+
expect(prompt).toContain("List every test the package writes or changes")
|
|
1276
|
+
expect(prompt).toContain("also list an existing test a criterion relies on")
|
|
1277
|
+
expect(prompt).toContain("list no other untouched test")
|
|
1278
|
+
expect(prompt).toContain("- unit: \\`<path>\\` (unchanged)")
|
|
1279
|
+
expect(prompt).toContain("- e2e: \\`<path>\\` (unchanged)")
|
|
1280
|
+
expect(prompt).toContain("only decompose places the marker")
|
|
1281
|
+
})
|
|
1282
|
+
})
|
|
1283
|
+
|
|
1284
|
+
describe("packagesItemBuildingPrompt", () => {
|
|
1285
|
+
const prompt = renderText(() => packagesItemBuildingPrompt(".gtd/packages/01-a.md")).replace(
|
|
1286
|
+
/\s+/g,
|
|
1287
|
+
" ",
|
|
1288
|
+
)
|
|
1289
|
+
it("carries the evidence kinds and the already-done check", () => {
|
|
1290
|
+
for (const w of [
|
|
1291
|
+
"evidence per criterion",
|
|
1292
|
+
"commit, file, symbol, issue URL, or command output — and change nothing else",
|
|
1293
|
+
"it was not already done",
|
|
1294
|
+
"via `gh`",
|
|
1295
|
+
])
|
|
1296
|
+
expect(prompt).toContain(w)
|
|
1297
|
+
})
|
|
1298
|
+
it("states the already-met, no-tests out-of-tree and implement-normally rules flat", () => {
|
|
1299
|
+
expect(prompt).toContain("Every criterion already met in the tree")
|
|
1300
|
+
expect(prompt).toContain("No `## Tests` and work outside the tree")
|
|
1301
|
+
expect(prompt).toContain(
|
|
1302
|
+
"Any other package**: implement normally, write no `.gtd/SATISFIED.md`",
|
|
1303
|
+
)
|
|
1304
|
+
})
|
|
1305
|
+
it("says an (unchanged) line must exist and stay untouched", () => {
|
|
1306
|
+
expect(prompt).toContain("(unchanged)")
|
|
1307
|
+
expect(prompt).toContain("stay untouched")
|
|
1308
|
+
expect(prompt).toContain("creating, editing or deleting it refuses the turn")
|
|
1309
|
+
expect(prompt).toContain("must exist before the turn")
|
|
1310
|
+
expect(prompt).toContain("stop without creating it")
|
|
1311
|
+
})
|
|
1312
|
+
})
|
|
@@ -376,7 +376,12 @@ ${stateFileRules}
|
|
|
376
376
|
- Every package with tests has a \`## Tests\` section, one line per
|
|
377
377
|
declared test, copied from the architecture entries:
|
|
378
378
|
\`- unit: \\\`<path>\\\`\` or \`- e2e: \\\`<path>\\\`\`. Chore packages have
|
|
379
|
-
none
|
|
379
|
+
none. List every test the package writes or changes; also list an
|
|
380
|
+
existing test a criterion relies on, marked \`(unchanged)\` —
|
|
381
|
+
\`- unit: \\\`<path>\\\` (unchanged)\` or
|
|
382
|
+
\`- e2e: \\\`<path>\\\` (unchanged)\`; list no other untouched test;
|
|
383
|
+
never mark a file the package also writes or changes — list it once,
|
|
384
|
+
unmarked; only decompose places the marker
|
|
380
385
|
- Leave everything uncommitted and finish
|
|
381
386
|
`
|
|
382
387
|
|
|
@@ -413,15 +418,26 @@ export const packagesItemBuildingPrompt = (pkg: string): string =>
|
|
|
413
418
|
never delete the package file (the declared-tests guard reads it
|
|
414
419
|
after you)
|
|
415
420
|
- The package to implement is \`${pkg}\`
|
|
416
|
-
- First check
|
|
417
|
-
an earlier fix turn may already
|
|
418
|
-
|
|
419
|
-
with
|
|
420
|
-
symbol
|
|
421
|
-
|
|
421
|
+
- First check the acceptance criteria against the current tree —
|
|
422
|
+
an earlier fix turn may already meet them
|
|
423
|
+
- **Every criterion already met in the tree**: write
|
|
424
|
+
\`.gtd/SATISFIED.md\` with evidence per criterion — commit, file,
|
|
425
|
+
symbol, issue URL, or command output — and change nothing else
|
|
426
|
+
- **No \`## Tests\` and work outside the tree**: first check it was
|
|
427
|
+
not already done by an earlier attempt (e.g. an existing issue
|
|
428
|
+
with the same title via \`gh\`) and reuse that as evidence, or do
|
|
429
|
+
the work; then write \`.gtd/SATISFIED.md\` with that evidence and
|
|
430
|
+
change nothing else in the tree
|
|
431
|
+
- **Any other package**: implement normally, write no
|
|
432
|
+
\`.gtd/SATISFIED.md\`
|
|
422
433
|
- Write the tests the package declares under \`## Tests\` first,
|
|
423
434
|
then the code that makes them pass; the turn is refused unless
|
|
424
|
-
every declared test is in your diff
|
|
435
|
+
every declared test is in your diff — except a line marked
|
|
436
|
+
\`(unchanged)\`, which must exist before the turn and stay
|
|
437
|
+
untouched — creating, editing or deleting it refuses the turn. If
|
|
438
|
+
it does not exist yet, or the marker is wrong because the package
|
|
439
|
+
must change that test, stop without creating it and leave it to
|
|
440
|
+
a person
|
|
425
441
|
- A declared e2e test is a scenario in the repository's own e2e
|
|
426
442
|
framework for the behaviour its \`## E2E Scenarios\` entry names.
|
|
427
443
|
Word it to fit the existing steps — reuse and generalise them
|
|
@@ -457,13 +473,44 @@ ${strays.map((path) => ` - \`${displayPath(path)}\``).join("\n")}
|
|
|
457
473
|
|
|
458
474
|
const withRules = (rules?: string): string => (rules === undefined ? "" : `${rules}\n`)
|
|
459
475
|
|
|
460
|
-
|
|
476
|
+
interface RefactorBuildingArgs {
|
|
477
|
+
pkg: string
|
|
478
|
+
strays: readonly string[]
|
|
479
|
+
pattern: string
|
|
480
|
+
reference?: string | undefined
|
|
481
|
+
feedback?: boolean | undefined
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export function refactorBuildingPrompt(args: RefactorBuildingArgs): string
|
|
485
|
+
/** @deprecated Pass `refactorBuildingPrompt({ pkg, strays, pattern, reference, feedback })`. */
|
|
486
|
+
export function refactorBuildingPrompt(
|
|
461
487
|
pkg: string,
|
|
462
488
|
strays: readonly string[],
|
|
463
489
|
pattern: string,
|
|
464
490
|
reference?: string,
|
|
491
|
+
feedback?: boolean,
|
|
492
|
+
): string
|
|
493
|
+
export function refactorBuildingPrompt(
|
|
494
|
+
first: string | RefactorBuildingArgs,
|
|
495
|
+
strays?: readonly string[],
|
|
496
|
+
pattern?: string,
|
|
497
|
+
reference?: string,
|
|
498
|
+
feedback?: boolean,
|
|
499
|
+
): string {
|
|
500
|
+
return refactorBuildingPromptOf(
|
|
501
|
+
typeof first === "string"
|
|
502
|
+
? { pkg: first, strays: strays ?? [], pattern: pattern ?? "", reference, feedback }
|
|
503
|
+
: first,
|
|
504
|
+
)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const refactorBuildingPromptOf = ({
|
|
508
|
+
pkg,
|
|
509
|
+
strays,
|
|
510
|
+
pattern,
|
|
511
|
+
reference,
|
|
465
512
|
feedback = false,
|
|
466
|
-
): string =>
|
|
513
|
+
}: RefactorBuildingArgs): string =>
|
|
467
514
|
`${stateFileRules}
|
|
468
515
|
- The package to implement is \`${pkg}\`; never delete it
|
|
469
516
|
${
|