@pmelab/gtd 29.1.1 → 29.2.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 +3 -0
- package/claude/hooks/judge.ts +3 -1
- package/dist/chunks/{reserved-fqf-c9dc.mjs → reserved-BzeofpoC.mjs} +1 -10
- package/dist/chunks/{tail-Do11DVcw.mjs → tail-eS_ZSImR.mjs} +1 -1
- package/dist/flows.mjs +1 -1
- package/dist/gtd.bundle.mjs +4 -2
- package/dist/testkit.mjs +3 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +7 -6
- 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 +7 -15
- package/package.json +1 -1
- package/skills/authoring/SKILL.md +13 -5
- package/src/cli/Program.ts +3 -1
- package/src/flows/helpers.ts +1 -1
- package/src/replay/judgeDiffShape.test.ts +14 -0
- package/src/workflows/feature/feature.test.ts +1 -1
- package/src/workflows/feature/index.ts +1 -1
- package/src/workflows/feature/review.test.ts +128 -6
- package/src/workflows/feature/review.ts +18 -10
- package/src/workflows/feature/seam.test.ts +15 -1
- package/src/workflows/index.ts +1 -1
- package/src/workflows/shared/options.test.ts +9 -3
- package/src/workflows/shared/text.test.ts +3 -3
- package/src/workflows/shared/text.ts +8 -33
package/README.md
CHANGED
|
@@ -601,6 +601,9 @@ means `high`. Each lap lands a `Gtd-Mark: risk-tier=<low|high>` trailer and a
|
|
|
601
601
|
lap's only. Its "needs a human reviewer" line for `high` is advisory, not a
|
|
602
602
|
gate.
|
|
603
603
|
|
|
604
|
+
A `gtd.config.ts` call to `buildTail` that fits neither call form is rejected;
|
|
605
|
+
see [docs/configuration.md](docs/configuration.md).
|
|
606
|
+
|
|
604
607
|
### Rolling out and cleaning up release flags
|
|
605
608
|
|
|
606
609
|
Two more bundled workflows retire a release flag. They are opt-in: the
|
package/claude/hooks/judge.ts
CHANGED
|
@@ -83,7 +83,9 @@ export function judgePrompt(j: Judgment): string {
|
|
|
83
83
|
"Answer every question below from the evidence alone. For each, give your answer and p, your confidence between 0 and 1 that the given answer is correct.",
|
|
84
84
|
]
|
|
85
85
|
for (const [key, body] of Object.entries(j.state ?? {}))
|
|
86
|
-
parts.push(
|
|
86
|
+
parts.push(
|
|
87
|
+
`<evidence key="${key}">\n${body.replace(/<\/evidence/gi, "<\\/evidence")}\n</evidence>`,
|
|
88
|
+
)
|
|
87
89
|
for (const q of j.questions) {
|
|
88
90
|
const levels = levelsOf(q)
|
|
89
91
|
if (typeof levels === "string") throw new Error(levels)
|
|
@@ -776,16 +776,7 @@ ${strays.map((path) => ` - \`${displayPath(path)}\``).join("\n")}
|
|
|
776
776
|
- Introduce no new flag read and add no second read of an existing one:
|
|
777
777
|
every match of \`${pattern}\` may move, never increase in number`;
|
|
778
778
|
const withRules = (rules) => rules === void 0 ? "" : `${rules}\n`;
|
|
779
|
-
|
|
780
|
-
return refactorBuildingPromptOf(typeof first === "string" ? {
|
|
781
|
-
pkg: first,
|
|
782
|
-
strays: strays ?? [],
|
|
783
|
-
pattern: pattern ?? "",
|
|
784
|
-
reference,
|
|
785
|
-
feedback
|
|
786
|
-
} : first);
|
|
787
|
-
}
|
|
788
|
-
const refactorBuildingPromptOf = ({ pkg, strays, pattern, reference, feedback = false }) => `${stateFileRules}
|
|
779
|
+
const refactorBuildingPrompt = ({ pkg, strays, pattern, reference, feedback = false }) => `${stateFileRules}
|
|
789
780
|
- The package to implement is \`${pkg}\`; never delete it
|
|
790
781
|
${feedback ? `- The package carries a \`## Review feedback\` section: a human's review of the
|
|
791
782
|
last refactor. Act on it, within the rules below
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { read, refuse, removeScript, run, scope, showScript, vars } from "../flows.mjs";
|
|
2
2
|
import { fixFindings, lensesOf, qualityLap as qualityLap$1, reviewToHandOff } from "../workflow-kit.mjs";
|
|
3
|
-
import { E as reviewing, S as fixRisks, T as reviewQuality, W as handOffAwaitReviewMessage, _ as REVIEW, b as fixCheck, c as escalation, d as healthy, m as QUALITY, u as guarded, v as awaitReview, w as reviewMissing, x as fixQuality } from "./reserved-
|
|
3
|
+
import { E as reviewing, S as fixRisks, T as reviewQuality, W as handOffAwaitReviewMessage, _ as REVIEW, b as fixCheck, c as escalation, d as healthy, m as QUALITY, u as guarded, v as awaitReview, w as reviewMissing, x as fixQuality } from "./reserved-BzeofpoC.mjs";
|
|
4
4
|
//#region src/workflows/shared/tail.ts
|
|
5
5
|
/** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
|
|
6
6
|
const REVIEW_ORIG = ".gtd/REVIEW.orig.md";
|
package/dist/flows.mjs
CHANGED
|
@@ -370,7 +370,7 @@ const SHAPE_CRITERIA = [
|
|
|
370
370
|
"land: every stray is incidental (a needed import, a test fixture, a renamed call site).",
|
|
371
371
|
"refactor-first: the strays are a seam change the task needs; land them first as a behaviour-neutral refactor.",
|
|
372
372
|
"reject: the naive pass solved the task the wrong way; discard it and re-implement, no human; strays a refactor could absorb never count as reject."
|
|
373
|
-
].join("
|
|
373
|
+
].join(" ");
|
|
374
374
|
const readable = (c) => (c.status === "deleted" || c.after !== void 0) && (c.status === "added" || c.before !== void 0);
|
|
375
375
|
const strayContent = (strays) => strays.map((c) => [
|
|
376
376
|
`=== ${c.path} (${c.status})`,
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -22014,7 +22014,7 @@ const SHAPE_CRITERIA = [
|
|
|
22014
22014
|
"land: every stray is incidental (a needed import, a test fixture, a renamed call site).",
|
|
22015
22015
|
"refactor-first: the strays are a seam change the task needs; land them first as a behaviour-neutral refactor.",
|
|
22016
22016
|
"reject: the naive pass solved the task the wrong way; discard it and re-implement, no human; strays a refactor could absorb never count as reject."
|
|
22017
|
-
].join("
|
|
22017
|
+
].join(" ");
|
|
22018
22018
|
const readable = (c) => (c.status === "deleted" || c.after !== void 0) && (c.status === "added" || c.before !== void 0);
|
|
22019
22019
|
const strayContent = (strays) => strays.map((c) => [
|
|
22020
22020
|
`=== ${c.path} (${c.status})`,
|
|
@@ -41238,7 +41238,9 @@ const planLanding = (opts = {}, current) => gen(function* () {
|
|
|
41238
41238
|
* closes or sits inside its own closing message. Writes nothing — no git, no
|
|
41239
41239
|
* state transition, no file. Refuses (throws, mapped to the runtime-error
|
|
41240
41240
|
* exit code) when the workflow declares no `summary` prompt, or the resolved
|
|
41241
|
-
* run has no commits to name.
|
|
41241
|
+
* run has no commits to name. `--slice` also refuses an unknown slice name
|
|
41242
|
+
* (listing the valid slices), a config edited since the process closed, and a
|
|
41243
|
+
* replay that diverged. The edge never narrates: the CLI prints the
|
|
41242
41244
|
* version warning, before the unknown-slice refusal; a replay refusal carries
|
|
41243
41245
|
* it in its message.
|
|
41244
41246
|
*/
|
package/dist/testkit.mjs
CHANGED
|
@@ -10341,7 +10341,9 @@ const planLanding = (opts = {}, current) => gen(function* () {
|
|
|
10341
10341
|
* closes or sits inside its own closing message. Writes nothing — no git, no
|
|
10342
10342
|
* state transition, no file. Refuses (throws, mapped to the runtime-error
|
|
10343
10343
|
* exit code) when the workflow declares no `summary` prompt, or the resolved
|
|
10344
|
-
* run has no commits to name.
|
|
10344
|
+
* run has no commits to name. `--slice` also refuses an unknown slice name
|
|
10345
|
+
* (listing the valid slices), a config edited since the process closed, and a
|
|
10346
|
+
* replay that diverged. The edge never narrates: the CLI prints the
|
|
10345
10347
|
* version warning, before the unknown-slice refusal; a replay refusal carries
|
|
10346
10348
|
* it in its message.
|
|
10347
10349
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
|
|
2
|
-
import { _t as registryEntry, a as flagShipped, dt as defaults, ft as envDefaults, ht as flagReferences, i as skills, n as summary, r as access, t as steering, vt as requireFlag, y as coder } from "../chunks/reserved-
|
|
3
|
-
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { _t as registryEntry, a as flagShipped, dt as defaults, ft as envDefaults, ht as flagReferences, i as skills, n as summary, r as access, t as steering, vt as requireFlag, y as coder } from "../chunks/reserved-BzeofpoC.mjs";
|
|
3
|
+
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-eS_ZSImR.mjs";
|
|
4
4
|
//#region src/workflows/cleanup/cleanup.ts
|
|
5
5
|
const removingPrompt = (name, registry) => `Clean up the release flag "${name}", which is default-on: delete its entry in ${registry}, and remove every \`GTD_FLAG_${name.toUpperCase()}\` check from the shipped sources. Keep the flag-on path; delete code only the off path reached, including tests that pin the off path.`;
|
|
6
6
|
const cleanupFlow = async () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { answered, changes, changesSince, glob, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, slice, start, vars } from "../flows.mjs";
|
|
2
2
|
import { lensesOf, review as review$1 } from "../workflow-kit.mjs";
|
|
3
|
-
import { $ as refactorFixPrompt, A as architectureDecomposePrompt, B as designSystem, C as planner, D as agentWithSkills, E as reviewing, F as buildReviewFixNitsPrompt, G as idleMessage, I as buildReviewTriageMessage, J as naivePassPrompt, K as implementBuildingPrompt, L as builderSystem, M as buildReviewAnswerQuestionsPrompt, N as buildReviewAwaitAfterDeferMessage, O as architectSystem, P as buildReviewCollectingPrompt, Q as refactorDroppedRefusal, R as deferNotesScript, S as fixRisks, V as designTriagePrompt, X as packagesItemFixSuitePrompt, Y as packagesItemBuildingPrompt, Z as refactorBuildingPrompt, _ as REVIEW, at as reviewerSystem, b as fixCheck, ct as startGateBlockedMessage, d as healthy, dt as defaults, et as refactorRules, f as ARCHITECTURE, ft as envDefaults, g as REQUIREMENTS, h as REFACTOR, i as skills, it as reviewNotesCapture, j as architectureGateAnswerMessage, k as architectureAuthorPrompt, l as gate, lt as unwindFailedMessage, n as summary, nt as reviewEditsCapture, o as shipped, ot as seamByHandMessage, p as FEEDBACK, pt as testFileGlobs, q as implementFixPrompt, r as access, st as seamSteerMessage, t as steering, tt as reviewEditNotesCapture, u as guarded, ut as unwindFailure, v as awaitReview, w as reviewMissing, y as coder, z as designGateAnswerMessage } from "../chunks/reserved-
|
|
4
|
-
import { a as tail, i as refusePendingHandOff, n as closeReview } from "../chunks/tail-
|
|
3
|
+
import { $ as refactorFixPrompt, A as architectureDecomposePrompt, B as designSystem, C as planner, D as agentWithSkills, E as reviewing, F as buildReviewFixNitsPrompt, G as idleMessage, I as buildReviewTriageMessage, J as naivePassPrompt, K as implementBuildingPrompt, L as builderSystem, M as buildReviewAnswerQuestionsPrompt, N as buildReviewAwaitAfterDeferMessage, O as architectSystem, P as buildReviewCollectingPrompt, Q as refactorDroppedRefusal, R as deferNotesScript, S as fixRisks, V as designTriagePrompt, X as packagesItemFixSuitePrompt, Y as packagesItemBuildingPrompt, Z as refactorBuildingPrompt, _ as REVIEW, at as reviewerSystem, b as fixCheck, ct as startGateBlockedMessage, d as healthy, dt as defaults, et as refactorRules, f as ARCHITECTURE, ft as envDefaults, g as REQUIREMENTS, h as REFACTOR, i as skills, it as reviewNotesCapture, j as architectureGateAnswerMessage, k as architectureAuthorPrompt, l as gate, lt as unwindFailedMessage, n as summary, nt as reviewEditsCapture, o as shipped, ot as seamByHandMessage, p as FEEDBACK, pt as testFileGlobs, q as implementFixPrompt, r as access, st as seamSteerMessage, t as steering, tt as reviewEditNotesCapture, u as guarded, ut as unwindFailure, v as awaitReview, w as reviewMissing, y as coder, z as designGateAnswerMessage } from "../chunks/reserved-BzeofpoC.mjs";
|
|
4
|
+
import { a as tail, i as refusePendingHandOff, n as closeReview } from "../chunks/tail-eS_ZSImR.mjs";
|
|
5
5
|
//#region src/workflows/shared/fences.ts
|
|
6
6
|
const markFenced = (text) => {
|
|
7
7
|
let fence;
|
|
@@ -476,13 +476,14 @@ const review = (base, escalations = { rounds: 0 }, options = {}) => {
|
|
|
476
476
|
});
|
|
477
477
|
};
|
|
478
478
|
function buildTail(first, second, third = false, fourth = {}) {
|
|
479
|
-
|
|
480
|
-
const options = typeof first === "string" ? second ?? {} : {
|
|
479
|
+
if (typeof first !== "string" && typeof first !== "boolean" && typeof second !== "string") return Promise.reject(/* @__PURE__ */ new Error("buildTail takes buildTail(base, options?) or the deprecated buildTail(fixFirst, base, green?, options?); this call fits neither"));
|
|
480
|
+
const [base, options] = typeof first === "string" ? [first, second ?? {}] : [second, {
|
|
481
481
|
...fourth,
|
|
482
482
|
fixFirst: first,
|
|
483
483
|
green: third
|
|
484
|
-
};
|
|
485
|
-
|
|
484
|
+
}];
|
|
485
|
+
const { fixFirst, green, ...reviewOptions } = options;
|
|
486
|
+
return tail(fixFirst ?? false, (escalations) => review(base, escalations, reviewOptions), green ?? false, options.guard, options.lenses, options.guardRests, options.lensBase, options.rules);
|
|
486
487
|
}
|
|
487
488
|
/**
|
|
488
489
|
* Undo the human's review-round code edits. A path changed since the human
|
package/dist/workflows/fix.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
|
|
2
2
|
import { d as reviewNotes, f as reviewRisks } from "../chunks/steering-DVILanVf.mjs";
|
|
3
|
-
import { D as agentWithSkills, H as finisherSystem, U as fixReviewNotesPrompt, _ as REVIEW, dt as defaults, ft as envDefaults, i as skills, n as summary, o as shipped, r as access, s as baseline, t as steering, y as coder } from "../chunks/reserved-
|
|
4
|
-
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-
|
|
3
|
+
import { D as agentWithSkills, H as finisherSystem, U as fixReviewNotesPrompt, _ as REVIEW, dt as defaults, ft as envDefaults, i as skills, n as summary, o as shipped, r as access, s as baseline, t as steering, y as coder } from "../chunks/reserved-BzeofpoC.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-eS_ZSImR.mjs";
|
|
5
5
|
//#region src/workflows/fix/fix.ts
|
|
6
6
|
/**
|
|
7
7
|
* Fix every note a human left in a handed-off `.gtd/REVIEW.md` and every
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { start } from "../flows.mjs";
|
|
2
|
-
import { dt as defaults, ft as envDefaults, i as skills, l as gate, n as summary, o as shipped, r as access, rt as reviewGateBlockedMessage, t as steering } from "../chunks/reserved-
|
|
3
|
-
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { dt as defaults, ft as envDefaults, i as skills, l as gate, n as summary, o as shipped, r as access, rt as reviewGateBlockedMessage, t as steering } from "../chunks/reserved-BzeofpoC.mjs";
|
|
3
|
+
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-eS_ZSImR.mjs";
|
|
4
4
|
//#region src/workflows/review/review.ts
|
|
5
5
|
/**
|
|
6
6
|
* Pure review of everything since `reviewBase`: a green gate, the quality
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
|
|
2
|
-
import { a as flagShipped, dt as defaults, ft as envDefaults, gt as flipProblems, i as skills, l as gate, mt as flagMentions, n as summary, p as FEEDBACK, r as access, t as steering, vt as requireFlag, y as coder } from "../chunks/reserved-
|
|
2
|
+
import { a as flagShipped, dt as defaults, ft as envDefaults, gt as flipProblems, i as skills, l as gate, mt as flagMentions, n as summary, p as FEEDBACK, r as access, t as steering, vt as requireFlag, y as coder } from "../chunks/reserved-BzeofpoC.mjs";
|
|
3
3
|
//#region src/workflows/rollout/rollout.ts
|
|
4
4
|
const flippingPrompt = (name, registry) => `Roll out the release flag "${name}": in ${registry}, set \`defaultOn: true\` on its entry. Change nothing else — no other entry, no other field, no other file, no comments.`;
|
|
5
5
|
const suiteBlockedMessage = () => "The suite is red after the rollout. Fix it by hand — a rollout may only touch the flag's registry entry — then land.";
|
package/dist/workflows.mjs
CHANGED
|
@@ -349,7 +349,7 @@ const SHAPE_CRITERIA = [
|
|
|
349
349
|
"land: every stray is incidental (a needed import, a test fixture, a renamed call site).",
|
|
350
350
|
"refactor-first: the strays are a seam change the task needs; land them first as a behaviour-neutral refactor.",
|
|
351
351
|
"reject: the naive pass solved the task the wrong way; discard it and re-implement, no human; strays a refactor could absorb never count as reject."
|
|
352
|
-
].join("
|
|
352
|
+
].join(" ");
|
|
353
353
|
const readable = (c) => (c.status === "deleted" || c.after !== void 0) && (c.status === "added" || c.before !== void 0);
|
|
354
354
|
const strayContent = (strays) => strays.map((c) => [
|
|
355
355
|
`=== ${c.path} (${c.status})`,
|
|
@@ -17560,16 +17560,7 @@ ${strays.map((path) => ` - \`${displayPath(path)}\``).join("\n")}
|
|
|
17560
17560
|
- Introduce no new flag read and add no second read of an existing one:
|
|
17561
17561
|
every match of \`${pattern}\` may move, never increase in number`;
|
|
17562
17562
|
const withRules = (rules) => rules === void 0 ? "" : `${rules}\n`;
|
|
17563
|
-
|
|
17564
|
-
return refactorBuildingPromptOf(typeof first === "string" ? {
|
|
17565
|
-
pkg: first,
|
|
17566
|
-
strays: strays ?? [],
|
|
17567
|
-
pattern: pattern ?? "",
|
|
17568
|
-
reference,
|
|
17569
|
-
feedback
|
|
17570
|
-
} : first);
|
|
17571
|
-
}
|
|
17572
|
-
const refactorBuildingPromptOf = ({ pkg, strays, pattern, reference, feedback = false }) => `${stateFileRules}
|
|
17563
|
+
const refactorBuildingPrompt = ({ pkg, strays, pattern, reference, feedback = false }) => `${stateFileRules}
|
|
17573
17564
|
- The package to implement is \`${pkg}\`; never delete it
|
|
17574
17565
|
${feedback ? `- The package carries a \`## Review feedback\` section: a human's review of the
|
|
17575
17566
|
last refactor. Act on it, within the rules below
|
|
@@ -38308,13 +38299,14 @@ const review$1 = (base, escalations = { rounds: 0 }, options = {}) => {
|
|
|
38308
38299
|
});
|
|
38309
38300
|
};
|
|
38310
38301
|
function buildTail(first, second, third = false, fourth = {}) {
|
|
38311
|
-
|
|
38312
|
-
const options = typeof first === "string" ? second ?? {} : {
|
|
38302
|
+
if (typeof first !== "string" && typeof first !== "boolean" && typeof second !== "string") return Promise.reject(/* @__PURE__ */ new Error("buildTail takes buildTail(base, options?) or the deprecated buildTail(fixFirst, base, green?, options?); this call fits neither"));
|
|
38303
|
+
const [base, options] = typeof first === "string" ? [first, second ?? {}] : [second, {
|
|
38313
38304
|
...fourth,
|
|
38314
38305
|
fixFirst: first,
|
|
38315
38306
|
green: third
|
|
38316
|
-
};
|
|
38317
|
-
|
|
38307
|
+
}];
|
|
38308
|
+
const { fixFirst, green, ...reviewOptions } = options;
|
|
38309
|
+
return tail(fixFirst ?? false, (escalations) => review$1(base, escalations, reviewOptions), green ?? false, options.guard, options.lenses, options.guardRests, options.lensBase, options.rules);
|
|
38318
38310
|
}
|
|
38319
38311
|
/**
|
|
38320
38312
|
* Undo the human's review-round code edits. A path changed since the human
|
package/package.json
CHANGED
|
@@ -56,7 +56,7 @@ export {
|
|
|
56
56
|
export default feature
|
|
57
57
|
|
|
58
58
|
export async function hotfix() {
|
|
59
|
-
return afterTail(await buildTail(
|
|
59
|
+
return afterTail(await buildTail(start(), { fixFirst: true }))
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export const doors = { hotfix: { workflow: "hotfix" } }
|
|
@@ -227,9 +227,11 @@ pass (which writes `.gtd/packages/`), then builds the packages. Add a human
|
|
|
227
227
|
sign-off between the two:
|
|
228
228
|
|
|
229
229
|
```ts
|
|
230
|
-
const planAndBuild = async (): Promise<void> => {
|
|
230
|
+
const planAndBuild = async (firstBase: string): Promise<void> => {
|
|
231
|
+
let base = firstBase
|
|
232
|
+
let lap: "first" | "feedback" = "first"
|
|
231
233
|
for (;;) {
|
|
232
|
-
await design()
|
|
234
|
+
await design(base)
|
|
233
235
|
await architecture()
|
|
234
236
|
await human("approve-plan", {
|
|
235
237
|
message:
|
|
@@ -238,8 +240,14 @@ const planAndBuild = async (): Promise<void> => {
|
|
|
238
240
|
acceptClean: true,
|
|
239
241
|
})
|
|
240
242
|
await packages()
|
|
241
|
-
|
|
242
|
-
|
|
243
|
+
const outcome = await buildTail(base, {
|
|
244
|
+
green: true,
|
|
245
|
+
...(lap === "first" ? {} : { lap }),
|
|
246
|
+
})
|
|
247
|
+
if (outcome.verdict === "signoff") return
|
|
248
|
+
await reUnwind(outcome)
|
|
249
|
+
base = outcome.base
|
|
250
|
+
lap = "feedback"
|
|
243
251
|
}
|
|
244
252
|
}
|
|
245
253
|
```
|
package/src/cli/Program.ts
CHANGED
|
@@ -326,7 +326,9 @@ const planLanding = (
|
|
|
326
326
|
* closes or sits inside its own closing message. Writes nothing — no git, no
|
|
327
327
|
* state transition, no file. Refuses (throws, mapped to the runtime-error
|
|
328
328
|
* exit code) when the workflow declares no `summary` prompt, or the resolved
|
|
329
|
-
* run has no commits to name.
|
|
329
|
+
* run has no commits to name. `--slice` also refuses an unknown slice name
|
|
330
|
+
* (listing the valid slices), a config edited since the process closed, and a
|
|
331
|
+
* replay that diverged. The edge never narrates: the CLI prints the
|
|
330
332
|
* version warning, before the unknown-slice refusal; a replay refusal carries
|
|
331
333
|
* it in its message.
|
|
332
334
|
*/
|
package/src/flows/helpers.ts
CHANGED
|
@@ -174,7 +174,7 @@ const SHAPE_CRITERIA = [
|
|
|
174
174
|
"land: every stray is incidental (a needed import, a test fixture, a renamed call site).",
|
|
175
175
|
"refactor-first: the strays are a seam change the task needs; land them first as a behaviour-neutral refactor.",
|
|
176
176
|
"reject: the naive pass solved the task the wrong way; discard it and re-implement, no human; strays a refactor could absorb never count as reject.",
|
|
177
|
-
].join("
|
|
177
|
+
].join(" ")
|
|
178
178
|
|
|
179
179
|
const readable = (c: Change): boolean =>
|
|
180
180
|
(c.status === "deleted" || c.after !== undefined) &&
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type JudgeAnswer,
|
|
8
8
|
type StepRequest,
|
|
9
9
|
} from "../flows/index.js"
|
|
10
|
+
import { toRequest } from "../judges/index.js"
|
|
10
11
|
import { globMatches } from "../wire/index.js"
|
|
11
12
|
|
|
12
13
|
// Lives in replay, which wires `matches: globMatches` (flows tests may not import wire).
|
|
@@ -123,6 +124,19 @@ describe("judgeDiffShape", () => {
|
|
|
123
124
|
expect(q?.criteria).toContain("strays a refactor could absorb never count as reject")
|
|
124
125
|
})
|
|
125
126
|
|
|
127
|
+
it("is sendable to jev with exactly its three labels", async () => {
|
|
128
|
+
const requests = install({ shape: { answer: "land", p: 0.9 } })
|
|
129
|
+
await judgeDiffShape("shape", { changes: naive, allow: ["src/**"] })
|
|
130
|
+
const request = requests[0]
|
|
131
|
+
if (request?.kind !== "judge") throw new Error("expected a judge step")
|
|
132
|
+
const { questions } = toRequest(request.questions, {})
|
|
133
|
+
expect(Object.keys((questions["shape"] as { criteria: object }).criteria)).toEqual([
|
|
134
|
+
"land",
|
|
135
|
+
"refactor-first",
|
|
136
|
+
"reject",
|
|
137
|
+
])
|
|
138
|
+
})
|
|
139
|
+
|
|
126
140
|
it("carries exactly four evidence keys, with stray content before/after and in-allowlist paths only", async () => {
|
|
127
141
|
const requests = install({ shape: { answer: "land", p: 0.9 } })
|
|
128
142
|
await judgeDiffShape("shape", { changes: naive, allow: ["src/**"] })
|
|
@@ -131,7 +131,7 @@ describe("afterTail feedback lap", () => {
|
|
|
131
131
|
`export { feature as default, defaults, envDefaults, summary, base, steering, skills, access } from "@pmelab/gtd/workflows"`,
|
|
132
132
|
`export async function tail() {`,
|
|
133
133
|
` await gate("review-gate", "The baseline is red.")`,
|
|
134
|
-
` return afterTail(await buildTail(
|
|
134
|
+
` return afterTail(await buildTail(start()))`,
|
|
135
135
|
`}`,
|
|
136
136
|
].join("\n")
|
|
137
137
|
|
|
@@ -5,7 +5,7 @@ export * from "./planning.js"
|
|
|
5
5
|
export * from "./packages.js"
|
|
6
6
|
export * from "./refactor.js"
|
|
7
7
|
export * from "./seam.js"
|
|
8
|
-
export { buildTail, type ReviewOutcome } from "./review.js"
|
|
8
|
+
export { buildTail, type BuildTailOptions, type ReviewOutcome } from "./review.js"
|
|
9
9
|
export * from "./feature.js"
|
|
10
10
|
export * from "../shared/reserved.js"
|
|
11
11
|
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
installContext,
|
|
4
4
|
type Change,
|
|
5
5
|
type JudgeAnswer,
|
|
6
|
+
type JudgeQuestion,
|
|
6
7
|
type ScopeOptions,
|
|
7
8
|
type StepRequest,
|
|
8
9
|
} from "../../flows/index.js"
|
|
@@ -16,7 +17,9 @@ import {
|
|
|
16
17
|
} from "./review.js"
|
|
17
18
|
import type { ReviewNote } from "../../steering/index.js"
|
|
18
19
|
import * as bundled from "../index.js"
|
|
20
|
+
import type { BuildTailOptions as PublicBuildTailOptions } from "./index.js"
|
|
19
21
|
import { fixtureContext } from "../shared/text.fixture.js"
|
|
22
|
+
import { toRequest } from "../../judges/index.js"
|
|
20
23
|
|
|
21
24
|
afterEach(() => installContext(undefined))
|
|
22
25
|
|
|
@@ -64,6 +67,7 @@ interface Run {
|
|
|
64
67
|
readonly prompts: Map<string, string>
|
|
65
68
|
readonly result: ReviewOutcome | "stopped"
|
|
66
69
|
readonly judged: readonly string[]
|
|
70
|
+
readonly asked: readonly JudgeQuestion[]
|
|
67
71
|
/** `acceptClean` of each `review.await-review` gate, in order. */
|
|
68
72
|
readonly gates: readonly boolean[]
|
|
69
73
|
readonly gateMessages: readonly string[]
|
|
@@ -77,7 +81,7 @@ interface Run {
|
|
|
77
81
|
const drive = async (d: Drive): Promise<Run> => {
|
|
78
82
|
const log: string[] = []
|
|
79
83
|
const prompts = new Map<string, string>()
|
|
80
|
-
const
|
|
84
|
+
const asked: JudgeQuestion[] = []
|
|
81
85
|
const gates: boolean[] = []
|
|
82
86
|
const gateMessages: string[] = []
|
|
83
87
|
const since: string[] = []
|
|
@@ -112,7 +116,7 @@ const drive = async (d: Drive): Promise<Run> => {
|
|
|
112
116
|
gateMessages.push(request.options.message ?? "")
|
|
113
117
|
}
|
|
114
118
|
if (request.kind === "agent") prompts.set(request.name, request.prompt)
|
|
115
|
-
if (request.kind === "judge")
|
|
119
|
+
if (request.kind === "judge") asked.push(...request.questions)
|
|
116
120
|
}
|
|
117
121
|
const context = fixtureContext(
|
|
118
122
|
{ vars: d.vars ?? {} },
|
|
@@ -158,7 +162,17 @@ const drive = async (d: Drive): Promise<Run> => {
|
|
|
158
162
|
if (!(error instanceof Stop)) throw error
|
|
159
163
|
result = "stopped"
|
|
160
164
|
}
|
|
161
|
-
return {
|
|
165
|
+
return {
|
|
166
|
+
log,
|
|
167
|
+
prompts,
|
|
168
|
+
result,
|
|
169
|
+
judged: asked.map((q) => q.id),
|
|
170
|
+
asked,
|
|
171
|
+
gates,
|
|
172
|
+
gateMessages,
|
|
173
|
+
since,
|
|
174
|
+
scopes,
|
|
175
|
+
}
|
|
162
176
|
}
|
|
163
177
|
|
|
164
178
|
const verdict = (answer: string, p = 0.9): JudgeAnswer => ({ answer, p })
|
|
@@ -417,6 +431,15 @@ describe("review verdict routing", () => {
|
|
|
417
431
|
expect(run.log.filter((n) => n === "review.triage")).toHaveLength(1)
|
|
418
432
|
})
|
|
419
433
|
|
|
434
|
+
it("asks only questions sendable to jev, each choice with its labels", async () => {
|
|
435
|
+
const run = await drive({ notes: ["add — a"] })
|
|
436
|
+
const { questions } = toRequest(run.asked, {})
|
|
437
|
+
const labels = (id: string) => Object.keys((questions[id] as { criteria: object }).criteria)
|
|
438
|
+
expect(labels("note-1")).toEqual(["edit", "question", "nit", "praise"])
|
|
439
|
+
expect(labels("note-1.reversibility")).toEqual(["one-way", "two-way"])
|
|
440
|
+
expect(labels("note-1.blastRadius")).toEqual(["local", "module", "contract"])
|
|
441
|
+
})
|
|
442
|
+
|
|
420
443
|
it("an unanswered note counts as edit", async () => {
|
|
421
444
|
const run = await drive({ notes: ["add — typo", "sub", "mul"], answers: {} })
|
|
422
445
|
expect(run.log).toEqual([
|
|
@@ -771,7 +794,10 @@ describe("severity routing", () => {
|
|
|
771
794
|
})
|
|
772
795
|
|
|
773
796
|
describe("BuildTailOptions.lensBase", () => {
|
|
774
|
-
const lensPrompts = async (
|
|
797
|
+
const lensPrompts = async (
|
|
798
|
+
options: BuildTailOptions,
|
|
799
|
+
call: () => Promise<unknown> = () => buildTail("base", { ...options, green: true }),
|
|
800
|
+
): Promise<string[]> => {
|
|
775
801
|
const prompts: string[] = []
|
|
776
802
|
installContext(
|
|
777
803
|
fixtureContext(
|
|
@@ -793,7 +819,7 @@ describe("BuildTailOptions.lensBase", () => {
|
|
|
793
819
|
},
|
|
794
820
|
),
|
|
795
821
|
)
|
|
796
|
-
await
|
|
822
|
+
await call().catch((error: unknown) => {
|
|
797
823
|
if (!(error instanceof Stop)) throw error
|
|
798
824
|
})
|
|
799
825
|
return prompts
|
|
@@ -810,8 +836,104 @@ describe("BuildTailOptions.lensBase", () => {
|
|
|
810
836
|
expect(prompts).toHaveLength(2)
|
|
811
837
|
for (const prompt of prompts) expect(prompt).toContain("from `START-SHA`")
|
|
812
838
|
})
|
|
839
|
+
|
|
840
|
+
it("the deprecated positional form runs the same tail as the options form", async () => {
|
|
841
|
+
const options = { lensBase: "SLICE-BASE" }
|
|
842
|
+
const modern = await lensPrompts(options)
|
|
843
|
+
const legacy = await lensPrompts(options, () => buildTail(false, "base", true, options))
|
|
844
|
+
expect(legacy).toEqual(modern)
|
|
845
|
+
expect(legacy).toHaveLength(2)
|
|
846
|
+
})
|
|
847
|
+
|
|
848
|
+
describe("buildTail flags", () => {
|
|
849
|
+
const requestNames = async (
|
|
850
|
+
call: () => Promise<unknown>,
|
|
851
|
+
names: string[] = [],
|
|
852
|
+
): Promise<string[]> => {
|
|
853
|
+
installContext(
|
|
854
|
+
fixtureContext(
|
|
855
|
+
{},
|
|
856
|
+
{
|
|
857
|
+
step: (request: StepRequest) => {
|
|
858
|
+
if (request.kind === "restart") return Promise.resolve()
|
|
859
|
+
names.push(`${request.kind}:${request.name}`)
|
|
860
|
+
if (names.length >= 4) throw new Stop()
|
|
861
|
+
return Promise.resolve()
|
|
862
|
+
},
|
|
863
|
+
pushScope: () => undefined,
|
|
864
|
+
popScope: () => undefined,
|
|
865
|
+
changesSince: () => {
|
|
866
|
+
throw new Stop()
|
|
867
|
+
},
|
|
868
|
+
start: () => "START-SHA",
|
|
869
|
+
},
|
|
870
|
+
),
|
|
871
|
+
)
|
|
872
|
+
await call().catch((error: unknown) => {
|
|
873
|
+
if (!(error instanceof Stop)) throw error
|
|
874
|
+
})
|
|
875
|
+
return names
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
for (const [fixFirst, green] of [
|
|
879
|
+
[true, false],
|
|
880
|
+
[true, true],
|
|
881
|
+
[false, true],
|
|
882
|
+
[false, false],
|
|
883
|
+
] as const) {
|
|
884
|
+
it(`fixFirst ${fixFirst}, green ${green} match the options form`, async () => {
|
|
885
|
+
const modern = await requestNames(() => buildTail("base", { fixFirst, green }))
|
|
886
|
+
const legacy = await requestNames(() => buildTail(fixFirst, "base", green))
|
|
887
|
+
expect(legacy).toEqual(modern)
|
|
888
|
+
})
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
it("fixFirst and green each change the request sequence", async () => {
|
|
892
|
+
const seq = (fixFirst: boolean, green: boolean) =>
|
|
893
|
+
requestNames(() => buildTail(fixFirst, "base", green))
|
|
894
|
+
const base = await seq(false, false)
|
|
895
|
+
expect(await seq(true, false)).not.toEqual(base)
|
|
896
|
+
expect(await seq(false, true)).not.toEqual(base)
|
|
897
|
+
})
|
|
898
|
+
|
|
899
|
+
for (const bad of [undefined, null, 1, {}]) {
|
|
900
|
+
it(`rejects naming both forms for sole first argument ${String(bad)}`, async () => {
|
|
901
|
+
const names: string[] = []
|
|
902
|
+
const rejected = expect(requestNames(() => buildTail(bad as never), names)).rejects
|
|
903
|
+
await rejected.toThrow("buildTail(base, options?)")
|
|
904
|
+
await rejected.toThrow("buildTail(fixFirst, base, green?, options?)")
|
|
905
|
+
expect(names).toEqual([])
|
|
906
|
+
})
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
it("rejects an undefined first argument followed by an options object", async () => {
|
|
910
|
+
const names: string[] = []
|
|
911
|
+
const rejected = expect(
|
|
912
|
+
requestNames(() => buildTail(undefined as never, { green: true } as never), names),
|
|
913
|
+
).rejects
|
|
914
|
+
await rejected.toThrow("buildTail(base, options?)")
|
|
915
|
+
await rejected.toThrow("buildTail(fixFirst, base, green?, options?)")
|
|
916
|
+
expect(names).toEqual([])
|
|
917
|
+
})
|
|
918
|
+
|
|
919
|
+
it("still runs buildTail(undefined, base) as a legacy call", async () => {
|
|
920
|
+
const names: string[] = []
|
|
921
|
+
await requestNames(() => buildTail(undefined as never, "base"), names)
|
|
922
|
+
expect(names).not.toEqual([])
|
|
923
|
+
})
|
|
924
|
+
|
|
925
|
+
// pins `| undefined` on BuildTailOptions under exactOptionalPropertyTypes (typecheck)
|
|
926
|
+
it("explicitly undefined fixFirst and green default like omitted ones", async () => {
|
|
927
|
+
expect(
|
|
928
|
+
await requestNames(() => buildTail("base", { fixFirst: undefined, green: undefined })),
|
|
929
|
+
).toEqual(await requestNames(() => buildTail("base")))
|
|
930
|
+
})
|
|
931
|
+
})
|
|
813
932
|
})
|
|
814
933
|
|
|
934
|
+
// pins the public re-exports from the feature entry point and the published workflows entry
|
|
935
|
+
export type _PublicBuildTailOptions = [PublicBuildTailOptions, bundled.BuildTailOptions]
|
|
936
|
+
|
|
815
937
|
describe("buildTail without rules", () => {
|
|
816
938
|
const RULES = "Change only these paths"
|
|
817
939
|
|
|
@@ -850,7 +972,7 @@ describe("buildTail without rules", () => {
|
|
|
850
972
|
},
|
|
851
973
|
),
|
|
852
974
|
)
|
|
853
|
-
await buildTail(
|
|
975
|
+
await buildTail("base", { fixFirst: true }).catch((error: unknown) => {
|
|
854
976
|
if (!(error instanceof Stop)) throw error
|
|
855
977
|
})
|
|
856
978
|
return prompts
|
|
@@ -257,9 +257,9 @@ export interface FeedbackTarget {
|
|
|
257
257
|
|
|
258
258
|
export interface BuildTailOptions {
|
|
259
259
|
/** Fix first when entered red; default `false`. */
|
|
260
|
-
readonly fixFirst?: boolean
|
|
261
|
-
/**
|
|
262
|
-
readonly green?: boolean
|
|
260
|
+
readonly fixFirst?: boolean | undefined
|
|
261
|
+
/** Asserts the tree is already green, so the tail skips keep-green; default `false`. */
|
|
262
|
+
readonly green?: boolean | undefined
|
|
263
263
|
/** Runs after every code-changing turn: fix-check, quality fix, risk fix, nit fix, escalation describe. */
|
|
264
264
|
readonly guard?: Guard
|
|
265
265
|
/** `false`: `guard` skips the escalation's human rests. */
|
|
@@ -292,15 +292,23 @@ export function buildTail(
|
|
|
292
292
|
third = false,
|
|
293
293
|
fourth: Omit<BuildTailOptions, "fixFirst" | "green"> = {},
|
|
294
294
|
): Promise<kit.ReviewOutcome> {
|
|
295
|
-
|
|
296
|
-
|
|
295
|
+
// Before any step runs, so a bad call never leaves a half-walked flow.
|
|
296
|
+
if (typeof first !== "string" && typeof first !== "boolean" && typeof second !== "string") {
|
|
297
|
+
return Promise.reject(
|
|
298
|
+
new Error(
|
|
299
|
+
"buildTail takes buildTail(base, options?) or the deprecated buildTail(fixFirst, base, green?, options?); this call fits neither",
|
|
300
|
+
),
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
const [base, options]: [string, BuildTailOptions] =
|
|
297
304
|
typeof first === "string"
|
|
298
|
-
? (
|
|
299
|
-
: { ...fourth, fixFirst: first, green: third }
|
|
305
|
+
? [first, (second as BuildTailOptions | undefined) ?? {}]
|
|
306
|
+
: [second as string, { ...fourth, fixFirst: first, green: third }]
|
|
307
|
+
const { fixFirst, green, ...reviewOptions } = options
|
|
300
308
|
return tail(
|
|
301
|
-
|
|
302
|
-
(escalations) => review(base, escalations,
|
|
303
|
-
|
|
309
|
+
fixFirst ?? false,
|
|
310
|
+
(escalations) => review(base, escalations, reviewOptions),
|
|
311
|
+
green ?? false,
|
|
304
312
|
options.guard,
|
|
305
313
|
options.lenses,
|
|
306
314
|
options.guardRests,
|
|
@@ -225,6 +225,7 @@ describe("implement", () => {
|
|
|
225
225
|
reference: string | undefined,
|
|
226
226
|
tier?: "low" | "high",
|
|
227
227
|
amend?: boolean,
|
|
228
|
+
call: () => Promise<void> = () => implement({ reference, tier, amend }),
|
|
228
229
|
) => {
|
|
229
230
|
const requests: StepRequest[] = []
|
|
230
231
|
installContext(
|
|
@@ -241,12 +242,25 @@ describe("implement", () => {
|
|
|
241
242
|
},
|
|
242
243
|
),
|
|
243
244
|
)
|
|
244
|
-
await
|
|
245
|
+
await call()
|
|
245
246
|
const request = requests.find((r) => r.kind === "agent" && r.name === "building")
|
|
246
247
|
if (request?.kind !== "agent") throw new Error("no building turn")
|
|
247
248
|
return request.prompt
|
|
248
249
|
}
|
|
249
250
|
|
|
251
|
+
it("the deprecated positional form renders the same prompt as the options form", async () => {
|
|
252
|
+
const modern = await building("abc", "low")
|
|
253
|
+
const legacy = await building("abc", "low", undefined, () => implement("abc", "low"))
|
|
254
|
+
expect(legacy).toBe(modern)
|
|
255
|
+
expect(legacy).toContain("git show abc")
|
|
256
|
+
const amended = await building(undefined, undefined, true)
|
|
257
|
+
expect(
|
|
258
|
+
await building(undefined, undefined, undefined, () => implement(undefined, undefined, true)),
|
|
259
|
+
).toBe(amended)
|
|
260
|
+
const bare = await building(undefined, undefined, undefined, () => implement())
|
|
261
|
+
expect(bare).toBe(await building(undefined))
|
|
262
|
+
})
|
|
263
|
+
|
|
250
264
|
it("names the reference with a git show line", async () => {
|
|
251
265
|
expect(await building("abc")).toContain("git show abc")
|
|
252
266
|
})
|
package/src/workflows/index.ts
CHANGED
|
@@ -24,7 +24,7 @@ export * from "./feature/planning.js"
|
|
|
24
24
|
export * from "./feature/packages.js"
|
|
25
25
|
export * from "./feature/refactor.js"
|
|
26
26
|
export * from "./feature/seam.js"
|
|
27
|
-
export { buildTail, type ReviewOutcome } from "./feature/review.js"
|
|
27
|
+
export { buildTail, type BuildTailOptions, type ReviewOutcome } from "./feature/review.js"
|
|
28
28
|
export * from "./feature/feature.js"
|
|
29
29
|
export { base, review, reviewFlow } from "./review/review.js"
|
|
30
30
|
export { fix, fixFlow, fixReviewNotes } from "./fix/fix.js"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { FLAGS, type FlagDeclaration } from "../../flags/index.js"
|
|
2
3
|
import { configure, type WorkflowDoors } from "../../flows/index.js"
|
|
3
4
|
import * as workflows from "../index.js"
|
|
4
5
|
import * as options from "./options.js"
|
|
@@ -10,13 +11,19 @@ describe("the shipped workflow factories", () => {
|
|
|
10
11
|
it("take exactly the settings the shipped workflows declare as options", () => {
|
|
11
12
|
expect(Object.keys(ShippedOptions.fields).sort()).toEqual(
|
|
12
13
|
[...Object.keys(defaults), ...Object.keys(envDefaults)]
|
|
13
|
-
.filter((name) => name
|
|
14
|
+
.filter((name) => !name.startsWith("flag_"))
|
|
14
15
|
.sort(),
|
|
15
16
|
)
|
|
16
17
|
})
|
|
17
18
|
|
|
19
|
+
it("declare a flag_<name> default for exactly the registered flags", () => {
|
|
20
|
+
const declared = Object.keys(defaults).filter((name) => name.startsWith("flag_"))
|
|
21
|
+
expect(declared.sort()).toEqual(FLAGS.map(({ name }) => `flag_${name}`).sort())
|
|
22
|
+
for (const { name, defaultOn } of FLAGS as readonly FlagDeclaration[])
|
|
23
|
+
expect(defaults).toHaveProperty(`flag_${name}`, defaultOn ? "1" : "0")
|
|
24
|
+
})
|
|
25
|
+
|
|
18
26
|
it("declare seamAllow and flag_naive_pass defaults; flag_naive_pass is no option", () => {
|
|
19
|
-
expect(defaults.flag_naive_pass).toBe("0")
|
|
20
27
|
expect(Object.keys(ShippedOptions.fields)).not.toContain("flag_naive_pass")
|
|
21
28
|
expect(configure(feature({ seamAllow: "docs/**" }))).toMatchObject({
|
|
22
29
|
ok: true,
|
|
@@ -25,7 +32,6 @@ describe("the shipped workflow factories", () => {
|
|
|
25
32
|
})
|
|
26
33
|
|
|
27
34
|
it("declare flag_risk_tier off by default; it is no factory option", () => {
|
|
28
|
-
expect(defaults.flag_risk_tier).toBe("0")
|
|
29
35
|
expect(Object.keys(ShippedOptions.fields)).not.toContain("flag_risk_tier")
|
|
30
36
|
})
|
|
31
37
|
|
|
@@ -1063,7 +1063,7 @@ describe("the refactor prompts", () => {
|
|
|
1063
1063
|
|
|
1064
1064
|
it("building and fix rules forbid every test file, with no stray exception", () => {
|
|
1065
1065
|
for (const prompt of renderText(() => [
|
|
1066
|
-
refactorBuildingPrompt(".gtd/REFACTOR.md", strays, pattern),
|
|
1066
|
+
refactorBuildingPrompt({ pkg: ".gtd/REFACTOR.md", strays, pattern }),
|
|
1067
1067
|
refactorFixPrompt(strays, pattern),
|
|
1068
1068
|
])) {
|
|
1069
1069
|
expect(prompt).toContain("never a test file")
|
|
@@ -1073,8 +1073,8 @@ describe("the refactor prompts", () => {
|
|
|
1073
1073
|
|
|
1074
1074
|
it("building adds the act-on-review-feedback line only when asked", () => {
|
|
1075
1075
|
const [plain, feedback] = renderText(() => [
|
|
1076
|
-
refactorBuildingPrompt(".gtd/REFACTOR.md", strays, pattern),
|
|
1077
|
-
refactorBuildingPrompt(".gtd/REFACTOR.md", strays, pattern,
|
|
1076
|
+
refactorBuildingPrompt({ pkg: ".gtd/REFACTOR.md", strays, pattern }),
|
|
1077
|
+
refactorBuildingPrompt({ pkg: ".gtd/REFACTOR.md", strays, pattern, feedback: true }),
|
|
1078
1078
|
])
|
|
1079
1079
|
expect(plain).not.toContain("## Review feedback")
|
|
1080
1080
|
expect(feedback).toContain("## Review feedback")
|
|
@@ -473,44 +473,19 @@ ${strays.map((path) => ` - \`${displayPath(path)}\``).join("\n")}
|
|
|
473
473
|
|
|
474
474
|
const withRules = (rules?: string): string => (rules === undefined ? "" : `${rules}\n`)
|
|
475
475
|
|
|
476
|
-
|
|
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(
|
|
487
|
-
pkg: string,
|
|
488
|
-
strays: readonly string[],
|
|
489
|
-
pattern: string,
|
|
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 = ({
|
|
476
|
+
export const refactorBuildingPrompt = ({
|
|
508
477
|
pkg,
|
|
509
478
|
strays,
|
|
510
479
|
pattern,
|
|
511
480
|
reference,
|
|
512
481
|
feedback = false,
|
|
513
|
-
}:
|
|
482
|
+
}: {
|
|
483
|
+
pkg: string
|
|
484
|
+
strays: readonly string[]
|
|
485
|
+
pattern: string
|
|
486
|
+
reference?: string | undefined
|
|
487
|
+
feedback?: boolean | undefined
|
|
488
|
+
}): string =>
|
|
514
489
|
`${stateFileRules}
|
|
515
490
|
- The package to implement is \`${pkg}\`; never delete it
|
|
516
491
|
${
|