@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +17 -13
- package/dist/chunks/{reserved-COEMFEpy.mjs → reserved-DJk_6-ju.mjs} +41 -1
- package/dist/chunks/review-X7Jfz0dY.mjs +249 -0
- package/dist/chunks/{tail-C_xn8U7U.mjs → tail-5w5mxGTq.mjs} +3 -3
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +6 -278
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +27 -7
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +74 -44
- package/package.json +1 -1
- package/src/steering/FORMATS.md +4 -4
- package/src/workflows/feature/coverage.ts +1 -1
- package/src/workflows/feature/feature.test.ts +8 -0
- package/src/workflows/feature/feature.ts +2 -2
- package/src/workflows/feature/index.ts +1 -1
- package/src/workflows/feature/refactor.ts +1 -1
- package/src/workflows/feature/seam.ts +1 -1
- package/src/workflows/feature/steps.test.ts +2 -64
- package/src/workflows/feature/steps.ts +5 -41
- package/src/workflows/fix/fix.feature +20 -21
- package/src/workflows/index.ts +1 -1
- package/src/workflows/review/review-door.feature +149 -0
- package/src/workflows/review/review.test.ts +1 -73
- package/src/workflows/review/review.ts +34 -6
- package/src/workflows/{feature → shared}/review.test.ts +4 -8
- package/src/workflows/{feature → shared}/review.ts +13 -10
- package/src/workflows/shared/skills.test.ts +2 -0
- package/src/workflows/shared/skills.ts +2 -0
- package/src/workflows/shared/steps.test.ts +64 -2
- package/src/workflows/shared/steps.ts +40 -1
- package/src/workflows/shared/tail.ts +2 -2
- package/src/workflows/shared/text.ts +11 -0
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`.
|
|
592
|
-
|
|
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
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
|
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 {
|
|
3
|
-
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
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 () => {
|