@pmelab/gtd 58.2.0 → 59.0.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 +120 -395
- package/dist/chunks/{Schema-B3o6eRNU.mjs → testkit-07Z_ENch.mjs} +13503 -3460
- package/dist/chunks/{wire-C12dahrH.mjs → wire-BTUhRGXj.mjs} +1 -1
- package/dist/flows.mjs +1 -1
- package/dist/gtd.bundle.mjs +484 -497
- package/dist/testkit/gherkin.mjs +145 -65
- package/dist/testkit.mjs +1 -1
- package/dist/wire.mjs +1 -1
- package/package.json +3 -42
- package/skills/authoring/SKILL.md +34 -27
- package/src/cli/Cli.test.ts +148 -71
- package/src/cli/Cli.ts +384 -463
- package/src/cli/Help.test.ts +47 -0
- package/src/cli/Help.ts +70 -0
- package/src/cli/Program.test.ts +127 -33
- package/src/cli/Program.ts +70 -25
- package/src/edge/Edge.ts +4 -4
- package/src/emit/Emit.test.ts +1 -1
- package/src/emit/Emit.ts +1 -1
- package/src/flags/index.test.ts +0 -13
- package/src/flags/index.ts +1 -3
- package/src/flows/factory.ts +3 -1
- package/src/gherkin/index.ts +135 -71
- package/src/gherkin/messages.test.ts +90 -0
- package/src/gherkin/messages.ts +84 -0
- package/src/install/Install.test.ts +14 -11
- package/src/install/Install.ts +38 -15
- package/src/install/index.ts +1 -2
- package/src/replay/Replay.test.ts +1 -1
- package/src/replay/index.ts +1 -1
- package/src/step/planStart.ts +6 -1
- package/src/testkit/TestRepo.test.ts +47 -2
- package/src/testkit/TestRepo.ts +51 -6
- package/src/twins/EmittedScriptRecognizer.ts +5 -8
- package/src/twins/FlowScripts.ts +1 -1
- package/src/wire/Land.ts +1 -1
- package/src/workflow/index.ts +1 -0
- package/src/workflow/load.test.ts +138 -113
- package/src/workflow/load.ts +7 -7
- package/src/workflow/skills.test.ts +5 -5
- package/src/workflow/skills.ts +1 -1
- package/src/workflow/vars.test.ts +30 -3
- package/src/workflow/vars.ts +34 -2
- package/src/workflow-kit/index.ts +1 -1
- package/dist/chunks/reserved-lMYCaapY.mjs +0 -1990
- package/dist/chunks/review-Bfaror38.mjs +0 -427
- package/dist/chunks/tail-Bd7L940p.mjs +0 -355
- package/dist/chunks/testkit-JDtpNKyi.mjs +0 -10410
- package/dist/workflows/cleanup.mjs +0 -36
- package/dist/workflows/feature.mjs +0 -1018
- package/dist/workflows/fix.mjs +0 -41
- package/dist/workflows/review.mjs +0 -45
- package/dist/workflows/rollout.mjs +0 -48
- package/dist/workflows.mjs +0 -40651
- package/src/install/ConfigScaffold.ts +0 -22
- package/src/workflows/cleanup/cleanup-path.feature +0 -94
- package/src/workflows/cleanup/cleanup.feature +0 -315
- package/src/workflows/cleanup/cleanup.test.ts +0 -276
- package/src/workflows/cleanup/cleanup.ts +0 -52
- package/src/workflows/cleanup/index.ts +0 -6
- package/src/workflows/feature/acceptance-criteria-tier.feature +0 -25
- package/src/workflows/feature/acceptance-criteria.feature +0 -389
- package/src/workflows/feature/criteria-coverage.feature +0 -102
- package/src/workflows/feature/criteria.test.ts +0 -147
- package/src/workflows/feature/criteria.ts +0 -267
- package/src/workflows/feature/fast-path.feature +0 -93
- package/src/workflows/feature/feature.test.ts +0 -338
- package/src/workflows/feature/feature.ts +0 -156
- package/src/workflows/feature/green.ts +0 -19
- package/src/workflows/feature/index.ts +0 -18
- package/src/workflows/feature/lens-models.feature +0 -69
- package/src/workflows/feature/lens-review.feature +0 -566
- package/src/workflows/feature/naive-acceptance.feature +0 -147
- package/src/workflows/feature/naive-test.feature +0 -263
- package/src/workflows/feature/one-step-land.feature +0 -93
- package/src/workflows/feature/planning.ts +0 -41
- package/src/workflows/feature/refactor-absorbs.feature +0 -131
- package/src/workflows/feature/refactor-first.feature +0 -158
- package/src/workflows/feature/refactor-lens-models.feature +0 -79
- package/src/workflows/feature/refactor-rebuild.feature +0 -273
- package/src/workflows/feature/refactor-review.feature +0 -1528
- package/src/workflows/feature/refactor.test.ts +0 -1220
- package/src/workflows/feature/refactor.ts +0 -368
- package/src/workflows/feature/release-flag.feature +0 -167
- package/src/workflows/feature/release-flag.test.ts +0 -49
- package/src/workflows/feature/release-flag.ts +0 -46
- package/src/workflows/feature/review-gate.feature +0 -241
- package/src/workflows/feature/reviewer-walkthrough.feature +0 -142
- package/src/workflows/feature/risk-tier.feature +0 -351
- package/src/workflows/feature/seam-rejection.feature +0 -146
- package/src/workflows/feature/seam.feature +0 -297
- package/src/workflows/feature/seam.test.ts +0 -527
- package/src/workflows/feature/seam.ts +0 -426
- package/src/workflows/feature/standards.feature +0 -354
- package/src/workflows/feature/steps.test.ts +0 -65
- package/src/workflows/feature/steps.ts +0 -45
- package/src/workflows/feature/tier-pin.feature +0 -66
- package/src/workflows/feature/tier.test.ts +0 -166
- package/src/workflows/feature/tier.ts +0 -56
- package/src/workflows/fix/fix-lens-origin.feature +0 -114
- package/src/workflows/fix/fix-quality.feature +0 -212
- package/src/workflows/fix/fix.feature +0 -79
- package/src/workflows/fix/fix.test.ts +0 -74
- package/src/workflows/fix/fix.ts +0 -52
- package/src/workflows/fix/index.ts +0 -6
- package/src/workflows/index.ts +0 -47
- package/src/workflows/review/index.ts +0 -6
- package/src/workflows/review/review-door.feature +0 -199
- package/src/workflows/review/review.test.ts +0 -27
- package/src/workflows/review/review.ts +0 -60
- package/src/workflows/rollout/index.ts +0 -6
- package/src/workflows/rollout/rollout.feature +0 -510
- package/src/workflows/rollout/rollout.test.ts +0 -263
- package/src/workflows/rollout/rollout.ts +0 -82
- package/src/workflows/shared/__fixtures__/feature-collecting-prompt.txt +0 -98
- package/src/workflows/shared/access.test.ts +0 -62
- package/src/workflows/shared/access.ts +0 -24
- package/src/workflows/shared/config.fixture.ts +0 -12
- package/src/workflows/shared/fences.ts +0 -24
- package/src/workflows/shared/health.test.ts +0 -148
- package/src/workflows/shared/health.ts +0 -191
- package/src/workflows/shared/learn.test.ts +0 -140
- package/src/workflows/shared/learn.ts +0 -201
- package/src/workflows/shared/lenses.test.ts +0 -39
- package/src/workflows/shared/lenses.ts +0 -294
- package/src/workflows/shared/options.test.ts +0 -157
- package/src/workflows/shared/options.ts +0 -92
- package/src/workflows/shared/progress.feature +0 -124
- package/src/workflows/shared/prose.ts +0 -263
- package/src/workflows/shared/recheck.feature +0 -60
- package/src/workflows/shared/registry.test.ts +0 -345
- package/src/workflows/shared/registry.ts +0 -183
- package/src/workflows/shared/reserved.ts +0 -11
- package/src/workflows/shared/review.test.ts +0 -1062
- package/src/workflows/shared/review.ts +0 -446
- package/src/workflows/shared/skills.test.ts +0 -121
- package/src/workflows/shared/skills.ts +0 -49
- package/src/workflows/shared/standards.test.ts +0 -90
- package/src/workflows/shared/standards.ts +0 -156
- package/src/workflows/shared/steps.test.ts +0 -219
- package/src/workflows/shared/steps.ts +0 -201
- package/src/workflows/shared/tail.test.ts +0 -454
- package/src/workflows/shared/tail.ts +0 -264
- package/src/workflows/shared/text.fixture.ts +0 -128
- package/src/workflows/shared/text.test.ts +0 -1430
- package/src/workflows/shared/text.ts +0 -1700
- package/src/workflows/shared/threads.test.ts +0 -129
- package/src/workflows/shared/vars.ts +0 -47
- package/src/workflows/shared/workflows.steps.ts +0 -54
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
read,
|
|
3
|
-
refuse,
|
|
4
|
-
removeScript,
|
|
5
|
-
run,
|
|
6
|
-
scope,
|
|
7
|
-
showScript,
|
|
8
|
-
vars,
|
|
9
|
-
writeScript,
|
|
10
|
-
} from "../../flows/index.js"
|
|
11
|
-
import { reviewRisks } from "../../steering/index.js"
|
|
12
|
-
import * as kit from "../../workflow-kit/index.js"
|
|
13
|
-
import { guarded, healthy, type EscalationCount, type Guard } from "./health.js"
|
|
14
|
-
import {
|
|
15
|
-
awaitReview,
|
|
16
|
-
fixCheck,
|
|
17
|
-
fixRisks,
|
|
18
|
-
QUALITY,
|
|
19
|
-
REQUIREMENTS,
|
|
20
|
-
REVIEW,
|
|
21
|
-
reviewing,
|
|
22
|
-
reviewMissing,
|
|
23
|
-
reviewQuality,
|
|
24
|
-
} from "./steps.js"
|
|
25
|
-
import { findingsOf, NO_FINDINGS, triageFindings, type Finding, type LensReport } from "./lenses.js"
|
|
26
|
-
import * as t from "./text.js"
|
|
27
|
-
|
|
28
|
-
/** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
|
|
29
|
-
export const REVIEW_ORIG = ".gtd/REVIEW.orig.md"
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* One review turn per lens over the whole change, each appending every
|
|
33
|
-
* finding to `.gtd/QUALITY.md`. Resolves `"findings"` when that file
|
|
34
|
-
* has any.
|
|
35
|
-
*/
|
|
36
|
-
export const qualityLap = (
|
|
37
|
-
guard?: Guard,
|
|
38
|
-
lenses?: readonly string[],
|
|
39
|
-
base?: string,
|
|
40
|
-
): Promise<"clean" | "findings"> => lensLap(guard, lenses, base, undefined, () => {})
|
|
41
|
-
|
|
42
|
-
const COVERAGE = "criteria-coverage"
|
|
43
|
-
|
|
44
|
-
// `seen` gets each lens and every finding in the file once its turn is done.
|
|
45
|
-
const lensLap = (
|
|
46
|
-
guard: Guard | undefined,
|
|
47
|
-
lenses: readonly string[] | undefined,
|
|
48
|
-
base: string | undefined,
|
|
49
|
-
given: string | undefined,
|
|
50
|
-
seen: (lens: string, findings: readonly Finding[]) => void,
|
|
51
|
-
): Promise<"clean" | "findings"> => {
|
|
52
|
-
const requirements = given ?? read(REQUIREMENTS)
|
|
53
|
-
return kit.qualityLap(
|
|
54
|
-
(lenses ?? kit.lensEntriesOf(vars.qualityReviews ?? "")).filter(
|
|
55
|
-
(entry) => requirements !== undefined || kit.lensOf(entry).lens !== COVERAGE,
|
|
56
|
-
),
|
|
57
|
-
guarded(async (entry: string) => {
|
|
58
|
-
const { lens, model } = kit.lensOf(entry)
|
|
59
|
-
await reviewQuality(lens, base, model, lens === COVERAGE ? requirements : undefined)
|
|
60
|
-
seen(lens, findingsOf(read(QUALITY) ?? ""))
|
|
61
|
-
}, guard),
|
|
62
|
-
QUALITY,
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// What the fixer left is in the lap's report by now; the next lap must not append to it.
|
|
67
|
-
const closeQuality = async (): Promise<void> => {
|
|
68
|
-
if (read(QUALITY) === undefined) return
|
|
69
|
-
await run("quality.closing", removeScript([QUALITY]), { label: "Clearing quality findings" })
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** `.gtd/REVIEW.md` removed at the end of a round at commit `round`. */
|
|
73
|
-
export const closeReview = (round: string): Promise<void> =>
|
|
74
|
-
run("review.closing", removeScript([REVIEW, REVIEW_ORIG]), {
|
|
75
|
-
label: "Closing the review",
|
|
76
|
-
base: round,
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
/** Refuses while a reviewer's notes still await `gtd door fix`. */
|
|
80
|
-
export const refusePendingHandOff = (): void => {
|
|
81
|
-
if (read(REVIEW_ORIG) !== undefined) {
|
|
82
|
-
refuse(
|
|
83
|
-
`gtd: ${REVIEW_ORIG} is a pending hand-off — run \`gtd door fix\`, or remove ${REVIEW} and ${REVIEW_ORIG}.`,
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface TailOptions {
|
|
89
|
-
/** Fix first when entered red; default `false`. */
|
|
90
|
-
readonly fixFirst?: boolean | undefined
|
|
91
|
-
/** Asserts the tree is already green, so the tail skips keep-green; default `false`. */
|
|
92
|
-
readonly green?: boolean | undefined
|
|
93
|
-
/** Runs after every code-changing turn: fix-check, quality fix, risk fix, nit fix, escalation describe. */
|
|
94
|
-
readonly guard?: Guard | undefined
|
|
95
|
-
/** Quality-lap lens entries, `<lens>` or `<lens>=<model>`; default `lensEntriesOf(vars.qualityReviews)`. */
|
|
96
|
-
readonly lenses?: readonly string[] | undefined
|
|
97
|
-
/** `false` leaves the escalation's human rests unguarded; default `true`. */
|
|
98
|
-
readonly guardRests?: boolean | undefined
|
|
99
|
-
/** The commit the quality lap reviews from; absent: `start()`. */
|
|
100
|
-
readonly lensBase?: string | undefined
|
|
101
|
-
/** Appended to every fix and describe prompt of the tail. */
|
|
102
|
-
readonly rules?: string | undefined
|
|
103
|
-
/**
|
|
104
|
-
* The requirements the change was built to, for the `criteria-coverage`
|
|
105
|
-
* lens; absent: `.gtd/REQUIREMENTS.md`, and with neither the lens is skipped.
|
|
106
|
-
*/
|
|
107
|
-
readonly requirements?: string | undefined
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* The build tail: fix (when entered red), keep green, the quality lap — a
|
|
112
|
-
* judge sorts its findings and the mechanical ones are fixed — then the
|
|
113
|
-
* finding gate over the rest, then `finish` with what became of them all.
|
|
114
|
-
*/
|
|
115
|
-
export function tail<T>(
|
|
116
|
-
finish: (escalations: EscalationCount, report: LensReport) => Promise<T>,
|
|
117
|
-
options?: TailOptions,
|
|
118
|
-
): Promise<T>
|
|
119
|
-
/** @deprecated Pass `tail(finish, { fixFirst, green, guard, lenses, guardRests, lensBase, rules })`. */
|
|
120
|
-
export function tail<T>(
|
|
121
|
-
fixFirst: boolean,
|
|
122
|
-
finish: (escalations: EscalationCount, report: LensReport) => Promise<T>,
|
|
123
|
-
green?: boolean,
|
|
124
|
-
guard?: Guard,
|
|
125
|
-
lenses?: readonly string[],
|
|
126
|
-
guardRests?: boolean,
|
|
127
|
-
lensBase?: string,
|
|
128
|
-
rules?: string,
|
|
129
|
-
): Promise<T>
|
|
130
|
-
export function tail<T>(first: unknown, second?: unknown, ...rest: unknown[]): Promise<T> {
|
|
131
|
-
const form = tailForm<T>(first, second, rest)
|
|
132
|
-
// Before any step runs, so a bad call never leaves a half-walked flow.
|
|
133
|
-
if (form === undefined) {
|
|
134
|
-
return Promise.reject(
|
|
135
|
-
new Error(
|
|
136
|
-
"tail takes tail(finish, options?) or the deprecated tail(fixFirst, finish, green?, guard?, lenses?, guardRests?, lensBase?, rules?); this call fits neither",
|
|
137
|
-
),
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
const [finish, options] = form
|
|
141
|
-
return runTail(finish, options)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
type Finish<T> = (escalations: EscalationCount, report: LensReport) => Promise<T>
|
|
145
|
-
|
|
146
|
-
// null reads as absent; an un-awaited Promise is a mistake, not options.
|
|
147
|
-
export const isOptionalObject = (value: unknown): value is object | null | undefined =>
|
|
148
|
-
value == null ||
|
|
149
|
-
(typeof value === "object" &&
|
|
150
|
-
!Array.isArray(value) &&
|
|
151
|
-
typeof (value as { then?: unknown }).then !== "function")
|
|
152
|
-
|
|
153
|
-
// Picks the form by shape only: the deprecated form never validated its
|
|
154
|
-
// positionals, and still runs whatever it ran before.
|
|
155
|
-
const tailForm = <T>(
|
|
156
|
-
first: unknown,
|
|
157
|
-
second: unknown,
|
|
158
|
-
rest: readonly unknown[],
|
|
159
|
-
): [Finish<T>, TailOptions] | undefined => {
|
|
160
|
-
// A function first is a misplaced finish, never a fixFirst.
|
|
161
|
-
if (typeof second === "function" && typeof first !== "function") {
|
|
162
|
-
const [green, guard, lenses, guardRests, lensBase, rules] = rest
|
|
163
|
-
const options = { fixFirst: first, green, guard, lenses, guardRests, lensBase, rules }
|
|
164
|
-
return [second as Finish<T>, options as TailOptions]
|
|
165
|
-
}
|
|
166
|
-
if (typeof first === "function" && isOptionalObject(second) && rest.every((v) => v == null))
|
|
167
|
-
return [first as Finish<T>, (second as TailOptions | null | undefined) ?? {}]
|
|
168
|
-
return undefined
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const runTail = <T>(finish: Finish<T>, options: TailOptions): Promise<T> =>
|
|
172
|
-
scope("build", async () => {
|
|
173
|
-
const { guard, lenses, lensBase, rules, requirements } = options
|
|
174
|
-
// Only undefined defaults: a null reaches every escalation as falsy, as it reaches review's.
|
|
175
|
-
const guardRests = options.guardRests === undefined ? true : options.guardRests
|
|
176
|
-
const escalations: EscalationCount = { rounds: 0 }
|
|
177
|
-
const fix = () => fixCheck(rules)
|
|
178
|
-
// fixFirst's own fix + healthy below already is the full run.
|
|
179
|
-
// Truthy, not `true`: an untyped config's flags read as they always did.
|
|
180
|
-
if (options.fixFirst) {
|
|
181
|
-
await guarded(fix, guard)()
|
|
182
|
-
await healthy(fix, { fixesSoFar: 1, escalations, guard, guardRests, rules })
|
|
183
|
-
} else if (!options.green) await healthy(fix, { escalations, guard, guardRests, rules })
|
|
184
|
-
// The lap runs once a tail: after its findings are fixed, `finish` follows.
|
|
185
|
-
// Lenses append, so a title the file did not hold before a lens's turn is that lens's.
|
|
186
|
-
const byTitle: { title: string; lens: string }[] = []
|
|
187
|
-
const lap = await lensLap(guard, lenses, lensBase, requirements, (lens, findings) => {
|
|
188
|
-
const before = byTitle.map((f) => f.title)
|
|
189
|
-
for (const { title } of findings) {
|
|
190
|
-
const at = before.indexOf(title)
|
|
191
|
-
if (at >= 0) before.splice(at, 1)
|
|
192
|
-
else byTitle.push({ title, lens })
|
|
193
|
-
}
|
|
194
|
-
})
|
|
195
|
-
// Asked once per finding in file order, so a title two lenses wrote goes to each in turn.
|
|
196
|
-
const lensOf = (f: Finding): string | undefined => {
|
|
197
|
-
const at = byTitle.findIndex((e) => e.title === f.title)
|
|
198
|
-
return at < 0 ? undefined : byTitle.splice(at, 1)[0]!.lens
|
|
199
|
-
}
|
|
200
|
-
const report =
|
|
201
|
-
lap === "findings"
|
|
202
|
-
? await triageFindings({ escalations, guard, guardRests, rules, lensOf })
|
|
203
|
-
: NO_FINDINGS
|
|
204
|
-
await closeQuality()
|
|
205
|
-
return finish(escalations, report)
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* The fix workflow's human review since `base`: a sign-off closes
|
|
210
|
-
* `.gtd/REVIEW.md`; a note leaves it, with the reviewer's own version as
|
|
211
|
-
* `REVIEW_ORIG`, for the next `fix` to read — never a re-plan.
|
|
212
|
-
*/
|
|
213
|
-
export const handOffReview = (
|
|
214
|
-
base: string,
|
|
215
|
-
escalations: EscalationCount,
|
|
216
|
-
guard?: Guard,
|
|
217
|
-
report: LensReport = NO_FINDINGS,
|
|
218
|
-
): Promise<void> => {
|
|
219
|
-
// Set by a risk fix, so the re-review written next is summarised whatever it marks.
|
|
220
|
-
let riskFixed = false
|
|
221
|
-
return kit.reviewToHandOff(base, {
|
|
222
|
-
file: REVIEW,
|
|
223
|
-
reviewing: guarded(async (b, carry) => {
|
|
224
|
-
const afterRiskFix = riskFixed
|
|
225
|
-
riskFixed = false
|
|
226
|
-
await reviewing(b, carry)
|
|
227
|
-
const review = read(REVIEW)
|
|
228
|
-
// A risk-marked draft is fixed and written again first: the re-reviewer edits it, never gtd's summary.
|
|
229
|
-
if (review === undefined || (!afterRiskFix && reviewRisks(review).length > 0)) return
|
|
230
|
-
const full = t.withSummary(review, { report })
|
|
231
|
-
if (full !== review) {
|
|
232
|
-
await run("review.carrying", writeScript(REVIEW, full), {
|
|
233
|
-
label: "Adding the quality lap's summary",
|
|
234
|
-
})
|
|
235
|
-
}
|
|
236
|
-
}, guard),
|
|
237
|
-
fixRisks: guarded((risks) => {
|
|
238
|
-
riskFixed = true
|
|
239
|
-
return fixRisks(risks)
|
|
240
|
-
}, guard),
|
|
241
|
-
keepGreen: () => healthy(fixCheck, { escalations, guard }),
|
|
242
|
-
awaitReview: (b) => awaitReview(b, t.handOffAwaitReviewMessage(b)),
|
|
243
|
-
reviewMissing,
|
|
244
|
-
close: closeReview,
|
|
245
|
-
handOff: (reviewed) =>
|
|
246
|
-
// The committed blob, not the working copy: the human's notes already landed over it.
|
|
247
|
-
run("review.handing-off", showScript(reviewed, REVIEW, REVIEW_ORIG), {
|
|
248
|
-
label: "Handing your notes to fix",
|
|
249
|
-
}),
|
|
250
|
-
threadsPending:
|
|
251
|
-
"gtd land: open-threads: a thread in .gtd/REVIEW.md or an `H:` code comment waits on the agent — nothing answers it here; delete it, or turn it into a note for fix.",
|
|
252
|
-
unparsed:
|
|
253
|
-
"gtd land: review-doc: your edit to .gtd/REVIEW.md contains no note fix can read — restore the deleted chunk, or write the note as a line under its entry.",
|
|
254
|
-
deleted:
|
|
255
|
-
"gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note to hand to fix.",
|
|
256
|
-
})
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/** The tail the fix workflow runs: ends at the hand-off review since `base`, which gets what the lap left. */
|
|
260
|
-
export const handOffTail = (fixFirst: boolean, base: string, guard?: Guard): Promise<void> =>
|
|
261
|
-
tail((escalations, report) => handOffReview(base, escalations, guard, report), {
|
|
262
|
-
fixFirst,
|
|
263
|
-
guard,
|
|
264
|
-
})
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
installContext,
|
|
3
|
-
type CodeThreadInfo,
|
|
4
|
-
type AccessDef,
|
|
5
|
-
type FlowContext,
|
|
6
|
-
type ScopeAccess,
|
|
7
|
-
type StepRequest,
|
|
8
|
-
} from "../../flows/index.js"
|
|
9
|
-
import { foldAccess } from "../../replay/index.js"
|
|
10
|
-
import { access as bundledAccess } from "./access.js"
|
|
11
|
-
import { skills as bundledSkills } from "./skills.js"
|
|
12
|
-
import { defaults, envDefaults } from "./vars.js"
|
|
13
|
-
|
|
14
|
-
export interface TextContext {
|
|
15
|
-
readonly vars?: Readonly<Record<string, string>>
|
|
16
|
-
readonly env?: Readonly<Record<string, string>>
|
|
17
|
-
readonly head?: string
|
|
18
|
-
readonly start?: string
|
|
19
|
-
readonly codeThreads?: readonly CodeThreadInfo[]
|
|
20
|
-
readonly read?: (path: string) => string | undefined
|
|
21
|
-
/** The scope the step runs in (`"build.review"`); the fixture pushes no scopes, so this stands in for the caller's. */
|
|
22
|
-
readonly scope?: string
|
|
23
|
-
/** Stands in for `config.skills`, keyed by scope full name. */
|
|
24
|
-
readonly skills?: Readonly<Record<string, readonly string[]>>
|
|
25
|
-
/** Stands in for `config.access`, keyed by scope full name. */
|
|
26
|
-
readonly access?: Readonly<Record<string, ScopeAccess>>
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const unavailable = (): never => {
|
|
30
|
-
throw new Error("not available while rendering a text outside a replay")
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Mirrors `Replay.ts`'s scope walk: for each prefix of the step's memory scope, innermost first, a `config` entry, then the bundled export. */
|
|
34
|
-
const skillsForOf =
|
|
35
|
-
(context: TextContext) =>
|
|
36
|
-
(localName: string): readonly string[] => {
|
|
37
|
-
const full = [context.scope, localName].filter((part) => part !== undefined && part !== "")
|
|
38
|
-
const parts = full.join(".").split(".").slice(0, -1)
|
|
39
|
-
const bundled = bundledSkills({ ...defaults, ...context.vars })
|
|
40
|
-
for (let n = parts.length; n >= 0; n--) {
|
|
41
|
-
const prefix = parts.slice(0, n).join(".")
|
|
42
|
-
const hit = context.skills?.[prefix] ?? bundled[prefix]
|
|
43
|
-
if (hit !== undefined) return hit
|
|
44
|
-
}
|
|
45
|
-
return []
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** The same walk for access, folded the way `Replay.ts` folds it. */
|
|
49
|
-
const accessForOf =
|
|
50
|
-
(context: TextContext) =>
|
|
51
|
-
(localName: string, file?: string): AccessDef => {
|
|
52
|
-
const full = [context.scope, localName].filter((part) => part !== undefined && part !== "")
|
|
53
|
-
const parts = full.join(".").split(".").slice(0, -1)
|
|
54
|
-
const bundled = bundledAccess({ ...defaults, ...context.vars })
|
|
55
|
-
let hit: ScopeAccess | undefined
|
|
56
|
-
for (let n = parts.length; n >= 0 && hit === undefined; n--) {
|
|
57
|
-
const prefix = parts.slice(0, n).join(".")
|
|
58
|
-
hit = context.access?.[prefix] ?? bundled[prefix]
|
|
59
|
-
}
|
|
60
|
-
return foldAccess(
|
|
61
|
-
hit,
|
|
62
|
-
file,
|
|
63
|
-
(context.codeThreads ?? []).filter((t) => t.waitingOn === "agent").map((t) => t.path),
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** The fixed replay context texts render against; steps, refusals and scopes are unavailable unless overridden. */
|
|
68
|
-
export const fixtureContext = (
|
|
69
|
-
context: TextContext = {},
|
|
70
|
-
overrides: Partial<FlowContext> = {},
|
|
71
|
-
): FlowContext => ({
|
|
72
|
-
step: unavailable,
|
|
73
|
-
refuse: unavailable,
|
|
74
|
-
pushScope: unavailable,
|
|
75
|
-
popScope: unavailable,
|
|
76
|
-
read: context.read ?? (() => undefined),
|
|
77
|
-
glob: () => [],
|
|
78
|
-
changes: () => [],
|
|
79
|
-
changesSince: unavailable,
|
|
80
|
-
kept: () => undefined,
|
|
81
|
-
matches: () => false,
|
|
82
|
-
sections: () => [],
|
|
83
|
-
sectionBodies: () => [],
|
|
84
|
-
openQuestions: () => [],
|
|
85
|
-
threads: () => [],
|
|
86
|
-
codeThreads: () => context.codeThreads ?? [],
|
|
87
|
-
vars: { ...defaults, ...context.vars },
|
|
88
|
-
env: { ...envDefaults, ...context.env },
|
|
89
|
-
head: () => context.head ?? "",
|
|
90
|
-
start: () => context.start ?? "",
|
|
91
|
-
skillsFor: skillsForOf(context),
|
|
92
|
-
accessFor: accessForOf(context),
|
|
93
|
-
...overrides,
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
/** Evaluate one of the bundled workflow's texts the way replay would, against a fixed context. */
|
|
97
|
-
export const renderText = <T>(text: () => T, context: TextContext = {}): T => {
|
|
98
|
-
installContext(fixtureContext(context))
|
|
99
|
-
try {
|
|
100
|
-
return text()
|
|
101
|
-
} finally {
|
|
102
|
-
installContext(undefined)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Run `fn`, recording the one `StepRequest` it issues, against the same fixed context `renderText` installs. */
|
|
107
|
-
export const captureStep = async (
|
|
108
|
-
fn: () => Promise<void>,
|
|
109
|
-
context: TextContext = {},
|
|
110
|
-
): Promise<StepRequest> => {
|
|
111
|
-
let captured: StepRequest | undefined
|
|
112
|
-
installContext(
|
|
113
|
-
fixtureContext(context, {
|
|
114
|
-
step: (request) => {
|
|
115
|
-
captured = request
|
|
116
|
-
return Promise.resolve()
|
|
117
|
-
},
|
|
118
|
-
changesSince: () => [],
|
|
119
|
-
}),
|
|
120
|
-
)
|
|
121
|
-
try {
|
|
122
|
-
await fn()
|
|
123
|
-
} finally {
|
|
124
|
-
installContext(undefined)
|
|
125
|
-
}
|
|
126
|
-
if (captured === undefined) throw new Error("no step was issued")
|
|
127
|
-
return captured
|
|
128
|
-
}
|