@pmelab/gtd 42.0.0 → 43.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 +79 -35
- package/claude/hooks/sketch.ts +1 -1
- package/dist/chunks/{reserved-D7MNY9QJ.mjs → reserved-COEMFEpy.mjs} +35 -46
- package/dist/chunks/{steering-oCDdQlpP.mjs → steering-D4LUQ-av.mjs} +95 -12
- package/dist/chunks/{tail-D2o2h5cc.mjs → tail-C_xn8U7U.mjs} +1 -1
- 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 +31 -42
- package/dist/workflows/fix.mjs +3 -3
- package/dist/workflows/review.mjs +2 -2
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +217 -121
- 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/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/lens-review.feature +16 -4
- package/src/workflows/feature/review-answers.feature +179 -0
- package/src/workflows/feature/review.test.ts +5 -53
- package/src/workflows/feature/review.ts +31 -42
- package/src/workflows/feature/steps.test.ts +0 -12
- package/src/workflows/feature/steps.ts +3 -10
- 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/shared/text.test.ts +40 -89
- package/src/workflows/shared/text.ts +52 -54
|
@@ -5,7 +5,6 @@ import { join } from "node:path"
|
|
|
5
5
|
import { afterEach, describe, expect, it } from "vitest"
|
|
6
6
|
import {
|
|
7
7
|
buildReviewTriageMessage,
|
|
8
|
-
deferNotesScript,
|
|
9
8
|
buildReviewReviewingPrompt,
|
|
10
9
|
agentWithSkills,
|
|
11
10
|
buildFixPrompt,
|
|
@@ -32,8 +31,7 @@ import {
|
|
|
32
31
|
withSkills,
|
|
33
32
|
composedReview,
|
|
34
33
|
} from "./text.js"
|
|
35
|
-
import { steeringFormatFor } from "../../steering/index.js"
|
|
36
|
-
import { applyEmittedScript, InMemRepo } from "../../testkit/index.js"
|
|
34
|
+
import { steeringFormatFor, unansweredNotes } from "../../steering/index.js"
|
|
37
35
|
import { captureStep, renderText } from "./text.fixture.js"
|
|
38
36
|
|
|
39
37
|
describe("withSkills", () => {
|
|
@@ -829,53 +827,6 @@ describe("buildReviewReviewingPrompt risk marking", () => {
|
|
|
829
827
|
})
|
|
830
828
|
})
|
|
831
829
|
|
|
832
|
-
describe("deferNotesScript", () => {
|
|
833
|
-
const run = (notes: Parameters<typeof deferNotesScript>[0], existing?: string): string => {
|
|
834
|
-
const dir = mkdtempSync(join(tmpdir(), "defer-"))
|
|
835
|
-
mkdirSync(join(dir, ".gtd"))
|
|
836
|
-
if (existing !== undefined) writeFileSync(join(dir, ".gtd/TODO.md"), existing)
|
|
837
|
-
execFileSync("sh", ["-c", deferNotesScript(notes, "abcdef1234567890")], { cwd: dir })
|
|
838
|
-
return readFileSync(join(dir, ".gtd/TODO.md"), "utf8")
|
|
839
|
-
}
|
|
840
|
-
const notes = [{ id: "note-1", anchor: "a.ts:3", text: "plain", label: "could: tiny" }]
|
|
841
|
-
|
|
842
|
-
it("creates the file with one section headed by the round's short sha", () => {
|
|
843
|
-
expect(run(notes)).toBe(
|
|
844
|
-
"## Deferred review notes (abcdef1)\n\n- note-1 — a.ts:3 (could: tiny): plain\n",
|
|
845
|
-
)
|
|
846
|
-
})
|
|
847
|
-
|
|
848
|
-
it("appends after a blank line, never truncating", () => {
|
|
849
|
-
const out = run(notes, "# Sketch\n\nkeep me\n")
|
|
850
|
-
expect(out.startsWith("# Sketch\n\nkeep me\n\n## Deferred review notes (abcdef1)\n")).toBe(true)
|
|
851
|
-
})
|
|
852
|
-
|
|
853
|
-
it("separates from a file lacking a trailing newline", () => {
|
|
854
|
-
expect(run(notes, "keep")).toContain("keep\n\n## Deferred")
|
|
855
|
-
})
|
|
856
|
-
|
|
857
|
-
it("lands quotes, dollars and backticks verbatim", () => {
|
|
858
|
-
const text = 'it\'s $HOME `x` "q" \\n'
|
|
859
|
-
const out = run([{ id: "note-1", anchor: "a", text }])
|
|
860
|
-
expect(out).toContain(text)
|
|
861
|
-
})
|
|
862
|
-
|
|
863
|
-
it("is an oxfmt fixed point, for a note longer than one line", () => {
|
|
864
|
-
const long = [
|
|
865
|
-
{
|
|
866
|
-
id: "note-3",
|
|
867
|
-
anchor: "Parser accepts CRLF ./src/parse.ts#30-31",
|
|
868
|
-
text: "rename the helper to something clearer please",
|
|
869
|
-
label: "could",
|
|
870
|
-
},
|
|
871
|
-
]
|
|
872
|
-
const dir = mkdtempSync(join(tmpdir(), "fmt-"))
|
|
873
|
-
const file = join(dir, "TODO.md")
|
|
874
|
-
writeFileSync(file, run(long, "# Sketch\n"))
|
|
875
|
-
execFileSync("npx", ["oxfmt", "--check", file], { cwd: process.cwd() })
|
|
876
|
-
})
|
|
877
|
-
})
|
|
878
|
-
|
|
879
830
|
describe("severity wording", () => {
|
|
880
831
|
it("triage message names the four severity ids", () => {
|
|
881
832
|
const m = renderText(() => buildReviewTriageMessage())
|
|
@@ -888,44 +839,6 @@ describe("severity wording", () => {
|
|
|
888
839
|
expect(p).toContain("capture order")
|
|
889
840
|
expect(p).toContain("(tier: reason)")
|
|
890
841
|
})
|
|
891
|
-
|
|
892
|
-
it("design triage parks deferred review notes", () => {
|
|
893
|
-
expect(renderText(() => designTriagePrompt("base"))).toContain("## Deferred review notes")
|
|
894
|
-
})
|
|
895
|
-
})
|
|
896
|
-
|
|
897
|
-
describe("deferNotesScript — the in-memory recognizer agrees with the real sh script", () => {
|
|
898
|
-
const notes = [
|
|
899
|
-
{ id: "note-1", anchor: "a.ts:3", text: 'it\'s $HOME `x` "q" 100%', label: "could" },
|
|
900
|
-
{
|
|
901
|
-
id: "note-2",
|
|
902
|
-
anchor: "Parser accepts CRLF ./src/parse.ts#30-31",
|
|
903
|
-
text: "rename the helper to something clearer please",
|
|
904
|
-
label: "could",
|
|
905
|
-
},
|
|
906
|
-
]
|
|
907
|
-
const cases: [string, string | undefined][] = [
|
|
908
|
-
["no file", undefined],
|
|
909
|
-
["an empty file", ""],
|
|
910
|
-
["a file without a trailing newline", "keep"],
|
|
911
|
-
["a file with a trailing newline", "# Sketch\n\nkeep\n"],
|
|
912
|
-
]
|
|
913
|
-
|
|
914
|
-
it.each(cases)("%s", (_name, existing) => {
|
|
915
|
-
const script = deferNotesScript(notes, "abcdef1234567890")
|
|
916
|
-
const dir = mkdtempSync(join(tmpdir(), "defer-contract-"))
|
|
917
|
-
if (existing !== undefined) {
|
|
918
|
-
mkdirSync(join(dir, ".gtd"))
|
|
919
|
-
writeFileSync(join(dir, ".gtd/TODO.md"), existing)
|
|
920
|
-
}
|
|
921
|
-
execFileSync("sh", ["-c", script], { cwd: dir })
|
|
922
|
-
const real = readFileSync(join(dir, ".gtd/TODO.md"), "utf8")
|
|
923
|
-
|
|
924
|
-
const repo = new InMemRepo()
|
|
925
|
-
if (existing !== undefined) repo.writeFile(".gtd/TODO.md", existing)
|
|
926
|
-
expect(applyEmittedScript(repo, new Map(), script).ok).toBe(true)
|
|
927
|
-
expect(repo.readFile(".gtd/TODO.md")).toBe(real)
|
|
928
|
-
})
|
|
929
842
|
})
|
|
930
843
|
|
|
931
844
|
describe("buildReviewReviewingPrompt scenarios", () => {
|
|
@@ -1319,6 +1232,15 @@ it("asks the refactor review, and only it, to flag a test-file hunk that is more
|
|
|
1319
1232
|
expect(first).not.toContain("give any test-file hunk its own chunk")
|
|
1320
1233
|
})
|
|
1321
1234
|
|
|
1235
|
+
describe("buildReviewCollectingPrompt answer boxes", () => {
|
|
1236
|
+
it("a hand-edit round folds fix and own answers, other rounds leave them to gtd", () => {
|
|
1237
|
+
const edits = renderText(() => buildReviewCollectingPrompt("cap", undefined, "edits"))
|
|
1238
|
+
expect(edits).toContain("A hand-edit round routes no answer box itself")
|
|
1239
|
+
const triaged = renderText(() => buildReviewCollectingPrompt("cap", undefined, "triaged"))
|
|
1240
|
+
expect(triaged).toContain("Never fold a note's ticked answer box")
|
|
1241
|
+
})
|
|
1242
|
+
})
|
|
1243
|
+
|
|
1322
1244
|
describe("composedReview", () => {
|
|
1323
1245
|
const BASE = "0123456789abcdef0123456789abcdef01234567"
|
|
1324
1246
|
|
|
@@ -1380,6 +1302,35 @@ describe("composedReview", () => {
|
|
|
1380
1302
|
|
|
1381
1303
|
it("names the nit fix on the review after it", () => {
|
|
1382
1304
|
const doc = composedReview(BASE, { notes: [], fixed: [], noise: [], nitFix: "abcdef0123" })
|
|
1383
|
-
expect(doc).toContain(
|
|
1305
|
+
expect(doc).toContain(
|
|
1306
|
+
"## Fixed from your notes\n\nYour nits and fix-as-suggested notes were worked on in abcdef0;",
|
|
1307
|
+
)
|
|
1308
|
+
})
|
|
1309
|
+
|
|
1310
|
+
it("ends every note, and only a note, in its three answer boxes", () => {
|
|
1311
|
+
const doc = composedReview(BASE, {
|
|
1312
|
+
notes: [{ title: "a", body: "- ends in a list" }],
|
|
1313
|
+
fixed: [{ title: "f", commit: "fedcba9876543210" }],
|
|
1314
|
+
noise: [],
|
|
1315
|
+
})
|
|
1316
|
+
expect(doc).toContain(
|
|
1317
|
+
"## a\n\n- ends in a list\n\nTick exactly one:\n\n- [ ] fix as suggested\n- [ ] won't fix\n- [ ] _your answer_\n\n## Fixed automatically",
|
|
1318
|
+
)
|
|
1319
|
+
expect(doc.match(/fix as suggested/g)).toHaveLength(1)
|
|
1320
|
+
expect(unansweredNotes(doc)).toEqual([{ title: "a", line: 5, reason: "tick exactly one box" }])
|
|
1321
|
+
})
|
|
1322
|
+
|
|
1323
|
+
it("leaves out the notes already answered", () => {
|
|
1324
|
+
const doc = composedReview(BASE, {
|
|
1325
|
+
notes: [
|
|
1326
|
+
{ title: "a", body: "1" },
|
|
1327
|
+
{ title: "a", body: "2" },
|
|
1328
|
+
],
|
|
1329
|
+
fixed: [],
|
|
1330
|
+
noise: [],
|
|
1331
|
+
answered: new Set(["a"]),
|
|
1332
|
+
})
|
|
1333
|
+
expect(doc).not.toContain("## a\n")
|
|
1334
|
+
expect(doc).toContain("## a (2)\n\n2")
|
|
1384
1335
|
})
|
|
1385
1336
|
})
|
|
@@ -12,6 +12,12 @@ import {
|
|
|
12
12
|
type JudgeQuestion,
|
|
13
13
|
type SummaryContext,
|
|
14
14
|
} from "../../flows/index.js"
|
|
15
|
+
import {
|
|
16
|
+
ANSWER_FIX,
|
|
17
|
+
ANSWER_LEAD,
|
|
18
|
+
ANSWER_WONT_FIX,
|
|
19
|
+
FREE_TEXT_PLACEHOLDER,
|
|
20
|
+
} from "../../steering/index.js"
|
|
15
21
|
import { escapeRegExp } from "./registry.js"
|
|
16
22
|
import { defaultFlagRegistry } from "./vars.js"
|
|
17
23
|
import type { CollectRound, RemovedAssertions, Requested } from "../../workflow-kit/index.js"
|
|
@@ -116,6 +122,8 @@ export interface ComposedReview {
|
|
|
116
122
|
readonly weakened?: readonly RemovedAssertions[]
|
|
117
123
|
/** The commit that fixed the human's nits, on the review that follows it. */
|
|
118
124
|
readonly nitFix?: string | undefined
|
|
125
|
+
/** Titles of notes the human already answered, left out. */
|
|
126
|
+
readonly answered?: ReadonlySet<string> | undefined
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
const SUMMARY_TITLES = [
|
|
@@ -133,9 +141,9 @@ const unboxed = (body: string): string =>
|
|
|
133
141
|
const short = (hash: string): string => hash.slice(0, 7)
|
|
134
142
|
|
|
135
143
|
/**
|
|
136
|
-
* \`.gtd/REVIEW.md\` as gtd writes it: one chunk per note,
|
|
137
|
-
*
|
|
138
|
-
* matched to their chunk by title.
|
|
144
|
+
* \`.gtd/REVIEW.md\` as gtd writes it: one chunk per note, each ending in its
|
|
145
|
+
* answer boxes, then the fixed and noise lists. Chunk titles are made unique —
|
|
146
|
+
* notes and answers are matched to their chunk by title.
|
|
139
147
|
*/
|
|
140
148
|
export const composedReview = (base: string, report: ComposedReview): string => {
|
|
141
149
|
// Summary titles are taken first, so no note can claim one.
|
|
@@ -147,6 +155,11 @@ export const composedReview = (base: string, report: ComposedReview): string =>
|
|
|
147
155
|
return name
|
|
148
156
|
}
|
|
149
157
|
const chunk = (title: string, body: string): string => `## ${title}\n\n${body.trim()}`
|
|
158
|
+
// The lead-in line keeps a body that ends in a list from absorbing the boxes.
|
|
159
|
+
const answerBoxes = `${ANSWER_LEAD}\n\n- [ ] ${ANSWER_FIX}\n- [ ] ${ANSWER_WONT_FIX}\n- [ ] ${FREE_TEXT_PLACEHOLDER}`
|
|
160
|
+
const notes = report.notes
|
|
161
|
+
.map((n) => ({ ...n, title: unique(n.title) }))
|
|
162
|
+
.filter((n) => report.answered?.has(n.title) !== true)
|
|
150
163
|
const weakened = (report.weakened ?? []).flatMap(({ path, lines }) =>
|
|
151
164
|
lines.map((a) => `- ${path} — ${a.scenario} — ${a.line}`),
|
|
152
165
|
)
|
|
@@ -156,7 +169,7 @@ export const composedReview = (base: string, report: ComposedReview): string =>
|
|
|
156
169
|
: [
|
|
157
170
|
chunk(
|
|
158
171
|
"Fixed from your notes",
|
|
159
|
-
`Your nits were
|
|
172
|
+
`Your nits and fix-as-suggested notes were worked on in ${short(report.nitFix)}; the notes you answered are left out — check the diff.`,
|
|
160
173
|
),
|
|
161
174
|
]),
|
|
162
175
|
...(weakened.length === 0
|
|
@@ -167,14 +180,16 @@ export const composedReview = (base: string, report: ComposedReview): string =>
|
|
|
167
180
|
`These scenarios lost an assertion the review base had:\n\n${weakened.join("\n")}`,
|
|
168
181
|
),
|
|
169
182
|
]),
|
|
170
|
-
...
|
|
183
|
+
...notes.map((n) =>
|
|
171
184
|
chunk(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
n.title,
|
|
186
|
+
`${
|
|
187
|
+
n.unresolved === true
|
|
188
|
+
? `_The fixer left this unresolved._\n\n${unboxed(n.body)}`
|
|
189
|
+
: n.unclear === true
|
|
190
|
+
? `_A fix turn removed this while renaming another finding — check whether it was fixed._\n\n${unboxed(n.body)}`
|
|
191
|
+
: unboxed(n.body) || "_No detail given._"
|
|
192
|
+
}\n\n${answerBoxes}`,
|
|
178
193
|
),
|
|
179
194
|
),
|
|
180
195
|
...(report.fixed.length === 0
|
|
@@ -288,10 +303,6 @@ ${stateFileRules}
|
|
|
288
303
|
${footnoteFoldIn}${codeThreadReplies()}
|
|
289
304
|
- The only state file this turn touches is \`.gtd/REQUIREMENTS.md\`
|
|
290
305
|
— no other files for notes or output
|
|
291
|
-
- Lines of a \`## Deferred review notes\` section of \`.gtd/TODO.md\`
|
|
292
|
-
that the start diff does not touch are parked by review, never folded;
|
|
293
|
-
the human's edits to such a line (or its removal) are input like any
|
|
294
|
-
other part of the start diff
|
|
295
306
|
- \`.gtd/TODO.md\` is the likely home of the sketch that started
|
|
296
307
|
this process — the human's input, folded into the concerns
|
|
297
308
|
below like any other part of the start diff; never a state
|
|
@@ -680,8 +691,12 @@ export const buildReviewAwaitReviewMessage = (base: string): string =>
|
|
|
680
691
|
per finding that needs your judgement — it questions a criterion or the
|
|
681
692
|
approach, or the fixer could not resolve it — then, for reference only, what
|
|
682
693
|
was **fixed automatically** (with the fixing commit) and what was **dropped
|
|
683
|
-
as noise**.
|
|
684
|
-
|
|
694
|
+
as noise**. Each finding chunk ends in three boxes — tick exactly one:
|
|
695
|
+
\`fix as suggested\` (fixed in the nit batch below), \`won't fix\` (dropped,
|
|
696
|
+
recorded in the commit), or write your own answer over \`_your answer_\` and
|
|
697
|
+
tick it (judged like a note). Every landing here is refused until each
|
|
698
|
+
finding holds exactly one tick. The fixed and noise lists need no answer;
|
|
699
|
+
disagreeing with a fix or a drop means writing a note of your own.
|
|
685
700
|
|
|
686
701
|
Review the diff yourself, with whatever tool you like — gtd checks
|
|
687
702
|
nothing out and touches no ref; \`gtd base\` prints this same hash any
|
|
@@ -691,8 +706,8 @@ working tree:
|
|
|
691
706
|
git diff ${base}
|
|
692
707
|
When you've been through the whole diff, run \`gtd land\`:
|
|
693
708
|
|
|
694
|
-
- **Sign off** — leave no comment — no note
|
|
695
|
-
\`.gtd/REVIEW.md\`, no code edit — to close the process.
|
|
709
|
+
- **Sign off** — answer every finding, leave no other comment — no note
|
|
710
|
+
in \`.gtd/REVIEW.md\`, no code edit — to close the process.
|
|
696
711
|
Every turn commit stays on the
|
|
697
712
|
branch; run \`gtd summary\` afterward for a closing-message
|
|
698
713
|
prompt.
|
|
@@ -894,34 +909,6 @@ const wrapItem = (item: string): string[] => {
|
|
|
894
909
|
return [...out, line]
|
|
895
910
|
}
|
|
896
911
|
|
|
897
|
-
/** Appends one section of `notes` to `.gtd/TODO.md`, wrapped the way oxfmt writes it; an existing file is only ever appended to. */
|
|
898
|
-
export const deferNotesScript = (notes: readonly NoteInput[], round: string): string => {
|
|
899
|
-
const lines = [
|
|
900
|
-
`## Deferred review notes (${round.slice(0, 7)})`,
|
|
901
|
-
"",
|
|
902
|
-
...notes.flatMap((n) => wrapItem(`- ${noteHead(n)}: ${n.text.replace(/\s+/g, " ")}`)),
|
|
903
|
-
]
|
|
904
|
-
return `mkdir -p .gtd
|
|
905
|
-
if [ -s .gtd/TODO.md ]; then
|
|
906
|
-
[ -z "$(tail -c1 .gtd/TODO.md)" ] || printf '\\n' >> .gtd/TODO.md
|
|
907
|
-
printf '\\n' >> .gtd/TODO.md
|
|
908
|
-
fi
|
|
909
|
-
printf '%s\\n' ${lines.map(quote).join(" ")} >> .gtd/TODO.md
|
|
910
|
-
`
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
/** The gate right after a deferral: landing clean signs off without these notes. */
|
|
914
|
-
export const buildReviewAwaitAfterDeferMessage = (
|
|
915
|
-
base: string,
|
|
916
|
-
deferred: readonly NoteInput[],
|
|
917
|
-
): string =>
|
|
918
|
-
`${buildReviewAwaitReviewMessage(base)}
|
|
919
|
-
Deferred to \`.gtd/TODO.md\` just now, NOT acted on in this process — landing
|
|
920
|
-
clean signs off without them:
|
|
921
|
-
|
|
922
|
-
${deferred.map((n) => `- ${noteHead(n)}: ${n.text.replace(/\s*\n\s*/g, " ")}`).join("\n")}
|
|
923
|
-
`
|
|
924
|
-
|
|
925
912
|
export const buildReviewTriageMessage = (): string =>
|
|
926
913
|
`Judging each note the human added to \`.gtd/REVIEW.md\` (a line note, a
|
|
927
914
|
footnote, or prose under a chunk) with one verdict: \`edit\` (a change
|
|
@@ -932,12 +919,14 @@ default; a skipped judgment never dismisses a note).
|
|
|
932
919
|
|
|
933
920
|
Also judge each note's severity, one id per note: \`note-<n>.security\`,
|
|
934
921
|
\`note-<n>.reversibility\`, \`note-<n>.blastRadius\` and
|
|
935
|
-
\`note-<n>.newConcept\`.
|
|
922
|
+
\`note-<n>.newConcept\`. An own answer under a finding's boxes is judged
|
|
923
|
+
the same way, keyed \`answer-<n>\`.
|
|
936
924
|
`
|
|
937
925
|
const closedFold = (round: CollectRound, target: string): string =>
|
|
938
926
|
round === "edits"
|
|
939
927
|
? `- Fold every hand-edit and every note the human left that round into
|
|
940
|
-
\`${target}\`
|
|
928
|
+
\`${target}\` — a note ticked \`fix as suggested\` or answered in its
|
|
929
|
+
own words included; one ticked \`won't fix\` is dropped
|
|
941
930
|
`
|
|
942
931
|
: round === "notes"
|
|
943
932
|
? `- Fold each note that asks for a change into \`${target}\`
|
|
@@ -954,6 +943,7 @@ export const buildReviewCollectingPrompt = (
|
|
|
954
943
|
/** `"reply"` is the in-gate reply turn; any other round is closed and folds what that round left. */
|
|
955
944
|
readonly round: CollectRound
|
|
956
945
|
},
|
|
946
|
+
round?: CollectRound,
|
|
957
947
|
): string => {
|
|
958
948
|
const target = refactor ? ".gtd/REFACTOR.md" : ".gtd/REQUIREMENTS.md"
|
|
959
949
|
const fence = refactor
|
|
@@ -1038,12 +1028,20 @@ ${closedFold(refactor.round, target)}`
|
|
|
1038
1028
|
: `- This turn writes \`${target}\` (the folded concerns) and
|
|
1039
1029
|
replies inside \`.gtd/REVIEW.md\` (thread replies only; the file stays
|
|
1040
1030
|
in the tree) — you classify, you do not build
|
|
1041
|
-
- Fold every concluded thread
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1031
|
+
- Fold every concluded thread and hand-edit — and each \`edit\` note
|
|
1032
|
+
the capture lists, no other note — into \`${target}\` and
|
|
1033
|
+
delete the folded threads from \`.gtd/REVIEW.md\`; append one \`- A:\`
|
|
1034
|
+
reply to each thread whose last entry is a \`- H:\` question
|
|
1035
|
+
${
|
|
1036
|
+
round === "edits"
|
|
1037
|
+
? `- A hand-edit round routes no answer box itself: fold each note
|
|
1038
|
+
ticked \`fix as suggested\` or answered in its own words, too; drop one
|
|
1039
|
+
ticked \`won't fix\`
|
|
1046
1040
|
`
|
|
1041
|
+
: `- Never fold a note's ticked answer box the capture does not list: gtd
|
|
1042
|
+
routes those itself
|
|
1043
|
+
`
|
|
1044
|
+
}`
|
|
1047
1045
|
}- When the capture names an answering commit, carry each answered
|
|
1048
1046
|
question into \`${target}\`'s \`## Answered Questions\` as
|
|
1049
1047
|
\`### <the note>\` plus its answer — the lap replaces \`.gtd/REVIEW.md\`
|