@pmelab/gtd 41.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 +107 -53
- package/claude/hooks/sketch.ts +1 -1
- package/dist/chunks/{reserved-Ch4LLrEj.mjs → reserved-COEMFEpy.mjs} +84 -51
- package/dist/chunks/{steering-DVILanVf.mjs → steering-D4LUQ-av.mjs} +95 -12
- package/dist/chunks/tail-C_xn8U7U.mjs +246 -0
- package/dist/flows.mjs +11 -1
- package/dist/gtd.bundle.mjs +498 -30
- package/dist/steering.mjs +2 -2
- package/dist/testkit.mjs +460 -28
- 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 +66 -57
- 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 +446 -141
- 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/flows/scripts.ts +15 -0
- package/src/steering/index.ts +12 -1
- package/src/steering/review.test.ts +109 -20
- package/src/steering/review.ts +160 -9
- package/src/testkit/EmittedScriptRecognizer.ts +0 -14
- package/src/testkit/FlowScripts.test.ts +17 -0
- package/src/testkit/FlowScripts.ts +15 -0
- package/src/testkit/steps.ts +31 -0
- package/src/workflow-kit/review.test.ts +170 -26
- package/src/workflow-kit/review.ts +95 -19
- package/src/workflows/feature/coverage.feature +3 -3
- package/src/workflows/feature/fast-path.feature +2 -2
- package/src/workflows/feature/feature.test.ts +2 -37
- package/src/workflows/feature/feature.ts +12 -14
- package/src/workflows/feature/flag-mode.feature +2 -2
- package/src/workflows/feature/lens-review.feature +246 -0
- package/src/workflows/feature/one-step-land.feature +2 -2
- package/src/workflows/feature/quality-unresolved.feature +47 -32
- package/src/workflows/feature/refactor-absorbs.feature +3 -3
- package/src/workflows/feature/refactor-first.feature +5 -5
- package/src/workflows/feature/refactor-rebuild.feature +7 -7
- package/src/workflows/feature/refactor-review.feature +19 -100
- package/src/workflows/feature/refactor.test.ts +12 -27
- package/src/workflows/feature/refactor.ts +0 -1
- package/src/workflows/feature/review-answers.feature +179 -0
- package/src/workflows/feature/review-gate.feature +6 -6
- package/src/workflows/feature/review.test.ts +33 -77
- package/src/workflows/feature/review.ts +79 -47
- package/src/workflows/feature/seam-rejection.feature +2 -2
- package/src/workflows/feature/seam.feature +2 -2
- package/src/workflows/feature/seam.test.ts +1 -1
- package/src/workflows/feature/slicing.feature +6 -6
- package/src/workflows/feature/steps.test.ts +0 -12
- package/src/workflows/feature/steps.ts +3 -10
- package/src/workflows/index.ts +1 -0
- 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/lenses.test.ts +39 -0
- package/src/workflows/shared/lenses.ts +176 -0
- package/src/workflows/shared/options.ts +1 -0
- package/src/workflows/shared/tail.ts +19 -4
- package/src/workflows/shared/text.test.ts +106 -88
- package/src/workflows/shared/text.ts +164 -53
- package/src/workflows/shared/vars.ts +1 -0
- package/dist/chunks/tail-lY1UmJrU.mjs +0 -112
|
@@ -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"
|
|
@@ -90,6 +96,126 @@ What each change does next (then run \`gtd land\`):
|
|
|
90
96
|
- **Continue** — having undone the sketch by hand, check the test baseline is green and start triage (**start-gate.check**).
|
|
91
97
|
`
|
|
92
98
|
|
|
99
|
+
export const qualityTriageMessage = (): string =>
|
|
100
|
+
`Judging each quality finding. Run \`gtd judge answer\` and pipe a
|
|
101
|
+
\`mechanical\`/\`noise\`/\`design\` verdict per finding ID — or land without a
|
|
102
|
+
verdict, which hands every finding to the review as a note.
|
|
103
|
+
`
|
|
104
|
+
|
|
105
|
+
/** \`.gtd/QUALITY.md\` holding only \`findings\`, one \`## \` chunk each. */
|
|
106
|
+
export const qualityFindingsFile = (
|
|
107
|
+
findings: readonly { readonly title: string; readonly body: string }[],
|
|
108
|
+
): string =>
|
|
109
|
+
`# Quality\n\n${findings.map((f) => `## ${f.title}\n\n${f.body}`.trim()).join("\n\n")}\n`
|
|
110
|
+
|
|
111
|
+
export interface ComposedReview {
|
|
112
|
+
readonly notes: readonly {
|
|
113
|
+
readonly title: string
|
|
114
|
+
readonly body: string
|
|
115
|
+
readonly unresolved?: true
|
|
116
|
+
/** A fix turn removed its chunk while renaming another. */
|
|
117
|
+
readonly unclear?: true
|
|
118
|
+
}[]
|
|
119
|
+
readonly fixed: readonly { readonly title: string; readonly commit: string }[]
|
|
120
|
+
readonly noise: readonly { readonly title: string; readonly p: number }[]
|
|
121
|
+
/** `Then` lines a `.feature` file at the review base no longer carries. */
|
|
122
|
+
readonly weakened?: readonly RemovedAssertions[]
|
|
123
|
+
/** The commit that fixed the human's nits, on the review that follows it. */
|
|
124
|
+
readonly nitFix?: string | undefined
|
|
125
|
+
/** Titles of notes the human already answered, left out. */
|
|
126
|
+
readonly answered?: ReadonlySet<string> | undefined
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const SUMMARY_TITLES = [
|
|
130
|
+
"Weakened scenarios",
|
|
131
|
+
"Fixed from your notes",
|
|
132
|
+
"Fixed automatically",
|
|
133
|
+
"Dropped as noise",
|
|
134
|
+
"Nothing to note",
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
// A lens body is pasted as prose: a task box would read as a broken hunk pointer, or a `Risk:` one.
|
|
138
|
+
const unboxed = (body: string): string =>
|
|
139
|
+
body.replace(/^(\s*(?:>\s*)*(?:[-*+]|\d+[.)])\s+)\[[ xX]\]\s?/gm, "$1")
|
|
140
|
+
|
|
141
|
+
const short = (hash: string): string => hash.slice(0, 7)
|
|
142
|
+
|
|
143
|
+
/**
|
|
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.
|
|
147
|
+
*/
|
|
148
|
+
export const composedReview = (base: string, report: ComposedReview): string => {
|
|
149
|
+
// Summary titles are taken first, so no note can claim one.
|
|
150
|
+
const used = new Set(SUMMARY_TITLES)
|
|
151
|
+
const unique = (title: string): string => {
|
|
152
|
+
let name = title
|
|
153
|
+
for (let n = 2; used.has(name); n++) name = `${title} (${n})`
|
|
154
|
+
used.add(name)
|
|
155
|
+
return name
|
|
156
|
+
}
|
|
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)
|
|
163
|
+
const weakened = (report.weakened ?? []).flatMap(({ path, lines }) =>
|
|
164
|
+
lines.map((a) => `- ${path} — ${a.scenario} — ${a.line}`),
|
|
165
|
+
)
|
|
166
|
+
const chunks = [
|
|
167
|
+
...(report.nitFix === undefined
|
|
168
|
+
? []
|
|
169
|
+
: [
|
|
170
|
+
chunk(
|
|
171
|
+
"Fixed from your notes",
|
|
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.`,
|
|
173
|
+
),
|
|
174
|
+
]),
|
|
175
|
+
...(weakened.length === 0
|
|
176
|
+
? []
|
|
177
|
+
: [
|
|
178
|
+
chunk(
|
|
179
|
+
"Weakened scenarios",
|
|
180
|
+
`These scenarios lost an assertion the review base had:\n\n${weakened.join("\n")}`,
|
|
181
|
+
),
|
|
182
|
+
]),
|
|
183
|
+
...notes.map((n) =>
|
|
184
|
+
chunk(
|
|
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}`,
|
|
193
|
+
),
|
|
194
|
+
),
|
|
195
|
+
...(report.fixed.length === 0
|
|
196
|
+
? []
|
|
197
|
+
: [
|
|
198
|
+
chunk(
|
|
199
|
+
"Fixed automatically",
|
|
200
|
+
report.fixed.map((f) => `- ${f.title} (${short(f.commit)})`).join("\n"),
|
|
201
|
+
),
|
|
202
|
+
]),
|
|
203
|
+
...(report.noise.length === 0
|
|
204
|
+
? []
|
|
205
|
+
: [chunk("Dropped as noise", report.noise.map((f) => `- ${f.title} (p=${f.p})`).join("\n"))]),
|
|
206
|
+
]
|
|
207
|
+
const body =
|
|
208
|
+
chunks.length > 0
|
|
209
|
+
? chunks
|
|
210
|
+
: [
|
|
211
|
+
chunk(
|
|
212
|
+
"Nothing to note",
|
|
213
|
+
"No quality lens raised anything for you — or none ran. Review the diff yourself.",
|
|
214
|
+
),
|
|
215
|
+
]
|
|
216
|
+
return `# Review: ${short(base)}\n\n<!-- base: ${base} -->\n\n${body.join("\n\n")}\n`
|
|
217
|
+
}
|
|
218
|
+
|
|
93
219
|
export const qualityUnresolvedMessage = (): string =>
|
|
94
220
|
`The quality lap's fix turns hit their cap and \`.gtd/QUALITY.md\` still
|
|
95
221
|
holds findings. Nothing after this fixes them, so the process will not
|
|
@@ -177,10 +303,6 @@ ${stateFileRules}
|
|
|
177
303
|
${footnoteFoldIn}${codeThreadReplies()}
|
|
178
304
|
- The only state file this turn touches is \`.gtd/REQUIREMENTS.md\`
|
|
179
305
|
— no other files for notes or output
|
|
180
|
-
- Lines of a \`## Deferred review notes\` section of \`.gtd/TODO.md\`
|
|
181
|
-
that the start diff does not touch are parked by review, never folded;
|
|
182
|
-
the human's edits to such a line (or its removal) are input like any
|
|
183
|
-
other part of the start diff
|
|
184
306
|
- \`.gtd/TODO.md\` is the likely home of the sketch that started
|
|
185
307
|
this process — the human's input, folded into the concerns
|
|
186
308
|
below like any other part of the start diff; never a state
|
|
@@ -488,8 +610,10 @@ ${agentConduct}`
|
|
|
488
610
|
export const buildFixQualityPrompt = (rules?: string): string =>
|
|
489
611
|
`${stateFileRules}
|
|
490
612
|
- Read \`.gtd/QUALITY.md\` — one \`## \` chunk per finding a quality
|
|
491
|
-
dimension wrote.
|
|
492
|
-
|
|
613
|
+
dimension wrote. Fix every finding in every chunk, blocking or not;
|
|
614
|
+
where dimensions repeat one, fix the shared cause once
|
|
615
|
+
- Delete each finding's \`## \` chunk once it is resolved; never
|
|
616
|
+
rename, merge or reword a chunk you leave
|
|
493
617
|
- When findings conflict, missing test signal beats line count —
|
|
494
618
|
a test is never deleted to satisfy a simplification finding
|
|
495
619
|
- Delete \`.gtd/QUALITY.md\` once every finding is resolved
|
|
@@ -541,9 +665,9 @@ export const buildQualityReviewingPrompt = (
|
|
|
541
665
|
behaviour removed — decide that by reasoning, never by running a
|
|
542
666
|
mutation-testing tool. A test that pins a bug is a finding, not
|
|
543
667
|
praise
|
|
544
|
-
- APPEND every finding you have, blocking or not, as
|
|
545
|
-
chunk to \`.gtd/QUALITY.md\` —
|
|
546
|
-
dimension already wrote there
|
|
668
|
+
- APPEND every finding you have, blocking or not, as its own
|
|
669
|
+
\`## <one-line title>\` chunk to \`.gtd/QUALITY.md\` — one finding per
|
|
670
|
+
chunk, never overwrite what an earlier dimension already wrote there
|
|
547
671
|
- Write nothing only when this lens found nothing at all — then a
|
|
548
672
|
clean turn IS this dimension's approval
|
|
549
673
|
${
|
|
@@ -563,12 +687,16 @@ export const reviewerSystem = (): string =>
|
|
|
563
687
|
${agentConduct}`
|
|
564
688
|
|
|
565
689
|
export const buildReviewAwaitReviewMessage = (base: string): string =>
|
|
566
|
-
`\`.gtd/REVIEW.md\` holds the
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
690
|
+
`\`.gtd/REVIEW.md\` holds what the quality lenses left for you: one chunk
|
|
691
|
+
per finding that needs your judgement — it questions a criterion or the
|
|
692
|
+
approach, or the fixer could not resolve it — then, for reference only, what
|
|
693
|
+
was **fixed automatically** (with the fixing commit) and what was **dropped
|
|
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.
|
|
572
700
|
|
|
573
701
|
Review the diff yourself, with whatever tool you like — gtd checks
|
|
574
702
|
nothing out and touches no ref; \`gtd base\` prints this same hash any
|
|
@@ -578,9 +706,9 @@ working tree:
|
|
|
578
706
|
git diff ${base}
|
|
579
707
|
When you've been through the whole diff, run \`gtd land\`:
|
|
580
708
|
|
|
581
|
-
- **Sign off** — leave no comment — no note
|
|
582
|
-
\`.gtd/REVIEW.md\`, no code edit — to close the process
|
|
583
|
-
|
|
709
|
+
- **Sign off** — answer every finding, leave no other comment — no note
|
|
710
|
+
in \`.gtd/REVIEW.md\`, no code edit — to close the process.
|
|
711
|
+
Every turn commit stays on the
|
|
584
712
|
branch; run \`gtd summary\` afterward for a closing-message
|
|
585
713
|
prompt.
|
|
586
714
|
- **Ask a question** — start a thread (\`- H: <question>\`) on a
|
|
@@ -781,34 +909,6 @@ const wrapItem = (item: string): string[] => {
|
|
|
781
909
|
return [...out, line]
|
|
782
910
|
}
|
|
783
911
|
|
|
784
|
-
/** Appends one section of `notes` to `.gtd/TODO.md`, wrapped the way oxfmt writes it; an existing file is only ever appended to. */
|
|
785
|
-
export const deferNotesScript = (notes: readonly NoteInput[], round: string): string => {
|
|
786
|
-
const lines = [
|
|
787
|
-
`## Deferred review notes (${round.slice(0, 7)})`,
|
|
788
|
-
"",
|
|
789
|
-
...notes.flatMap((n) => wrapItem(`- ${noteHead(n)}: ${n.text.replace(/\s+/g, " ")}`)),
|
|
790
|
-
]
|
|
791
|
-
return `mkdir -p .gtd
|
|
792
|
-
if [ -s .gtd/TODO.md ]; then
|
|
793
|
-
[ -z "$(tail -c1 .gtd/TODO.md)" ] || printf '\\n' >> .gtd/TODO.md
|
|
794
|
-
printf '\\n' >> .gtd/TODO.md
|
|
795
|
-
fi
|
|
796
|
-
printf '%s\\n' ${lines.map(quote).join(" ")} >> .gtd/TODO.md
|
|
797
|
-
`
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/** The gate right after a deferral: landing clean signs off without these notes. */
|
|
801
|
-
export const buildReviewAwaitAfterDeferMessage = (
|
|
802
|
-
base: string,
|
|
803
|
-
deferred: readonly NoteInput[],
|
|
804
|
-
): string =>
|
|
805
|
-
`${buildReviewAwaitReviewMessage(base)}
|
|
806
|
-
Deferred to \`.gtd/TODO.md\` just now, NOT acted on in this process — landing
|
|
807
|
-
clean signs off without them:
|
|
808
|
-
|
|
809
|
-
${deferred.map((n) => `- ${noteHead(n)}: ${n.text.replace(/\s*\n\s*/g, " ")}`).join("\n")}
|
|
810
|
-
`
|
|
811
|
-
|
|
812
912
|
export const buildReviewTriageMessage = (): string =>
|
|
813
913
|
`Judging each note the human added to \`.gtd/REVIEW.md\` (a line note, a
|
|
814
914
|
footnote, or prose under a chunk) with one verdict: \`edit\` (a change
|
|
@@ -819,12 +919,14 @@ default; a skipped judgment never dismisses a note).
|
|
|
819
919
|
|
|
820
920
|
Also judge each note's severity, one id per note: \`note-<n>.security\`,
|
|
821
921
|
\`note-<n>.reversibility\`, \`note-<n>.blastRadius\` and
|
|
822
|
-
\`note-<n>.newConcept\`.
|
|
922
|
+
\`note-<n>.newConcept\`. An own answer under a finding's boxes is judged
|
|
923
|
+
the same way, keyed \`answer-<n>\`.
|
|
823
924
|
`
|
|
824
925
|
const closedFold = (round: CollectRound, target: string): string =>
|
|
825
926
|
round === "edits"
|
|
826
927
|
? `- Fold every hand-edit and every note the human left that round into
|
|
827
|
-
\`${target}\`
|
|
928
|
+
\`${target}\` — a note ticked \`fix as suggested\` or answered in its
|
|
929
|
+
own words included; one ticked \`won't fix\` is dropped
|
|
828
930
|
`
|
|
829
931
|
: round === "notes"
|
|
830
932
|
? `- Fold each note that asks for a change into \`${target}\`
|
|
@@ -841,6 +943,7 @@ export const buildReviewCollectingPrompt = (
|
|
|
841
943
|
/** `"reply"` is the in-gate reply turn; any other round is closed and folds what that round left. */
|
|
842
944
|
readonly round: CollectRound
|
|
843
945
|
},
|
|
946
|
+
round?: CollectRound,
|
|
844
947
|
): string => {
|
|
845
948
|
const target = refactor ? ".gtd/REFACTOR.md" : ".gtd/REQUIREMENTS.md"
|
|
846
949
|
const fence = refactor
|
|
@@ -925,12 +1028,20 @@ ${closedFold(refactor.round, target)}`
|
|
|
925
1028
|
: `- This turn writes \`${target}\` (the folded concerns) and
|
|
926
1029
|
replies inside \`.gtd/REVIEW.md\` (thread replies only; the file stays
|
|
927
1030
|
in the tree) — you classify, you do not build
|
|
928
|
-
- Fold every concluded thread
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
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\`
|
|
1040
|
+
`
|
|
1041
|
+
: `- Never fold a note's ticked answer box the capture does not list: gtd
|
|
1042
|
+
routes those itself
|
|
933
1043
|
`
|
|
1044
|
+
}`
|
|
934
1045
|
}- When the capture names an answering commit, carry each answered
|
|
935
1046
|
question into \`${target}\`'s \`## Answered Questions\` as
|
|
936
1047
|
\`### <the note>\` plus its answer — the lap replaces \`.gtd/REVIEW.md\`
|
|
@@ -23,6 +23,7 @@ export const defaults: Readonly<Record<string, string>> = {
|
|
|
23
23
|
judgeIdenticalMinP: "0.7",
|
|
24
24
|
reviewNoteActionable: "0.7",
|
|
25
25
|
reviewSeverityMinP: "0.7",
|
|
26
|
+
qualityTriageMinP: "0.7",
|
|
26
27
|
refactorFlagPattern: "\\bGTD_FLAG_\\w+",
|
|
27
28
|
seamAllow: [...testFileGlobs, ".gtd/**"].join(", "),
|
|
28
29
|
flagMode: "true",
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { read, refuse, removeScript, run, scope, showScript, vars } from "../flows.mjs";
|
|
2
|
-
import { fixFindings, lensEntriesOf, lensOf, qualityLap as qualityLap$1, reviewToHandOff } from "../workflow-kit.mjs";
|
|
3
|
-
import { C as fixCheck, D as reviewMissing, O as reviewQuality, T as fixRisks, Y as handOffAwaitReviewMessage, d as guarded, f as healthy, h as QUALITY, k as reviewing, l as escalation, w as fixQuality, x as awaitReview, y as REVIEW } from "./reserved-Ch4LLrEj.mjs";
|
|
4
|
-
//#region src/workflows/shared/tail.ts
|
|
5
|
-
/** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
|
|
6
|
-
const REVIEW_ORIG = ".gtd/REVIEW.orig.md";
|
|
7
|
-
/**
|
|
8
|
-
* One review turn per lens over the whole change, each appending every
|
|
9
|
-
* finding to `.gtd/QUALITY.md`. Resolves `"findings"` when that file
|
|
10
|
-
* has any.
|
|
11
|
-
*/
|
|
12
|
-
const qualityLap = (guard, lenses, base) => qualityLap$1(lenses ?? lensEntriesOf(vars.qualityReviews ?? ""), guarded((entry) => {
|
|
13
|
-
const { lens, model } = lensOf(entry);
|
|
14
|
-
return reviewQuality(lens, base, model);
|
|
15
|
-
}, guard), QUALITY);
|
|
16
|
-
/** Resolves `true` once `.gtd/QUALITY.md` is resolved, `false` when the fix cap escalated instead. */
|
|
17
|
-
const fixQualityFindings = (escalations, guard, guardRests = true, rules) => fixFindings({
|
|
18
|
-
fix: guarded(() => fixQuality(rules), guard),
|
|
19
|
-
findings: QUALITY,
|
|
20
|
-
cap: 3,
|
|
21
|
-
escalate: () => escalation(escalations, guard, guardRests, rules)
|
|
22
|
-
});
|
|
23
|
-
/** `.gtd/REVIEW.md` removed at the end of a round at commit `round`. */
|
|
24
|
-
const closeReview = (round) => run("review.closing", removeScript([REVIEW, REVIEW_ORIG]), {
|
|
25
|
-
label: "Closing the review",
|
|
26
|
-
base: round
|
|
27
|
-
});
|
|
28
|
-
/** Refuses while a reviewer's notes still await `gtd door fix`. */
|
|
29
|
-
const refusePendingHandOff = () => {
|
|
30
|
-
if (read(".gtd/REVIEW.orig.md") !== void 0) refuse(`gtd: ${REVIEW_ORIG} is a pending hand-off — run \`gtd door fix\`, or remove ${REVIEW} and ${REVIEW_ORIG}.`);
|
|
31
|
-
};
|
|
32
|
-
function tail(first, second, ...rest) {
|
|
33
|
-
const form = tailForm(first, second, rest);
|
|
34
|
-
if (form === void 0) return Promise.reject(/* @__PURE__ */ new Error("tail takes tail(finish, options?) or the deprecated tail(fixFirst, finish, green?, guard?, lenses?, guardRests?, lensBase?, rules?); this call fits neither"));
|
|
35
|
-
const [finish, options] = form;
|
|
36
|
-
return runTail(finish, options);
|
|
37
|
-
}
|
|
38
|
-
const isOptionalObject = (value) => value == null || typeof value === "object" && !Array.isArray(value) && typeof value.then !== "function";
|
|
39
|
-
const tailForm = (first, second, rest) => {
|
|
40
|
-
if (typeof second === "function" && typeof first !== "function") {
|
|
41
|
-
const [green, guard, lenses, guardRests, lensBase, rules] = rest;
|
|
42
|
-
return [second, {
|
|
43
|
-
fixFirst: first,
|
|
44
|
-
green,
|
|
45
|
-
guard,
|
|
46
|
-
lenses,
|
|
47
|
-
guardRests,
|
|
48
|
-
lensBase,
|
|
49
|
-
rules
|
|
50
|
-
}];
|
|
51
|
-
}
|
|
52
|
-
if (typeof first === "function" && isOptionalObject(second) && rest.every((v) => v == null)) return [first, second ?? {}];
|
|
53
|
-
};
|
|
54
|
-
const runTail = (finish, options) => scope("build", async () => {
|
|
55
|
-
const { guard, lenses, lensBase, rules } = options;
|
|
56
|
-
const guardRests = options.guardRests === void 0 ? true : options.guardRests;
|
|
57
|
-
const escalations = { rounds: 0 };
|
|
58
|
-
const fix = () => fixCheck(rules);
|
|
59
|
-
const repair = async () => {
|
|
60
|
-
await guarded(fix, guard)();
|
|
61
|
-
await healthy(fix, {
|
|
62
|
-
fixesSoFar: 1,
|
|
63
|
-
escalations,
|
|
64
|
-
guard,
|
|
65
|
-
guardRests,
|
|
66
|
-
rules
|
|
67
|
-
});
|
|
68
|
-
};
|
|
69
|
-
if (options.fixFirst) await repair();
|
|
70
|
-
else if (!options.green) await healthy(fix, {
|
|
71
|
-
escalations,
|
|
72
|
-
guard,
|
|
73
|
-
guardRests,
|
|
74
|
-
rules
|
|
75
|
-
});
|
|
76
|
-
if (await qualityLap(guard, lenses, lensBase) === "findings") if (await fixQualityFindings(escalations, guard, guardRests, rules)) await healthy(fix, {
|
|
77
|
-
escalations,
|
|
78
|
-
guard,
|
|
79
|
-
guardRests,
|
|
80
|
-
rules
|
|
81
|
-
});
|
|
82
|
-
else await repair();
|
|
83
|
-
return finish(escalations);
|
|
84
|
-
});
|
|
85
|
-
/**
|
|
86
|
-
* The review and fix workflows' human review since `base`: a sign-off closes
|
|
87
|
-
* `.gtd/REVIEW.md`; a note leaves it, with the reviewer's own version as
|
|
88
|
-
* `REVIEW_ORIG`, for the next `fix` to read — never a re-plan.
|
|
89
|
-
*/
|
|
90
|
-
const handOffReview = (base, escalations, guard) => reviewToHandOff(base, {
|
|
91
|
-
file: REVIEW,
|
|
92
|
-
reviewing: guarded((b, carry) => reviewing(b, carry), guard),
|
|
93
|
-
fixRisks: guarded(fixRisks, guard),
|
|
94
|
-
keepGreen: () => healthy(fixCheck, {
|
|
95
|
-
escalations,
|
|
96
|
-
guard
|
|
97
|
-
}),
|
|
98
|
-
awaitReview: (b) => awaitReview(b, handOffAwaitReviewMessage(b)),
|
|
99
|
-
reviewMissing,
|
|
100
|
-
close: closeReview,
|
|
101
|
-
handOff: (reviewed) => run("review.handing-off", showScript(reviewed, REVIEW, REVIEW_ORIG), { label: "Handing your notes to fix" }),
|
|
102
|
-
threadsPending: "gtd land: open-threads: a thread in .gtd/REVIEW.md or an `H:` code comment waits on the agent — nothing answers it here; delete it, or turn it into a note for fix.",
|
|
103
|
-
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.",
|
|
104
|
-
deleted: "gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note to hand to fix."
|
|
105
|
-
});
|
|
106
|
-
/** The tail the review and fix workflows run: ends at the hand-off review since `base`. */
|
|
107
|
-
const handOffTail = (fixFirst, base, guard) => tail((escalations) => handOffReview(base, escalations, guard), {
|
|
108
|
-
fixFirst,
|
|
109
|
-
guard
|
|
110
|
-
});
|
|
111
|
-
//#endregion
|
|
112
|
-
export { refusePendingHandOff as a, isOptionalObject as i, closeReview as n, tail as o, handOffTail as r, REVIEW_ORIG as t };
|