@pmelab/gtd 43.0.0 → 44.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +17 -13
  3. package/dist/chunks/{reserved-COEMFEpy.mjs → reserved-DJk_6-ju.mjs} +41 -1
  4. package/dist/chunks/review-X7Jfz0dY.mjs +249 -0
  5. package/dist/chunks/{tail-C_xn8U7U.mjs → tail-5w5mxGTq.mjs} +3 -3
  6. package/dist/workflows/cleanup.mjs +2 -2
  7. package/dist/workflows/feature.mjs +6 -278
  8. package/dist/workflows/fix.mjs +2 -2
  9. package/dist/workflows/review.mjs +27 -7
  10. package/dist/workflows/rollout.mjs +1 -1
  11. package/dist/workflows.mjs +74 -44
  12. package/package.json +1 -1
  13. package/src/steering/FORMATS.md +4 -4
  14. package/src/workflows/feature/coverage.ts +1 -1
  15. package/src/workflows/feature/feature.test.ts +8 -0
  16. package/src/workflows/feature/feature.ts +2 -2
  17. package/src/workflows/feature/index.ts +1 -1
  18. package/src/workflows/feature/refactor.ts +1 -1
  19. package/src/workflows/feature/seam.ts +1 -1
  20. package/src/workflows/feature/steps.test.ts +2 -64
  21. package/src/workflows/feature/steps.ts +5 -41
  22. package/src/workflows/fix/fix.feature +20 -21
  23. package/src/workflows/index.ts +1 -1
  24. package/src/workflows/review/review-door.feature +149 -0
  25. package/src/workflows/review/review.test.ts +1 -73
  26. package/src/workflows/review/review.ts +34 -6
  27. package/src/workflows/{feature → shared}/review.test.ts +4 -8
  28. package/src/workflows/{feature → shared}/review.ts +13 -10
  29. package/src/workflows/shared/skills.test.ts +2 -0
  30. package/src/workflows/shared/skills.ts +2 -0
  31. package/src/workflows/shared/steps.test.ts +64 -2
  32. package/src/workflows/shared/steps.ts +40 -1
  33. package/src/workflows/shared/tail.ts +2 -2
  34. package/src/workflows/shared/text.ts +11 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "43.0.0",
3
+ "version": "44.0.0",
4
4
  "description": "Drive the gtd loop inside Claude Code: agent turns run as subagents, gtd decides every beat",
