@pmelab/gtd 42.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 +96 -48
- package/claude/hooks/sketch.ts +1 -1
- package/dist/chunks/{reserved-D7MNY9QJ.mjs → reserved-DJk_6-ju.mjs} +75 -46
- package/dist/chunks/review-X7Jfz0dY.mjs +249 -0
- package/dist/chunks/{steering-oCDdQlpP.mjs → steering-D4LUQ-av.mjs} +95 -12
- package/dist/chunks/{tail-D2o2h5cc.mjs → tail-5w5mxGTq.mjs} +3 -3
- package/dist/gtd.bundle.mjs +488 -30
- package/dist/steering.mjs +2 -2
- package/dist/testkit.mjs +447 -27
- package/dist/web/main.js +42 -13
- package/dist/workflow-kit.mjs +61 -25
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +7 -290
- package/dist/workflows/fix.mjs +3 -3
- package/dist/workflows/review.mjs +27 -7
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +287 -161
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +33 -1
- package/src/cli/Cli.ts +70 -6
- package/src/cli/History.ts +15 -0
- package/src/cli/Judges.test.ts +373 -0
- package/src/cli/Judges.ts +624 -0
- package/src/cli/Metrics.ts +3 -16
- package/src/cli/Program.test.ts +3 -1
- package/src/cli/Program.ts +9 -0
- package/src/steering/FORMATS.md +4 -4
- package/src/steering/index.ts +12 -1
- package/src/steering/review.test.ts +107 -0
- package/src/steering/review.ts +159 -9
- package/src/testkit/EmittedScriptRecognizer.ts +0 -14
- package/src/testkit/steps.ts +4 -0
- package/src/workflow-kit/review.test.ts +170 -26
- package/src/workflow-kit/review.ts +95 -19
- 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/lens-review.feature +16 -4
- package/src/workflows/feature/refactor.ts +1 -1
- package/src/workflows/feature/review-answers.feature +179 -0
- package/src/workflows/feature/seam.ts +1 -1
- package/src/workflows/feature/steps.test.ts +2 -76
- package/src/workflows/feature/steps.ts +5 -48
- 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/shared/__fixtures__/feature-collecting-prompt.txt +6 -5
- package/src/workflows/shared/access.test.ts +2 -11
- package/src/workflows/shared/access.ts +0 -2
- package/src/workflows/{feature → shared}/review.test.ts +9 -61
- package/src/workflows/{feature → shared}/review.ts +40 -48
- 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.test.ts +40 -89
- package/src/workflows/shared/text.ts +63 -54
package/dist/web/main.js
CHANGED
|
@@ -29844,8 +29844,8 @@ const eolOf = (content) => content.includes("\r\n") ? "\r\n" : "\n";
|
|
|
29844
29844
|
const PREFIX_RE = /^(H|A):\s*/;
|
|
29845
29845
|
const lineOf = (node, which) => toLspPosition(node.position[which]).line;
|
|
29846
29846
|
/** The item's own blocks joined — never the item node's span, which can run past its last line into the next bullet's marker. */
|
|
29847
|
-
const itemText = (content, item) => item.children.map((child) => sourceText(content, child)).join(" ").trim();
|
|
29848
|
-
const prefixOf = (content, item) => PREFIX_RE.exec(itemText(content, item));
|
|
29847
|
+
const itemText$1 = (content, item) => item.children.map((child) => sourceText(content, child)).join(" ").trim();
|
|
29848
|
+
const prefixOf = (content, item) => PREFIX_RE.exec(itemText$1(content, item));
|
|
29849
29849
|
/** A thread is a definition whose body OPENS with a list whose first item carries a prefix — anything else stays a one-shot footnote. */
|
|
29850
29850
|
const isThreadBody = (content, children) => {
|
|
29851
29851
|
const first = children[0];
|
|
@@ -29865,7 +29865,7 @@ const entryOf = (content, item, previous, report) => {
|
|
|
29865
29865
|
return;
|
|
29866
29866
|
}
|
|
29867
29867
|
const author = match[1] === "H" ? "me" : "agent";
|
|
29868
|
-
const text = itemText(content, item).slice(match[0].length).trim();
|
|
29868
|
+
const text = itemText$1(content, item).slice(match[0].length).trim();
|
|
29869
29869
|
if (text === "") report(`empty "${match[1]}:" entry — write your own words there`, start, end);
|
|
29870
29870
|
if (previous === void 0 && author === "agent") report(`the agent never starts a thread — the first entry must be "H:"`, start, end);
|
|
29871
29871
|
else if (previous === author) report(`two consecutive "${match[1]}:" entries — entries must alternate`, start, end);
|
|
@@ -31565,8 +31565,6 @@ const qaDescriptor = {
|
|
|
31565
31565
|
unansweredQuestions,
|
|
31566
31566
|
openThreads: openThreadsOf
|
|
31567
31567
|
};
|
|
31568
|
-
//#endregion
|
|
31569
|
-
//#region src/steering/review.ts
|
|
31570
31568
|
/**
|
|
31571
31569
|
* The canonical sample. Every detail here is load-bearing for the formatter
|
|
31572
31570
|
* round-trip: both footnote bodies exceed 80 characters so a reflow actually
|
|
@@ -31833,16 +31831,42 @@ const parseHunk = (content, lines, title, item) => {
|
|
|
31833
31831
|
errors: [hunkSecondPointerFinding(content, lines, item, token, sourceLine, title, file), hunkLineWithoutRangeFinding(content, item, token, sourceLine, title, file)].filter((e) => e !== void 0)
|
|
31834
31832
|
};
|
|
31835
31833
|
};
|
|
31834
|
+
const itemText = (content, item) => item.children.map((c) => sourceText(content, c)).join(" ").replace(/^\[[ xX]\]\s*/, "").trim();
|
|
31835
|
+
/**
|
|
31836
|
+
* The task list right after the `Tick exactly one:` paragraph, with that
|
|
31837
|
+
* paragraph's index. Read by position, never by label, so an edited label or
|
|
31838
|
+
* a free text opening with `./path` is still an answer, not a pointer. Any
|
|
31839
|
+
* count but three boxes is kept, and reads as unanswered.
|
|
31840
|
+
*/
|
|
31841
|
+
const answerListOf = (content, body) => {
|
|
31842
|
+
const lead = body.findIndex((n, i) => n.type === "paragraph" && sourceText(content, n) === "Tick exactly one:" && body[i + 1]?.type === "list" && body[i + 1].children.every((item) => item.checked != null));
|
|
31843
|
+
if (lead === -1) return void 0;
|
|
31844
|
+
const list = body[lead + 1];
|
|
31845
|
+
const last = list.children.length - 1;
|
|
31846
|
+
return {
|
|
31847
|
+
lead,
|
|
31848
|
+
list,
|
|
31849
|
+
options: list.children.map((item, i) => {
|
|
31850
|
+
const text = itemText(content, item);
|
|
31851
|
+
return {
|
|
31852
|
+
checked: item.checked === true,
|
|
31853
|
+
text: i === last && text.toLowerCase() === "_your answer_".toLowerCase() ? "" : text
|
|
31854
|
+
};
|
|
31855
|
+
})
|
|
31856
|
+
};
|
|
31857
|
+
};
|
|
31836
31858
|
/** Splits one chunk's body nodes into its file pointers (hunk pointers are task items, collected recursively at ANY nesting depth via `taskItems` — a nested hunk is the same kind of hunk as a top-level one) and description prose. The description is the chunk's own leading run and never a node that contains a hunk pointer: it stops at the first node whose `taskItems` include a real pointer (at any depth — a blockquote wrapping a pointer list stops it just as a top-level list would). Every node kind in what's left before that point contributes — a `footnoteDefinition` is the one exception, excluded by `blockNodesOfRun` itself (it's a note, never document content). */
|
|
31837
31859
|
const parseChunkBody = (content, lines, title, body) => {
|
|
31838
|
-
const
|
|
31839
|
-
const
|
|
31860
|
+
const answer = answerListOf(content, body);
|
|
31861
|
+
const isAnswer = (n) => answer !== void 0 && (n === answer.list || n === body[answer.lead]);
|
|
31862
|
+
const firstPointerIndex = body.findIndex((n) => !isAnswer(n) && taskItems(n).some((item) => hasPointerToken(content, item)));
|
|
31863
|
+
const leadingRun = (firstPointerIndex === -1 ? body : body.slice(0, firstPointerIndex)).filter((n) => !isAnswer(n));
|
|
31840
31864
|
const descriptionNodes = blockNodesOfRun(content, leadingRun);
|
|
31841
31865
|
const descriptionInline = collapseImages(blockRunInline(content, leadingRun));
|
|
31842
31866
|
const description = leadingRun.filter((n) => n.type !== "footnoteDefinition").map((n) => sourceText(content, n)).join(" ").trim();
|
|
31843
31867
|
const files = [];
|
|
31844
31868
|
const errors = [];
|
|
31845
|
-
for (const item of body.flatMap((n) => taskItems(n))) {
|
|
31869
|
+
for (const item of body.filter((n) => !isAnswer(n)).flatMap((n) => taskItems(n))) {
|
|
31846
31870
|
const parsed = parseHunk(content, lines, title, item);
|
|
31847
31871
|
if (!parsed) continue;
|
|
31848
31872
|
files.push(parsed.file);
|
|
@@ -31853,7 +31877,9 @@ const parseChunkBody = (content, lines, title, body) => {
|
|
|
31853
31877
|
descriptionNodes,
|
|
31854
31878
|
descriptionInline,
|
|
31855
31879
|
files,
|
|
31856
|
-
errors
|
|
31880
|
+
errors,
|
|
31881
|
+
...answer === void 0 ? {} : { answers: answer.options },
|
|
31882
|
+
boxed: body.some((n) => !isAnswer(n) && taskItems(n).length > 0)
|
|
31857
31883
|
};
|
|
31858
31884
|
};
|
|
31859
31885
|
/** Splits the tree's top-level nodes into consecutive `##` chunk blocks — a `## ` line found only inside a fence never becomes a heading node at all, so it's never mistaken for a chunk. */
|
|
@@ -31889,9 +31915,9 @@ const parseChangesets = (tree, content, lines) => {
|
|
|
31889
31915
|
const errors = [];
|
|
31890
31916
|
for (const { heading, headingLine, body } of splitChunks(tree)) {
|
|
31891
31917
|
const title = headingText$1(content, heading);
|
|
31892
|
-
const { description, descriptionNodes, descriptionInline, files, errors: bodyErrors } = parseChunkBody(content, lines, title, body);
|
|
31918
|
+
const { description, descriptionNodes, descriptionInline, files, answers, boxed, errors: bodyErrors } = parseChunkBody(content, lines, title, body);
|
|
31893
31919
|
errors.push(...bodyErrors);
|
|
31894
|
-
if (files.length === 0 &&
|
|
31920
|
+
if (files.length === 0 && boxed) errors.push({
|
|
31895
31921
|
message: `Chunk "${title}" has no file pointers`,
|
|
31896
31922
|
line: headingLine,
|
|
31897
31923
|
range: nodeRange(heading)
|
|
@@ -31902,7 +31928,8 @@ const parseChangesets = (tree, content, lines) => {
|
|
|
31902
31928
|
descriptionNodes,
|
|
31903
31929
|
descriptionInline,
|
|
31904
31930
|
files,
|
|
31905
|
-
headingLine
|
|
31931
|
+
headingLine,
|
|
31932
|
+
...answers === void 0 ? {} : { answers }
|
|
31906
31933
|
});
|
|
31907
31934
|
}
|
|
31908
31935
|
if (changesets.length === 0) errors.push({ message: "REVIEW.md has no '##' chunks" });
|
|
@@ -31983,7 +32010,9 @@ const isHunkPointerItem = (content, item) => {
|
|
|
31983
32010
|
* clear, so a caller can skip the write and leave the mtime untouched.
|
|
31984
32011
|
*/
|
|
31985
32012
|
const reviewClearTicks = (content) => {
|
|
31986
|
-
const
|
|
32013
|
+
const tree = parseMarkdown(content);
|
|
32014
|
+
const answers = new Set(splitChunks(tree).flatMap(({ body }) => answerListOf(content, body)?.list.children ?? []));
|
|
32015
|
+
const offsets = taskItems(tree).filter((item) => item.checked === true && !answers.has(item) && isHunkPointerItem(content, item)).map((item) => checkboxOffset(content, item)).filter((o) => o !== void 0).sort((a, b) => a - b);
|
|
31987
32016
|
if (offsets.length === 0) return content;
|
|
31988
32017
|
let result = "";
|
|
31989
32018
|
let cursor = 0;
|
package/dist/workflow-kit.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { changes, changesSince, hasThreadFor, head, read, refuse, requireReplies, requireThreadsClosed, scope } from "./flows.mjs";
|
|
2
|
-
import { c as stripCodeThreads,
|
|
1
|
+
import { changes, changesSince, hasThreadFor, head, mark, read, refuse, requireReplies, requireThreadsClosed, scope } from "./flows.mjs";
|
|
2
|
+
import { _ as unansweredNotes, c as stripCodeThreads, g as reviewRisks, h as reviewNotes, m as reviewDecisions, w as parseThreads } from "./chunks/steering-D4LUQ-av.mjs";
|
|
3
3
|
//#region src/workflow-kit/health.ts
|
|
4
4
|
/** Hand a red suite that repeated attempts could not fix to a person, until `limit` rounds are spent. */
|
|
5
5
|
const escalation = async (count, steps) => {
|
|
@@ -172,6 +172,13 @@ const collect = async (p, capture, round, requested) => {
|
|
|
172
172
|
}
|
|
173
173
|
return refuse(p.refusals.unmatched);
|
|
174
174
|
};
|
|
175
|
+
/** Refuse while a note's answer boxes hold anything but exactly one tick, or a note's boxes were deleted. */
|
|
176
|
+
const requireDecisions = (file, reviewed) => {
|
|
177
|
+
const open = unansweredNotes(read(file) ?? "", changesSince(reviewed).get(file)?.before);
|
|
178
|
+
if (open.length === 0) return;
|
|
179
|
+
const list = open.map((n) => ` - ${file}${n.line > 0 ? `:${n.line}` : ""}: ${n.title} — ${n.reason}`).join("\n");
|
|
180
|
+
refuse(`gtd land: answer-completeness: ${open.length} note(s) in ${file} not answered — tick exactly one box per note: fix as suggested, won't fix, or your own answer written over _your answer_:\n${list}`);
|
|
181
|
+
};
|
|
175
182
|
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
176
183
|
/**
|
|
177
184
|
* A review with ticks and THREAD footnotes (definition plus markers) removed:
|
|
@@ -191,7 +198,7 @@ const notesOf = (text) => {
|
|
|
191
198
|
* commit of the last collecting turn, `"missing"` when the round left no
|
|
192
199
|
* review, or `undefined` when no such turn ran.
|
|
193
200
|
*/
|
|
194
|
-
const converse = async (p, base, collectedBefore) => {
|
|
201
|
+
const converse = async (p, base, reviewed, collectedBefore) => {
|
|
195
202
|
let collectedAt = collectedBefore;
|
|
196
203
|
for (;;) {
|
|
197
204
|
await p.awaitReview(base);
|
|
@@ -201,24 +208,47 @@ const converse = async (p, base, collectedBefore) => {
|
|
|
201
208
|
return "missing";
|
|
202
209
|
}
|
|
203
210
|
requireThreadsClosed(p.file);
|
|
204
|
-
if (!hasThreadFor("agent", p.file))
|
|
211
|
+
if (!hasThreadFor("agent", p.file)) {
|
|
212
|
+
requireDecisions(p.file, reviewed);
|
|
213
|
+
return collectedAt;
|
|
214
|
+
}
|
|
205
215
|
await p.collecting(p.captures.edits(head()), "reply");
|
|
206
216
|
collectedAt = head();
|
|
207
217
|
requireReplies(p.file);
|
|
208
|
-
if (!hasThreadFor("human", p.file)) return collectedAt;
|
|
218
|
+
if (!hasThreadFor("human", p.file) && unansweredNotes(read(p.file) ?? "", changesSince(reviewed).get(p.file)?.before).length === 0) return collectedAt;
|
|
209
219
|
}
|
|
210
220
|
};
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
221
|
+
const decisionNote = (d, id, text) => ({
|
|
222
|
+
id,
|
|
223
|
+
kind: "chunk",
|
|
224
|
+
anchor: d.anchor,
|
|
225
|
+
before: d.body,
|
|
226
|
+
text,
|
|
227
|
+
forced: /^!(\s|$)/.test(text)
|
|
228
|
+
});
|
|
229
|
+
/** One `Gtd-Mark` value per line: a control character would make mark() throw on every replay. */
|
|
230
|
+
const oneLine = (text) => text.replace(/[\s\x00-\x1f\x7f-\x9f\u2028\u2029]+/g, " ").trim() || "untitled";
|
|
231
|
+
/** Tell the policy, and mark each won't-fix on the gate's commit. */
|
|
232
|
+
const record = (p, decisions) => {
|
|
233
|
+
p.decided?.(decisions);
|
|
234
|
+
for (const d of decisions) if (d.choice === "wontfix") mark("wontfix", oneLine(d.anchor));
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Judge every note, then run each verdict's route: answers, then nit fixes,
|
|
238
|
+
* then the edits' lap. Of the human's `decisions`, a fix joins the nit batch
|
|
239
|
+
* unjudged, an own answer is judged like a note, and a won't-fix is dropped
|
|
240
|
+
* with a `Gtd-Mark` on the gate's commit.
|
|
241
|
+
*/
|
|
242
|
+
const routeNotes = async (p, notes, r, decisions = []) => {
|
|
243
|
+
record(p, decisions);
|
|
244
|
+
const of = (c) => decisions.filter((d) => d.choice === c);
|
|
245
|
+
const fixes = of("fix").map((d, i) => decisionNote(d, `fix-${i + 1}`, d.body));
|
|
246
|
+
const judged = [...notes, ...of("answer").map((d, i) => decisionNote(d, `answer-${i + 1}`, d.text))];
|
|
247
|
+
const verdicts = judged.length === 0 ? /* @__PURE__ */ new Map() : await p.triage(judged);
|
|
248
|
+
const ofVerdict = (v) => judged.filter((n) => (verdicts.get(n.id) ?? "edit") === v);
|
|
249
|
+
const [edits, questions] = [ofVerdict("edit"), ofVerdict("question")];
|
|
250
|
+
const nits = [...ofVerdict("nit"), ...fixes];
|
|
251
|
+
if (edits.length + questions.length + nits.length === 0) return r.unfolded();
|
|
222
252
|
let answeredAt;
|
|
223
253
|
if (questions.length > 0) {
|
|
224
254
|
await p.answerQuestions(questions);
|
|
@@ -228,7 +258,6 @@ const routeNotes = async (p, notes, r) => {
|
|
|
228
258
|
await p.fixNits(nits);
|
|
229
259
|
await p.keepGreen();
|
|
230
260
|
}
|
|
231
|
-
if (deferred.length > 0) await p.defer(deferred);
|
|
232
261
|
if (edits.length > 0) {
|
|
233
262
|
await r.close();
|
|
234
263
|
return r.outcome(await collect(p, p.captures.editNotes(r.round, edits, answeredAt), "triaged", {
|
|
@@ -250,8 +279,8 @@ const routeNotes = async (p, notes, r) => {
|
|
|
250
279
|
};
|
|
251
280
|
/** A code edit, not a thread-only change: lines that only add, answer or remove code threads don't count. */
|
|
252
281
|
const isCodeEdit = (p, c) => p.isCode(c.path) && stripCodeThreads(c.path, c.before ?? "") !== stripCodeThreads(c.path, c.after ?? "");
|
|
253
|
-
/** Whether
|
|
254
|
-
const notedSince = (p, commit) => changesSince(commit).some((c) => c.path === p.file && notesOf(c.before ?? "") !== notesOf(read(p.file) ?? ""));
|
|
282
|
+
/** Whether a note or an answer, not just thread edits and pointer ticks, landed in the review file since `commit`. */
|
|
283
|
+
const notedSince = (p, commit) => changesSince(commit).some((c) => c.path === p.file && (notesOf(c.before ?? "") !== notesOf(read(p.file) ?? "") || reviewDecisions(c.before ?? "", read(p.file) ?? "").length > 0));
|
|
255
284
|
/**
|
|
256
285
|
* What the in-loop collecting turn left: `folded` when it wrote `feedback`
|
|
257
286
|
* (those folds need their lap whatever is judged later), `settled` when only
|
|
@@ -265,9 +294,9 @@ const settledAfter = (p, collectedAt) => ({
|
|
|
265
294
|
folded: collectedAt !== void 0 && read(p.feedback) !== void 0,
|
|
266
295
|
settled: collectedAt !== void 0 && changesSince(collectedAt).every((c) => !isCodeEdit(p, c)) && !notedSince(p, collectedAt)
|
|
267
296
|
});
|
|
268
|
-
const finishNotes = async (p, before, reopened, ctx) => {
|
|
269
|
-
const notes = reviewNotes(before, read(p.file) ?? "");
|
|
270
|
-
if (notes.length > 0) return routeNotes(p, notes, ctx);
|
|
297
|
+
const finishNotes = async (p, before, decisions, reopened, ctx) => {
|
|
298
|
+
const notes = reviewNotes(before ?? "", read(p.file) ?? "");
|
|
299
|
+
if (notes.length + decisions.length > 0) return routeNotes(p, notes, ctx, decisions);
|
|
271
300
|
if (reopened) return ctx.unfolded();
|
|
272
301
|
await ctx.close();
|
|
273
302
|
return ctx.outcome(await collect(p, p.captures.notes(ctx.round), "notes"));
|
|
@@ -277,6 +306,11 @@ const toOutcome = (verdict, rest) => verdict === "signoff" ? { verdict } : {
|
|
|
277
306
|
...rest
|
|
278
307
|
};
|
|
279
308
|
const isReopened = (p, r) => r.settled && !r.folded && p.refusals.dropped !== void 0;
|
|
309
|
+
/** A carried answer is unchanged, never routed twice; one ticked beside a thread still routes once the thread has settled. */
|
|
310
|
+
const decisionsSince = (p, since) => {
|
|
311
|
+
const after = read(p.file) ?? "";
|
|
312
|
+
return reviewDecisions(since.get(p.file)?.before ?? after, after);
|
|
313
|
+
};
|
|
280
314
|
const finish = async (p, reviewed, collectedAt) => {
|
|
281
315
|
const round = head();
|
|
282
316
|
const since = changesSince(reviewed);
|
|
@@ -297,15 +331,17 @@ const finish = async (p, reviewed, collectedAt) => {
|
|
|
297
331
|
settled,
|
|
298
332
|
folded
|
|
299
333
|
});
|
|
300
|
-
|
|
334
|
+
const decisions = decisionsSince(p, since);
|
|
335
|
+
if (settled && !reopened && decisions.length === 0 || edited.length === 0 && !noted) return unfolded();
|
|
301
336
|
if (edited.length > 0) {
|
|
337
|
+
record(p, decisions);
|
|
302
338
|
await close();
|
|
303
339
|
return outcome(await collect(p, p.captures.edits(round, reopened ? reviewed : void 0), "edits", {
|
|
304
340
|
notes: [],
|
|
305
341
|
paths: edited.map((c) => c.path)
|
|
306
342
|
}));
|
|
307
343
|
}
|
|
308
|
-
return finishNotes(p, since.get(p.file)?.before
|
|
344
|
+
return finishNotes(p, since.get(p.file)?.before, decisions, reopened, {
|
|
309
345
|
round,
|
|
310
346
|
close,
|
|
311
347
|
outcome,
|
|
@@ -335,7 +371,7 @@ const review = async (base, p) => {
|
|
|
335
371
|
let reviewed = head();
|
|
336
372
|
let collectedAt;
|
|
337
373
|
for (;;) {
|
|
338
|
-
collectedAt = await converse(p, base, collectedAt);
|
|
374
|
+
collectedAt = await converse(p, base, reviewed, collectedAt);
|
|
339
375
|
if (collectedAt === "missing") break;
|
|
340
376
|
const result = await finish(p, reviewed, collectedAt);
|
|
341
377
|
if (!("next" in result)) return result;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
|
|
2
|
-
import { At as registryEntry,
|
|
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 () => {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { agent, answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches,
|
|
2
|
-
import { lensEntriesOf
|
|
3
|
-
import { $ as
|
|
4
|
-
import { a as refusePendingHandOff,
|
|
1
|
+
import { agent, answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireThreadsClosed, revertScript, run, scope, slice, start, vars, wrote } from "../flows.mjs";
|
|
2
|
+
import { lensEntriesOf } from "../workflow-kit.mjs";
|
|
3
|
+
import { $ as naiveApplyFailure, B as coverageMessage, C as coder, Ct as unwindFailure, E as fixNits, Et as testFileGlobs, G as designSystem, H as coveringPrompt, I as builderSystem, K as designTriagePrompt, L as clearCriteriaMessage, N as agentWithSkills, P as buildReviewCollectingPrompt, Q as implementFixPrompt, R as clearCriteriaQuestion, St as unwindFailedMessage, T as fixCheck, Tt as envDefaults, U as criterionLine, V as coverageQuestion, W as designGateAnswerMessage, X as idleMessage, Z as implementBuildingPrompt, _ as REFACTOR_NOTE, _t as seamByHandMessage, a as flagModeOn, at as qualityUnresolvedMessage, b as SLICES, bt as startGateBlockedMessage, ct as refactorFixPrompt, et as naivePassPrompt, f as healthy, g as REFACTOR, gt as reviewerSystem, h as QUALITY, i as skills, k as planner, lt as refactorNotePrompt, m as HANDOFF, n as summary, nt as oversizedMessage, ot as refactorBuildingPrompt, p as FEEDBACK, r as access, s as shipped, st as refactorDroppedRefusal, t as steering, tt as normaliseCriteriaScript, u as gate, ut as refactorRules, v as REQUIREMENTS, vt as seamSteerMessage, w as collecting, wt as defaults, x as answerReviewQuestions, xt as uncoveredMessage, yt as slicingPrompt } from "../chunks/reserved-DJk_6-ju.mjs";
|
|
4
|
+
import { a as refusePendingHandOff, o as tail } from "../chunks/tail-5w5mxGTq.mjs";
|
|
5
|
+
import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-X7Jfz0dY.mjs";
|
|
5
6
|
import { posix } from "node:path";
|
|
6
7
|
//#region src/workflows/shared/fences.ts
|
|
7
8
|
const markFenced = (text) => {
|
|
@@ -219,36 +220,6 @@ const answerProductQuestions = () => human("gate.answer", {
|
|
|
219
220
|
mode: "qa",
|
|
220
221
|
acceptClean: true
|
|
221
222
|
});
|
|
222
|
-
/** Answer every `question` note inline in `.gtd/REVIEW.md`. */
|
|
223
|
-
const answerReviewQuestions = (notes) => agentWithSkills("review.answer-review-questions", buildReviewAnswerQuestionsPrompt(notes), {
|
|
224
|
-
label: "Answering your questions",
|
|
225
|
-
file: REVIEW,
|
|
226
|
-
mode: "review",
|
|
227
|
-
model: planner(),
|
|
228
|
-
system: reviewerSystem()
|
|
229
|
-
});
|
|
230
|
-
/** Fix every `nit` note in one turn. */
|
|
231
|
-
const fixNits = (notes, access, rules) => {
|
|
232
|
-
const fixing = (name) => agentWithSkills(name, buildReviewFixNitsPrompt(notes, rules), {
|
|
233
|
-
label: "Fixing your nits",
|
|
234
|
-
file: REVIEW,
|
|
235
|
-
model: planner(),
|
|
236
|
-
system: reviewerSystem()
|
|
237
|
-
});
|
|
238
|
-
return access === void 0 ? fixing("review.fix.nits.fixing") : scope({
|
|
239
|
-
name: "review.fix.nits",
|
|
240
|
-
access
|
|
241
|
-
}, () => fixing("fixing"));
|
|
242
|
-
};
|
|
243
|
-
/** Turn the review round `capture` describes into `.gtd/REQUIREMENTS.md`, or change nothing. */
|
|
244
|
-
const collecting = (capture) => agentWithSkills("review.collecting", buildReviewCollectingPrompt(capture), {
|
|
245
|
-
label: "Collecting your feedback",
|
|
246
|
-
file: REQUIREMENTS,
|
|
247
|
-
mode: "qa",
|
|
248
|
-
model: planner(),
|
|
249
|
-
system: reviewerSystem(),
|
|
250
|
-
allowEmpty: true
|
|
251
|
-
});
|
|
252
223
|
/** Turn the refactor review round `capture` describes into `.gtd/REFACTOR.md`, or change nothing. */
|
|
253
224
|
const refactorCollecting = (capture, round, strays) => agentWithSkills("review.collecting", buildReviewCollectingPrompt(capture, {
|
|
254
225
|
strays,
|
|
@@ -260,11 +231,6 @@ const refactorCollecting = (capture, round, strays) => agentWithSkills("review.c
|
|
|
260
231
|
system: reviewerSystem(),
|
|
261
232
|
allowEmpty: true
|
|
262
233
|
});
|
|
263
|
-
/** `build.review.defer`: append `notes` to `.gtd/TODO.md` as one section; the step's landing is the commit. */
|
|
264
|
-
const deferNotes = (notes, round) => run("review.defer", deferNotesScript(notes, round), {
|
|
265
|
-
label: "Deferring could-notes to TODO.md",
|
|
266
|
-
file: ".gtd/TODO.md"
|
|
267
|
-
});
|
|
268
234
|
//#endregion
|
|
269
235
|
//#region src/workflows/feature/planning.ts
|
|
270
236
|
/**
|
|
@@ -291,255 +257,6 @@ const design = (base, guard) => scope("design", async () => {
|
|
|
291
257
|
} while (await noteGate(REQUIREMENTS, answerProductQuestions));
|
|
292
258
|
});
|
|
293
259
|
//#endregion
|
|
294
|
-
//#region src/workflows/feature/review.ts
|
|
295
|
-
const VERDICTS = [
|
|
296
|
-
"edit",
|
|
297
|
-
"question",
|
|
298
|
-
"nit",
|
|
299
|
-
"praise"
|
|
300
|
-
];
|
|
301
|
-
/** One four-way choice per note the human added to `.gtd/REVIEW.md`. */
|
|
302
|
-
const verdictQuestion = (note) => ({
|
|
303
|
-
id: note.id,
|
|
304
|
-
primitive: "choice",
|
|
305
|
-
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.`,
|
|
306
|
-
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."
|
|
307
|
-
});
|
|
308
|
-
const SEVERITY_ANSWERS = {
|
|
309
|
-
security: ["yes", "no"],
|
|
310
|
-
reversibility: ["one-way", "two-way"],
|
|
311
|
-
blastRadius: [
|
|
312
|
-
"local",
|
|
313
|
-
"module",
|
|
314
|
-
"contract"
|
|
315
|
-
],
|
|
316
|
-
newConcept: ["yes", "no"]
|
|
317
|
-
};
|
|
318
|
-
/**
|
|
319
|
-
* Deferring is the risky direction: anything unanswered, unrecognised, cut or
|
|
320
|
-
* below `minP` is `critical`, never `could`.
|
|
321
|
-
*/
|
|
322
|
-
const severityOf = (note, judgment, minP) => {
|
|
323
|
-
const ans = (q) => judgment.answers[`${note.id}.${q}`];
|
|
324
|
-
const a = (q) => ans(q)?.answer;
|
|
325
|
-
const rank = () => a("reversibility") === "one-way" ? 0 : a("newConcept") === "yes" ? 1 : 2;
|
|
326
|
-
if (note.forced) return {
|
|
327
|
-
tier: "critical",
|
|
328
|
-
reason: "critical: forced by `!`",
|
|
329
|
-
rank: 2
|
|
330
|
-
};
|
|
331
|
-
if (judgment.truncated.includes(note.id) || Object.entries(SEVERITY_ANSWERS).some(([q, allowed]) => !allowed.includes(a(q) ?? "") || !((ans(q)?.p ?? NaN) >= minP))) return {
|
|
332
|
-
tier: "critical",
|
|
333
|
-
reason: "critical: judge unsure",
|
|
334
|
-
rank: 2
|
|
335
|
-
};
|
|
336
|
-
const critical = [...a("security") === "yes" ? ["security"] : [], ...a("reversibility") === "one-way" ? ["one-way door"] : []];
|
|
337
|
-
if (critical.length > 0) return {
|
|
338
|
-
tier: "critical",
|
|
339
|
-
reason: `critical: ${critical.join(", ")}`,
|
|
340
|
-
rank: rank()
|
|
341
|
-
};
|
|
342
|
-
const should = [...a("blastRadius") === "contract" ? ["contract"] : [], ...a("newConcept") === "yes" ? ["new concept"] : []];
|
|
343
|
-
if (should.length > 0) return {
|
|
344
|
-
tier: "should",
|
|
345
|
-
reason: `should: ${should.join(", ")}`,
|
|
346
|
-
rank: rank()
|
|
347
|
-
};
|
|
348
|
-
return {
|
|
349
|
-
tier: "could",
|
|
350
|
-
reason: "could",
|
|
351
|
-
rank: 2
|
|
352
|
-
};
|
|
353
|
-
};
|
|
354
|
-
const severityQuestions = (note) => [
|
|
355
|
-
{
|
|
356
|
-
id: `${note.id}.security`,
|
|
357
|
-
primitive: "noul",
|
|
358
|
-
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?`,
|
|
359
|
-
criteria: "yes: it changes or could weaken a security property. no: it does not."
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
id: `${note.id}.reversibility`,
|
|
363
|
-
primitive: "choice",
|
|
364
|
-
instructions: `Once the change in note "${note.id}" ships, can it be undone without breaking anyone who already depends on it?`,
|
|
365
|
-
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."
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
id: `${note.id}.blastRadius`,
|
|
369
|
-
primitive: "choice",
|
|
370
|
-
instructions: `How far does the change in note "${note.id}" reach?`,
|
|
371
|
-
criteria: "local: one function or file. module: a whole module or several files. contract: a public interface other code or users depend on."
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
id: `${note.id}.newConcept`,
|
|
375
|
-
primitive: "noul",
|
|
376
|
-
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.`,
|
|
377
|
-
criteria: "yes: a genuinely new concept. no: reuses or extends an existing one."
|
|
378
|
-
}
|
|
379
|
-
];
|
|
380
|
-
/**
|
|
381
|
-
* One judge rest: a verdict per unforced note (a forced note is asked
|
|
382
|
-
* nothing) plus four severity answers. Dismissing a note is the risky
|
|
383
|
-
* direction, so only a confident non-`edit` verdict on uncut evidence counts;
|
|
384
|
-
* a cut, unanswered or below-floor note is an `edit`. Severity answers on a
|
|
385
|
-
* non-`edit` note are ignored. A confident `could` edit becomes `defer`.
|
|
386
|
-
*/
|
|
387
|
-
const triage$1 = (severities) => async (notes) => {
|
|
388
|
-
const asked = notes.filter((n) => !n.forced);
|
|
389
|
-
const judgment = asked.length === 0 ? {
|
|
390
|
-
answers: {},
|
|
391
|
-
truncated: []
|
|
392
|
-
} : await judge("review.triage", {
|
|
393
|
-
questions: asked.flatMap((n) => [verdictQuestion(n), ...severityQuestions(n)]),
|
|
394
|
-
evidence: Object.fromEntries(asked.map((n) => [n.id, `Anchor: ${n.anchor}\nReviewer's text: ${n.before}\nHuman's text: ${n.text}`])),
|
|
395
|
-
message: buildReviewTriageMessage(),
|
|
396
|
-
label: "Judging each note"
|
|
397
|
-
});
|
|
398
|
-
const { answers, truncated } = judgment;
|
|
399
|
-
const minP = numeric(vars.reviewNoteActionable, Infinity);
|
|
400
|
-
const severityMinP = numeric(vars.reviewSeverityMinP, Infinity);
|
|
401
|
-
severities.clear();
|
|
402
|
-
return new Map(notes.map((n) => {
|
|
403
|
-
const verdict = n.forced ? void 0 : VERDICTS.find((v) => v !== "edit" && answered(answers[n.id], v, minP));
|
|
404
|
-
if (verdict !== void 0 && !truncated.includes(n.id)) return [n.id, verdict];
|
|
405
|
-
const severity = severityOf(n, judgment, severityMinP);
|
|
406
|
-
severities.set(n.id, severity);
|
|
407
|
-
return [n.id, severity.tier === "could" ? "defer" : "edit"];
|
|
408
|
-
}));
|
|
409
|
-
};
|
|
410
|
-
const isCode$1 = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
|
|
411
|
-
/**
|
|
412
|
-
* gtd writes `.gtd/REVIEW.md` from the quality lap's `report` — no reviewer
|
|
413
|
-
* turn — and removes what the fixer left in `.gtd/QUALITY.md`. A re-review
|
|
414
|
-
* after fixed nits restores the doc as committed at `carry`, answers kept.
|
|
415
|
-
*/
|
|
416
|
-
const composing = (base, report, carry, nitFix) => run("review.composing", carry === void 0 ? writeScript(REVIEW, composedReview(base, {
|
|
417
|
-
...report,
|
|
418
|
-
weakened: removedAssertions(changesSince(base)),
|
|
419
|
-
nitFix
|
|
420
|
-
}), [QUALITY]) : showScript(carry, REVIEW, REVIEW), {
|
|
421
|
-
label: "Composing the review",
|
|
422
|
-
file: REVIEW,
|
|
423
|
-
base
|
|
424
|
-
});
|
|
425
|
-
/**
|
|
426
|
-
* The quality lap's notes reach a human over everything since `base`, who
|
|
427
|
-
* signs off or comments, and each note is judged: edits go to another
|
|
428
|
-
* planning lap, questions are answered in place, nits fixed in one batch,
|
|
429
|
-
* praise dropped.
|
|
430
|
-
*/
|
|
431
|
-
const review = (base, escalations = { rounds: 0 }, options = {}) => {
|
|
432
|
-
const { guard, guardRests, fixAccess, rules } = options;
|
|
433
|
-
const feedback = options.feedback ?? {
|
|
434
|
-
file: ".gtd/REQUIREMENTS.md",
|
|
435
|
-
collecting,
|
|
436
|
-
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"
|
|
437
|
-
};
|
|
438
|
-
const severities = /* @__PURE__ */ new Map();
|
|
439
|
-
let deferred = [];
|
|
440
|
-
let nitFix;
|
|
441
|
-
return review$1(base, {
|
|
442
|
-
file: REVIEW,
|
|
443
|
-
feedback: feedback.file,
|
|
444
|
-
isCode: isCode$1,
|
|
445
|
-
reviewing: (b, carry) => {
|
|
446
|
-
deferred = [];
|
|
447
|
-
const fixedAt = nitFix;
|
|
448
|
-
nitFix = void 0;
|
|
449
|
-
return composing(b, options.report ?? NO_FINDINGS, carry, fixedAt);
|
|
450
|
-
},
|
|
451
|
-
fixRisks: guarded((notes) => fixRisks(notes, fixAccess, rules), guard),
|
|
452
|
-
keepGreen: () => healthy(() => fixCheck(rules), {
|
|
453
|
-
escalations,
|
|
454
|
-
guard,
|
|
455
|
-
guardRests,
|
|
456
|
-
rules
|
|
457
|
-
}),
|
|
458
|
-
awaitReview: (b) => {
|
|
459
|
-
const notes = deferred;
|
|
460
|
-
deferred = [];
|
|
461
|
-
return notes.length > 0 ? awaitReview(b, buildReviewAwaitAfterDeferMessage(b, notes)) : awaitReview(b);
|
|
462
|
-
},
|
|
463
|
-
reviewMissing,
|
|
464
|
-
collecting: feedback.collecting,
|
|
465
|
-
triage: triage$1(severities),
|
|
466
|
-
defer: (notes) => {
|
|
467
|
-
deferred = notes.map((n) => ({
|
|
468
|
-
id: n.id,
|
|
469
|
-
anchor: n.anchor,
|
|
470
|
-
text: n.text,
|
|
471
|
-
label: severities.get(n.id).reason
|
|
472
|
-
}));
|
|
473
|
-
return deferNotes(deferred, head());
|
|
474
|
-
},
|
|
475
|
-
answerQuestions: answerReviewQuestions,
|
|
476
|
-
fixNits: async (notes) => {
|
|
477
|
-
await guarded((n) => fixNits(n, fixAccess, rules), guard)(notes);
|
|
478
|
-
nitFix = head();
|
|
479
|
-
},
|
|
480
|
-
close: closeReview,
|
|
481
|
-
captures: {
|
|
482
|
-
edits: reviewEditsCapture,
|
|
483
|
-
notes: reviewNotesCapture,
|
|
484
|
-
editNotes: (round, edits, answeredAt) => reviewEditNotesCapture(round, edits.map((n, i) => ({
|
|
485
|
-
n,
|
|
486
|
-
i,
|
|
487
|
-
s: severities.get(n.id)
|
|
488
|
-
})).sort((x, y) => Number(x.s?.tier === "should") - Number(y.s?.tier === "should") || (x.s?.rank ?? 2) - (y.s?.rank ?? 2) || x.i - y.i).map(({ n, s }) => ({
|
|
489
|
-
id: n.id,
|
|
490
|
-
anchor: n.anchor,
|
|
491
|
-
text: n.forced ? n.text.replace(/^!\s*/, "") : n.text,
|
|
492
|
-
...s === void 0 ? {} : { label: s.reason }
|
|
493
|
-
})), answeredAt)
|
|
494
|
-
},
|
|
495
|
-
refusals: {
|
|
496
|
-
deleted: "gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note (or edit code) to request changes.",
|
|
497
|
-
unmatched: feedback.unmatched,
|
|
498
|
-
...feedback.dropped === void 0 ? {} : { dropped: feedback.dropped }
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
};
|
|
502
|
-
function buildTail(first, second, ...rest) {
|
|
503
|
-
const options = callForm(first, second, rest);
|
|
504
|
-
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"));
|
|
505
|
-
const [base, form] = options;
|
|
506
|
-
return tail((escalations, report) => review(base, escalations, {
|
|
507
|
-
...form,
|
|
508
|
-
report
|
|
509
|
-
}), {
|
|
510
|
-
...form,
|
|
511
|
-
triage: true
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
const isOptionalBoolean = (value) => value == null || typeof value === "boolean";
|
|
515
|
-
const legacyForm = (fixFirst, base, green, options) => isOptionalBoolean(fixFirst) && isOptionalBoolean(green) && isOptionalObject(options) ? [base, {
|
|
516
|
-
...options,
|
|
517
|
-
fixFirst: fixFirst ?? void 0,
|
|
518
|
-
green: green ?? void 0
|
|
519
|
-
}] : void 0;
|
|
520
|
-
const currentForm = (base, options, third, fourth) => isOptionalObject(options) && third == null && fourth == null ? [base, options ?? {}] : void 0;
|
|
521
|
-
const callForm = (first, second, [third, fourth, ...extra]) => {
|
|
522
|
-
if (extra.some((value) => value != null)) return void 0;
|
|
523
|
-
if (typeof second === "string") return legacyForm(first, second, third, fourth);
|
|
524
|
-
if (typeof first === "string") return currentForm(first, second, third, fourth);
|
|
525
|
-
};
|
|
526
|
-
/**
|
|
527
|
-
* Undo the human's review-round code edits. A path changed since the human
|
|
528
|
-
* left it is not overwritten; requireRevert names it instead.
|
|
529
|
-
*/
|
|
530
|
-
const reUnwind = async (feedback) => {
|
|
531
|
-
const { base, restoreFrom, edited } = feedback;
|
|
532
|
-
await run("re-unwind", restoreScript(base, {
|
|
533
|
-
restore: edited.filter((c) => c.status !== "added").map((c) => c.path),
|
|
534
|
-
remove: edited.filter((c) => c.status === "added").map((c) => c.path)
|
|
535
|
-
}, restoreFrom), {
|
|
536
|
-
label: "Re-unwinding your review edit",
|
|
537
|
-
file: REVIEW,
|
|
538
|
-
base
|
|
539
|
-
});
|
|
540
|
-
requireRevert(edited, base);
|
|
541
|
-
};
|
|
542
|
-
//#endregion
|
|
543
260
|
//#region src/workflows/shared/continuation.ts
|
|
544
261
|
const phase = (name, steered) => ({
|
|
545
262
|
name,
|
|
@@ -1456,7 +1173,7 @@ const settleQuality = async () => {
|
|
|
1456
1173
|
if (rested) await healthy(() => fixCheck());
|
|
1457
1174
|
};
|
|
1458
1175
|
/** The triaged quality lap, then the human review since `base`. */
|
|
1459
|
-
const reviewedTail = (base, lensBase) => tail((escalations, report) =>
|
|
1176
|
+
const reviewedTail = (base, lensBase) => tail((escalations, report) => humanReview(base, escalations, { report }), {
|
|
1460
1177
|
green: true,
|
|
1461
1178
|
lensBase,
|
|
1462
1179
|
triage: true
|
|
@@ -1525,4 +1242,4 @@ const feature = shipped(ordinaryStart);
|
|
|
1525
1242
|
//#region src/workflows/feature/index.ts
|
|
1526
1243
|
var feature_default = feature;
|
|
1527
1244
|
//#endregion
|
|
1528
|
-
export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, closeOut, collecting, criteriaOf, criterionIds, feature_default as default, defaults,
|
|
1245
|
+
export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, closeOut, collecting, criteriaOf, criterionIds, feature_default as default, defaults, design, envDefaults, feature, fixNits, holdsCallSites, implement, implementing, isTestFile, judgeSeam, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireStableIds, requireTestCallSitesOnly, requireWithin, reviewedRefactor, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, triage, unstableIds, unwind };
|
package/dist/workflows/fix.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-
|
|
2
|
+
import { g as reviewRisks, h as reviewNotes } from "../chunks/steering-D4LUQ-av.mjs";
|
|
3
|
+
import { C as coder, J as fixReviewNotesPrompt, N as agentWithSkills, Tt as envDefaults, c as baseline, i as skills, n as summary, q as finisherSystem, r as access, s as shipped, t as steering, wt as defaults, y as REVIEW } from "../chunks/reserved-DJk_6-ju.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-5w5mxGTq.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
|