5
5
  "author": {
6
6
  "name": "Philipp Melab"
package/README.md CHANGED
@@ -588,24 +588,28 @@ the agent may overturn the answer. See
588
588
  for its `ui:` settings.
589
589
 
590
590
  The bundled workflows are named `feature` (the ordinary start above), `review`
591
- and `fix`. They are independent: `review` and `fix` never plan, and they meet
592
- only in `.gtd/REVIEW.md`. Each brings its own door. This command:
591
+ and `fix`. `review` and `fix` never plan. Each brings its own door. This
592
+ command:
593
593
 
594
594
  ```bash
595
595
  gtd door review <commitish>
596
596
  ```
597
597
 
598
- starts a pure review of everything from `<commitish>` to HEAD. It goes straight
599
- to the quality lap and a review, with no planning and no building; unlike
600
- `feature`, its lap fixes every finding and a reviewer turn
601
- (`build.review.reviewing`) writes `.gtd/REVIEW.md`, risks and all. Sign off and
602
- it closes. Leave notes and the process ends with `.gtd/REVIEW.md` (and the
603
- reviewer's original, `.gtd/REVIEW.orig.md`) left for `gtd door fix`. Until `fix`
604
- consumes them, starting `feature` or `review` is refused; remove both files to
605
- abandon the hand-off. `gtd door fix` fixes those notes and every `Risk:` in one
606
- turn, then asks for your review of the fix. With no review waiting, it repairs a
607
- red baseline as its own reviewed commit instead. `gtd doors` lists the doors of
608
- the workflows your `gtd.config.ts` exports, plus any it adds.
598
+ starts a review of everything from `<commitish>` to HEAD — the same review a
599
+ `feature` process ends with, minus planning and building. A green gate, then the
600
+ quality lenses: mechanical findings are fixed, noise is dropped, and the rest
601
+ become notes in `.gtd/REVIEW.md`, one tick box each. Tick `fix as suggested` or
602
+ write your own answer and the process fixes it in place — your own answers that
603
+ ask for a change are collected into `.gtd/REQUIREMENTS.md` and built by one
604
+ `feedback.fixing` turn, with no design phase and no criteria — under the health
605
+ loop, then asks for your review again. Sign off (or tick `won't fix`) and it
606
+ closes; nothing is left behind. A note asking for new behaviour belongs in a
607
+ `feature` process: tick `won't fix` and start one. `gtd door fix` repairs a red
608
+ baseline as its own reviewed commit; a note at its review is left in
609
+ `.gtd/REVIEW.md` (beside the reviewer's original, `.gtd/REVIEW.orig.md`) for the
610
+ next `gtd door fix`, and until that runs, starting `feature` or `review` is
611
+ refused — remove both files to abandon the hand-off. `gtd doors` lists the doors
612
+ of the workflows your `gtd.config.ts` exports, plus any it adds.
609
613
  `gtd --workflow <name>` starts any workflow by name.
610
614
 
611
615
  The workflow itself is a plain async TypeScript function, and the
@@ -978,6 +978,15 @@ ${notes.length === 0 ? "(none)\n" : notesCapture(notes)}
978
978
  The reviewer's risks are:
979
979
 
980
980
  ${risks.length === 0 ? "(none)\n" : notesCapture(risks)}`;
981
+ const reviewFeedbackFixPrompt = () => `${stateFileRules}
982
+ - Read \`.gtd/REQUIREMENTS.md\` — the concerns the human's review asked for,
983
+ one \`## \` heading each, in build order
984
+ - Build every concern in this one turn; an answered question is context,
985
+ not a concern
986
+ - Where a change is behavioural, add a test that fails without it
987
+ - Leave \`.gtd/REQUIREMENTS.md\` untouched — the next step removes it
988
+ - Leave everything uncommitted and finish your turn
989
+ `;
981
990
  const buildReviewReviewMissingMessage = (commit) => `The review round committed no \`.gtd/REVIEW.md\` (at ${commit}), so there is
982
991
  nothing to sign off on.
983
992
 
@@ -1606,6 +1615,36 @@ const awaitReview = (base, message = buildReviewAwaitReviewMessage(base)) => hum
1606
1615
  base,
1607
1616
  acceptClean: true
1608
1617
  });
1618
+ /** Answer every `question` note inline in `.gtd/REVIEW.md`. */
1619
+ const answerReviewQuestions = (notes) => agentWithSkills("review.answer-review-questions", buildReviewAnswerQuestionsPrompt(notes), {
1620
+ label: "Answering your questions",
1621
+ file: REVIEW,
1622
+ mode: "review",
1623
+ model: planner(),
1624
+ system: reviewerSystem()
1625
+ });
1626
+ /** Fix every `nit` note in one turn. */
1627
+ const fixNits = (notes, access, rules) => {
1628
+ const fixing = (name) => agentWithSkills(name, buildReviewFixNitsPrompt(notes, rules), {
1629
+ label: "Fixing your nits",
1630
+ file: REVIEW,
1631
+ model: planner(),
1632
+ system: reviewerSystem()
1633
+ });
1634
+ return access === void 0 ? fixing("review.fix.nits.fixing") : scope({
1635
+ name: "review.fix.nits",
1636
+ access
1637
+ }, () => fixing("fixing"));
1638
+ };
1639
+ /** Turn the review round `capture` describes into `.gtd/REQUIREMENTS.md`, or change nothing. */
1640
+ const collecting = (capture, round) => agentWithSkills("review.collecting", buildReviewCollectingPrompt(capture, void 0, round), {
1641
+ label: "Collecting your feedback",
1642
+ file: REQUIREMENTS,
1643
+ mode: "qa",
1644
+ model: planner(),
1645
+ system: reviewerSystem(),
1646
+ allowEmpty: true
1647
+ });
1609
1648
  /** The round at commit `round` left no `.gtd/REVIEW.md`. */
1610
1649
  const reviewMissing = (round) => human("review.review-missing", {
1611
1650
  message: buildReviewReviewMissingMessage(round),
@@ -1802,6 +1841,7 @@ const skills = (vars) => {
1802
1841
  return {
1803
1842
  design: ["spec-driven-development", "planning-and-task-breakdown"],
1804
1843
  notes: ["incremental-implementation", "code-simplification"],
1844
+ feedback: ["test-driven-development", "incremental-implementation"],
1805
1845
  ...build,
1806
1846
  ...Object.fromEntries(Object.entries(build).map(([name, list]) => [`refactor.${name}`, list])),
1807
1847
  refactor: ["ponytail"],
@@ -1834,4 +1874,4 @@ const steering = {
1834
1874
  [REVIEW]: "review"
1835
1875
  };
1836
1876
  //#endregion
1837
- export { naivePassPrompt as $, agentWithSkills as A, coverageQuestion as B, fixCheck as C, envDefaults as Ct, reviewMissing as D, flipProblems as Dt, planner as E, flagReferences as Et, builderSystem as F, designTriagePrompt as G, criterionLine as H, clearCriteriaMessage as I, handOffAwaitReviewMessage as J, finisherSystem as K, clearCriteriaQuestion as L, buildReviewCollectingPrompt as M, buildReviewFixNitsPrompt as N, reviewQuality as O, registryEntry as Ot, buildReviewTriageMessage as P, naiveApplyFailure as Q, composedReview as R, coder as S, defaults as St, fixRisks as T, flagMentions as Tt, designGateAnswerMessage as U, coveringPrompt as V, designSystem as W, implementBuildingPrompt as X, idleMessage as Y, implementFixPrompt as Z, REFACTOR_NOTE as _, slicingPrompt as _t, flagModeOn as a, refactorBuildingPrompt as at, SLICES as b, unwindFailedMessage as bt, baseline as c, refactorNotePrompt as ct, guarded as d, reviewEditsCapture as dt, normaliseCriteriaScript as et, healthy as f, reviewGateBlockedMessage as ft, REFACTOR as g, seamSteerMessage as gt, QUALITY as h, seamByHandMessage as ht, skills as i, qualityUnresolvedMessage as it, buildReviewAnswerQuestionsPrompt as j, reviewing as k, requireFlag as kt, escalation as l, refactorRules as lt, HANDOFF as m, reviewerSystem as mt, summary as n, qualityFindingsFile as nt, flagShipped as o, refactorDroppedRefusal as ot, FEEDBACK as p, reviewNotesCapture as pt, fixReviewNotesPrompt as q, access as r, qualityTriageMessage as rt, shipped as s, refactorFixPrompt as st, steering as t, oversizedMessage as tt, gate as u, reviewEditNotesCapture as ut, REQUIREMENTS as v, startGateBlockedMessage as vt, fixQuality as w, testFileGlobs as wt, awaitReview as x, unwindFailure as xt, REVIEW as y, uncoveredMessage as yt, coverageMessage as z };
1877
+ export { naiveApplyFailure as $, reviewMissing as A, registryEntry as At, coverageMessage as B, coder as C, unwindFailure as Ct, fixQuality as D, flagMentions as Dt, fixNits as E, testFileGlobs as Et, buildReviewTriageMessage as F, designSystem as G, coveringPrompt as H, builderSystem as I, fixReviewNotesPrompt as J, designTriagePrompt as K, clearCriteriaMessage as L, reviewing as M, agentWithSkills as N, fixRisks as O, flagReferences as Ot, buildReviewCollectingPrompt as P, implementFixPrompt as Q, clearCriteriaQuestion as R, awaitReview as S, unwindFailedMessage as St, fixCheck as T, envDefaults as Tt, criterionLine as U, coverageQuestion as V, designGateAnswerMessage as W, idleMessage as X, handOffAwaitReviewMessage as Y, implementBuildingPrompt as Z, REFACTOR_NOTE as _, seamByHandMessage as _t, flagModeOn as a, qualityUnresolvedMessage as at, SLICES as b, startGateBlockedMessage as bt, baseline as c, refactorFixPrompt as ct, guarded as d, reviewEditNotesCapture as dt, naivePassPrompt as et, healthy as f, reviewEditsCapture as ft, REFACTOR as g, reviewerSystem as gt, QUALITY as h, reviewNotesCapture as ht, skills as i, qualityTriageMessage as it, reviewQuality as j, requireFlag as jt, planner as k, flipProblems as kt, escalation as l, refactorNotePrompt as lt, HANDOFF as m, reviewGateBlockedMessage as mt, summary as n, oversizedMessage as nt, flagShipped as o, refactorBuildingPrompt as ot, FEEDBACK as p, reviewFeedbackFixPrompt as pt, finisherSystem as q, access as r, qualityFindingsFile as rt, shipped as s, refactorDroppedRefusal as st, steering as t, normaliseCriteriaScript as tt, gate as u, refactorRules as ut, REQUIREMENTS as v, seamSteerMessage as vt, collecting as w, defaults as wt, answerReviewQuestions as x, uncoveredMessage as xt, REVIEW as y, slicingPrompt as yt, composedReview as z };
@@ -0,0 +1,249 @@
1
+ import { answered, changesSince, head, judge, numeric, requireRevert, restoreScript, run, showScript, vars, writeScript } from "../flows.mjs";
2
+ import { removedAssertions, review } from "../workflow-kit.mjs";
3
+ import { A as reviewMissing, E as fixNits, F as buildReviewTriageMessage, O as fixRisks, S as awaitReview, T as fixCheck, d as guarded, dt as reviewEditNotesCapture, f as healthy, ft as reviewEditsCapture, h as QUALITY, ht as reviewNotesCapture, w as collecting, x as answerReviewQuestions, y as REVIEW, z as composedReview } from "./reserved-DJk_6-ju.mjs";
4
+ import { i as isOptionalObject, n as closeReview, o as tail, s as NO_FINDINGS } from "./tail-5w5mxGTq.mjs";
5
+ //#region src/workflows/shared/review.ts
6
+ const VERDICTS = [
7
+ "edit",
8
+ "question",
9
+ "nit",
10
+ "praise"
11
+ ];
12
+ /** One four-way choice per note the human added to `.gtd/REVIEW.md`. */
13
+ const verdictQuestion = (note) => ({
14
+ id: note.id,
15
+ primitive: "choice",
16
+ instructions: `Classify the human's note on review item "${note.anchor}" (in .gtd/REVIEW.md). Its evidence holds the anchor, the reviewer's text and the human's text.`,
17
+ criteria: "edit: a request to change behaviour, design or scope — anything needing a plan, or any note you are unsure about. question: asks something and wants an answer, with no change requested. nit: a small, local, unambiguous fix (naming, typo, formatting, comment wording) needing no re-plan. praise: an approving remark with nothing to do."
18
+ });
19
+ const TIER_ORDER = {
20
+ critical: 0,
21
+ should: 1,
22
+ could: 2
23
+ };
24
+ /** Capture order: critical, should, could; within a tier one-way doors, then new concepts. */
25
+ const captureKey = (s) => s === void 0 ? 2 : TIER_ORDER[s.tier] * 3 + s.rank;
26
+ const SEVERITY_ANSWERS = {
27
+ security: ["yes", "no"],
28
+ reversibility: ["one-way", "two-way"],
29
+ blastRadius: [
30
+ "local",
31
+ "module",
32
+ "contract"
33
+ ],
34
+ newConcept: ["yes", "no"]
35
+ };
36
+ /** Anything unanswered, unrecognised, cut or below `minP` is `critical`, never `could`. */
37
+ const severityOf = (note, judgment, minP) => {
38
+ const ans = (q) => judgment.answers[`${note.id}.${q}`];
39
+ const a = (q) => ans(q)?.answer;
40
+ const rank = () => a("reversibility") === "one-way" ? 0 : a("newConcept") === "yes" ? 1 : 2;
41
+ if (note.forced) return {
42
+ tier: "critical",
43
+ reason: "critical: forced by `!`",
44
+ rank: 2
45
+ };
46
+ if (judgment.truncated.includes(note.id) || Object.entries(SEVERITY_ANSWERS).some(([q, allowed]) => !allowed.includes(a(q) ?? "") || !((ans(q)?.p ?? NaN) >= minP))) return {
47
+ tier: "critical",
48
+ reason: "critical: judge unsure",
49
+ rank: 2
50
+ };
51
+ const critical = [...a("security") === "yes" ? ["security"] : [], ...a("reversibility") === "one-way" ? ["one-way door"] : []];
52
+ if (critical.length > 0) return {
53
+ tier: "critical",
54
+ reason: `critical: ${critical.join(", ")}`,
55
+ rank: rank()
56
+ };
57
+ const should = [...a("blastRadius") === "contract" ? ["contract"] : [], ...a("newConcept") === "yes" ? ["new concept"] : []];
58
+ if (should.length > 0) return {
59
+ tier: "should",
60
+ reason: `should: ${should.join(", ")}`,
61
+ rank: rank()
62
+ };
63
+ return {
64
+ tier: "could",
65
+ reason: "could",
66
+ rank: 2
67
+ };
68
+ };
69
+ const severityQuestions = (note) => [
70
+ {
71
+ id: `${note.id}.security`,
72
+ primitive: "noul",
73
+ instructions: `Does the change the human asks for in note "${note.id}" (on "${note.anchor}") touch security — authentication, authorisation, secrets, untrusted input or data exposure?`,
74
+ criteria: "yes: it changes or could weaken a security property. no: it does not."
75
+ },
76
+ {
77
+ id: `${note.id}.reversibility`,
78
+ primitive: "choice",
79
+ instructions: `Once the change in note "${note.id}" ships, can it be undone without breaking anyone who already depends on it?`,
80
+ criteria: "one-way: a published API, file format, persisted data, protocol or other contract that cannot be taken back. two-way: can be reverted freely."
81
+ },
82
+ {
83
+ id: `${note.id}.blastRadius`,
84
+ primitive: "choice",
85
+ instructions: `How far does the change in note "${note.id}" reach?`,
86
+ criteria: "local: one function or file. module: a whole module or several files. contract: a public interface other code or users depend on."
87
+ },
88
+ {
89
+ id: `${note.id}.newConcept`,
90
+ primitive: "noul",
91
+ instructions: `Does the change in note "${note.id}" introduce a concept new to this codebase — a new abstraction, entity or term? Judge from the evidence, never a glossary; reusing or extending an existing concept is not new.`,
92
+ criteria: "yes: a genuinely new concept. no: reuses or extends an existing one."
93
+ }
94
+ ];
95
+ /**
96
+ * One judge rest: a verdict per unforced note (a forced note is asked
97
+ * nothing) plus four severity answers. Dismissing a note is the risky
98
+ * direction, so only a confident non-`edit` verdict on uncut evidence counts;
99
+ * a cut, unanswered or below-floor note is an `edit`. Severity answers on a
100
+ * non-`edit` note are ignored; on an `edit` they order the collecting turn.
101
+ */
102
+ const triage = (severities) => async (notes) => {
103
+ const asked = notes.filter((n) => !n.forced);
104
+ const judgment = asked.length === 0 ? {
105
+ answers: {},
106
+ truncated: []
107
+ } : await judge("review.triage", {
108
+ questions: asked.flatMap((n) => [verdictQuestion(n), ...severityQuestions(n)]),
109
+ evidence: Object.fromEntries(asked.map((n) => [n.id, `Anchor: ${n.anchor}\nReviewer's text: ${n.before}\nHuman's text: ${n.text}`])),
110
+ message: buildReviewTriageMessage(),
111
+ label: "Judging each note"
112
+ });
113
+ const { answers, truncated } = judgment;
114
+ const minP = numeric(vars.reviewNoteActionable, Infinity);
115
+ const severityMinP = numeric(vars.reviewSeverityMinP, Infinity);
116
+ severities.clear();
117
+ return new Map(notes.map((n) => {
118
+ const verdict = n.forced ? void 0 : VERDICTS.find((v) => v !== "edit" && answered(answers[n.id], v, minP));
119
+ if (verdict !== void 0 && !truncated.includes(n.id)) return [n.id, verdict];
120
+ severities.set(n.id, severityOf(n, judgment, severityMinP));
121
+ return [n.id, "edit"];
122
+ }));
123
+ };
124
+ const isCode = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
125
+ /**
126
+ * gtd writes `.gtd/REVIEW.md` from the quality lap's `report` — no reviewer
127
+ * turn — and removes what the fixer left in `.gtd/QUALITY.md`. A re-review
128
+ * after fixed nits restores the doc as committed at `carry`, answers kept;
129
+ * a fresh one leaves out the notes the human already answered.
130
+ */
131
+ const composing = (base, report, carry, nitFix, answered = /* @__PURE__ */ new Set()) => run("review.composing", carry === void 0 ? writeScript(REVIEW, composedReview(base, {
132
+ ...report,
133
+ weakened: removedAssertions(changesSince(base)),
134
+ nitFix,
135
+ answered
136
+ }), [QUALITY]) : showScript(carry, REVIEW, REVIEW), {
137
+ label: "Composing the review",
138
+ file: REVIEW,
139
+ base
140
+ });
141
+ /**
142
+ * The quality lap's notes reach a human over everything since `base`, who
143
+ * answers each one — fix as suggested, won't fix, or their own answer — and
144
+ * may comment. A fix joins the nit batch, a won't-fix is dropped, and an own
145
+ * answer or comment is judged: edits end the review as `feedback` for the
146
+ * caller to build, questions are answered in place, nits fixed in one batch,
147
+ * praise dropped.
148
+ */
149
+ const humanReview = (base, escalations = { rounds: 0 }, options = {}) => {
150
+ const { guard, guardRests, fixAccess, rules } = options;
151
+ const feedback = options.feedback ?? {
152
+ file: ".gtd/REQUIREMENTS.md",
153
+ collecting,
154
+ unmatched: "gtd land: no declared pattern matches the pending changes — write .gtd/REQUIREMENTS.md from the feedback, or change nothing when it asks for nothing"
155
+ };
156
+ const severities = /* @__PURE__ */ new Map();
157
+ let nitFix;
158
+ const answered = /* @__PURE__ */ new Set();
159
+ return review(base, {
160
+ file: REVIEW,
161
+ feedback: feedback.file,
162
+ isCode,
163
+ reviewing: (b, carry) => {
164
+ const fixedAt = nitFix;
165
+ nitFix = void 0;
166
+ return composing(b, options.report ?? NO_FINDINGS, carry, fixedAt, answered);
167
+ },
168
+ fixRisks: guarded((notes) => fixRisks(notes, fixAccess, rules), guard),
169
+ keepGreen: () => healthy(() => fixCheck(rules), {
170
+ escalations,
171
+ guard,
172
+ guardRests,
173
+ rules
174
+ }),
175
+ awaitReview: (b) => awaitReview(b),
176
+ reviewMissing,
177
+ collecting: feedback.collecting,
178
+ triage: triage(severities),
179
+ decided: (decisions) => decisions.forEach((d) => answered.add(d.anchor)),
180
+ answerQuestions: answerReviewQuestions,
181
+ fixNits: async (notes) => {
182
+ await guarded((n) => fixNits(n, fixAccess, rules), guard)(notes);
183
+ nitFix = head();
184
+ },
185
+ close: closeReview,
186
+ captures: {
187
+ edits: reviewEditsCapture,
188
+ notes: reviewNotesCapture,
189
+ editNotes: (round, edits, answeredAt) => reviewEditNotesCapture(round, edits.map((n, i) => ({
190
+ n,
191
+ i,
192
+ s: severities.get(n.id),
193
+ key: captureKey(severities.get(n.id))
194
+ })).sort((x, y) => x.key - y.key || x.i - y.i).map(({ n, s }) => ({
195
+ id: n.id,
196
+ anchor: n.anchor,
197
+ text: n.forced ? n.text.replace(/^!\s*/, "") : n.text,
198
+ ...s === void 0 ? {} : { label: s.reason }
199
+ })), answeredAt)
200
+ },
201
+ refusals: {
202
+ deleted: "gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note (or edit code) to request changes.",
203
+ unmatched: feedback.unmatched,
204
+ ...feedback.dropped === void 0 ? {} : { dropped: feedback.dropped }
205
+ }
206
+ });
207
+ };
208
+ function buildTail(first, second, ...rest) {
209
+ const options = callForm(first, second, rest);
210
+ if (options === void 0) return Promise.reject(/* @__PURE__ */ new Error("buildTail takes buildTail(base, options?) or the deprecated buildTail(fixFirst, base, green?, options?); this call fits neither"));
211
+ const [base, form] = options;
212
+ return tail((escalations, report) => humanReview(base, escalations, {
213
+ ...form,
214
+ report
215
+ }), {
216
+ ...form,
217
+ triage: true
218
+ });
219
+ }
220
+ const isOptionalBoolean = (value) => value == null || typeof value === "boolean";
221
+ const legacyForm = (fixFirst, base, green, options) => isOptionalBoolean(fixFirst) && isOptionalBoolean(green) && isOptionalObject(options) ? [base, {
222
+ ...options,
223
+ fixFirst: fixFirst ?? void 0,
224
+ green: green ?? void 0
225
+ }] : void 0;
226
+ const currentForm = (base, options, third, fourth) => isOptionalObject(options) && third == null && fourth == null ? [base, options ?? {}] : void 0;
227
+ const callForm = (first, second, [third, fourth, ...extra]) => {
228
+ if (extra.some((value) => value != null)) return void 0;
229
+ if (typeof second === "string") return legacyForm(first, second, third, fourth);
230
+ if (typeof first === "string") return currentForm(first, second, third, fourth);
231
+ };
232
+ /**
233
+ * Undo the human's review-round code edits. A path changed since the human
234
+ * left it is not overwritten; requireRevert names it instead.
235
+ */
236
+ const reUnwind = async (feedback) => {
237
+ const { base, restoreFrom, edited } = feedback;
238
+ await run("re-unwind", restoreScript(base, {
239
+ restore: edited.filter((c) => c.status !== "added").map((c) => c.path),
240
+ remove: edited.filter((c) => c.status === "added").map((c) => c.path)
241
+ }, restoreFrom), {
242
+ label: "Re-unwinding your review edit",
243
+ file: REVIEW,
244
+ base
245
+ });
246
+ requireRevert(edited, base);
247
+ };
248
+ //#endregion
249
+ export { reUnwind as i, humanReview as n, isCode as r, buildTail as t };
@@ -1,6 +1,6 @@
1
1
  import { answered, head, judge, mark, numeric, read, refuse, removeScript, run, scope, sectionBodies, showScript, vars, writeScript } from "../flows.mjs";
2
2
  import { fixFindings, lensEntriesOf, lensOf, qualityLap as qualityLap$1, reviewToHandOff } from "../workflow-kit.mjs";
3
- import { C as fixCheck, D as reviewMissing, J as handOffAwaitReviewMessage, O as reviewQuality, T as fixRisks, d as guarded, f as healthy, h as QUALITY, k as reviewing, l as escalation, nt as qualityFindingsFile, rt as qualityTriageMessage, w as fixQuality, x as awaitReview, y as REVIEW } from "./reserved-COEMFEpy.mjs";
3
+ import { A as reviewMissing, D as fixQuality, M as reviewing, O as fixRisks, S as awaitReview, T as fixCheck, Y as handOffAwaitReviewMessage, d as guarded, f as healthy, h as QUALITY, it as qualityTriageMessage, j as reviewQuality, l as escalation, rt as qualityFindingsFile, y as REVIEW } from "./reserved-DJk_6-ju.mjs";
4
4
  //#region src/workflows/shared/lenses.ts
5
5
  const NO_FINDINGS = {
6
6
  notes: [],
@@ -217,7 +217,7 @@ const runTail = (finish, options) => scope("build", async () => {
217
217
  return finish(escalations, NO_FINDINGS);
218
218
  });
219
219
  /**
220
- * The review and fix workflows' human review since `base`: a sign-off closes
220
+ * The fix workflow's human review since `base`: a sign-off closes
221
221
  * `.gtd/REVIEW.md`; a note leaves it, with the reviewer's own version as
222
222
  * `REVIEW_ORIG`, for the next `fix` to read — never a re-plan.
223
223
  */
@@ -237,7 +237,7 @@ const handOffReview = (base, escalations, guard) => reviewToHandOff(base, {
237
237
  unparsed: "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.",
238
238
  deleted: "gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note to hand to fix."
239
239
  });
240
- /** The tail the review and fix workflows run: ends at the hand-off review since `base`. */
240
+ /** The tail the fix workflow runs: ends at the hand-off review since `base`. */
241
241
  const handOffTail = (fixFirst, base, guard) => tail((escalations) => handOffReview(base, escalations, guard), {
242
242
  fixFirst,
243
243
  guard
@@ -1,6 +1,6 @@
1
1
  import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
2
- import { Ct as envDefaults, Et as flagReferences, Ot as registryEntry, S as coder, St as defaults, i as skills, kt as requireFlag, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-COEMFEpy.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-C_xn8U7U.mjs";
2
+ import { At as registryEntry, C as coder, Ot as flagReferences, Tt as envDefaults, i as skills, jt as requireFlag, n as summary, o as flagShipped, r as access, t as steering, wt as defaults } from "../chunks/reserved-DJk_6-ju.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-5w5mxGTq.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 () => {