@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/README.md
CHANGED
|
@@ -339,10 +339,10 @@ Then, in any repository:
|
|
|
339
339
|
```
|
|
340
340
|
|
|
341
341
|
That starts a process from your requirements and drives it until it needs you.
|
|
342
|
-
The requirements go to the top of `.gtd/TODO.md
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
342
|
+
The requirements go to the top of `.gtd/TODO.md`, above whatever it already
|
|
343
|
+
holds. A long, multi-line requirements paste still starts a process; if Claude
|
|
344
|
+
Code never expanded the paste, gtd says it could not read it, and you add the
|
|
345
|
+
requirements to the top of `.gtd/TODO.md` and run `/gtd`. `/gtd fix`,
|
|
346
346
|
`/gtd review [base]`, `/gtd rollout <flag>` and `/gtd cleanup <flag>` (where the
|
|
347
347
|
config exports them) take the doors described below; text whose word count does
|
|
348
348
|
not fit a door's args (bare `/gtd rollout` included) starts a process from that
|
|
@@ -522,37 +522,41 @@ noted in steps 3 and 4 below.
|
|
|
522
522
|
|
|
523
523
|
4. **You review.** After the last slice's quality lap gtd writes the review
|
|
524
524
|
document itself (`build.review.composing`; no reviewer turn runs): one `## `
|
|
525
|
-
chunk per design note the lap left,
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
525
|
+
chunk per design note the lap left, each ending in three tick boxes — **fix
|
|
526
|
+
as suggested**, **won't fix**, or **your own answer** written over
|
|
527
|
+
`_your answer_` — then, for reference, no tick boxes, no answer needed — what
|
|
528
|
+
was **fixed automatically** (with the fixing commit) and what was **dropped
|
|
529
|
+
as noise**. On `refactor-first` the refactor slice gets one of its own first,
|
|
530
|
+
before the feature is built; see [the naive pass](#the-naive-pass). Review
|
|
531
|
+
the diff yourself (`gtd base` prints its base). **Every note needs exactly
|
|
532
|
+
one tick before `gtd land` accepts the review** (`answer-completeness`; an
|
|
533
|
+
open `- H:` thread waiting on the agent is the one exception). Fix as
|
|
534
|
+
suggested joins the nit batch below, unjudged; won't fix drops the note and
|
|
535
|
+
records it as `Gtd-Mark: wontfix=<title>` on the gate's commit; your own
|
|
536
|
+
answer is judged like a note. A note you answered is left out of the fresh
|
|
537
|
+
review after a nit fix; a round that also answered a question restores the
|
|
538
|
+
review instead, your ticks kept. Land with every note answered and nothing
|
|
539
|
+
else changed to approve, or write what is wrong — disagreeing with a fix or a
|
|
540
|
+
drop means writing a note of your own. Approving ends the process (at the
|
|
541
|
+
refactor review, it moves on to the feature); feedback is judged note by
|
|
542
|
+
note, each as `edit`, `question`, `nit` or `praise`. An `edit` sends the
|
|
543
|
+
process back to step 2 for re-derived requirements — the built criteria keep
|
|
544
|
+
their IDs (reworded in place when their behaviour changes) and new behaviour
|
|
545
|
+
gets new ones — then the implementation is amended to meet them, with no new
|
|
546
|
+
naive pass or seam judge, every criterion's coverage is judged again, the
|
|
547
|
+
quality lap runs, and you review again. At the refactor review an `edit`
|
|
548
|
+
re-derives the refactor instead. A `question` is answered inline under your
|
|
549
|
+
note and the process stops at the review again — no new plan. A `nit` is
|
|
550
|
+
fixed in one batch, the suite must go green (a red one gets fix turns first),
|
|
551
|
+
then a fresh review of the change stops at the review again. `praise` is
|
|
552
|
+
dropped; a round of only praise signs off. When a round mixes them, questions
|
|
553
|
+
are answered and nits fixed first, then the edits are planned — risk: the
|
|
554
|
+
planning lap may redo nit fixes it touches.
|
|
547
555
|
|
|
548
556
|
Each `edit` note also gets a severity tier from four judge questions
|
|
549
|
-
(security, reversibility, blast radius, new concept).
|
|
550
|
-
|
|
551
|
-
a note forces `critical` and skips the judge.
|
|
552
|
-
committed `.gtd/TODO.md`; it never starts a process on its own, and it ships
|
|
553
|
-
in the branch/PR unless you delete it. Landing clean at the gate after a
|
|
554
|
-
deferral signs off without the deferred notes; the gate lists them. To pull
|
|
555
|
-
one in, edit its line in `.gtd/TODO.md` when you start a process.
|
|
557
|
+
(security, reversibility, blast radius, new concept). Every tier is
|
|
558
|
+
re-planned this round, `critical` first, then `should`, then `could`; nothing
|
|
559
|
+
is deferred. A leading `!` on a note forces `critical` and skips the judge.
|
|
556
560
|
`reviewSeverityMinP` sets the confidence floor (see
|
|
557
561
|
[configuration](docs/configuration.md)). A hand-edit to code always plans a
|
|
558
562
|
lap. Only a confident non-`edit` verdict skips the replan — a note whose
|
|
@@ -584,24 +588,28 @@ the agent may overturn the answer. See
|
|
|
584
588
|
for its `ui:` settings.
|
|
585
589
|
|
|
586
590
|
The bundled workflows are named `feature` (the ordinary start above), `review`
|
|
587
|
-
and `fix`.
|
|
588
|
-
|
|
591
|
+
and `fix`. `review` and `fix` never plan. Each brings its own door. This
|
|
592
|
+
command:
|
|
589
593
|
|
|
590
594
|
```bash
|
|
591
595
|
gtd door review <commitish>
|
|
592
596
|
```
|
|
593
597
|
|
|
594
|
-
starts a
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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.
|
|
605
613
|
`gtd --workflow <name>` starts any workflow by name.
|
|
606
614
|
|
|
607
615
|
The workflow itself is a plain async TypeScript function, and the
|
|
@@ -740,6 +748,7 @@ full trail with `gtd history fetch | sh`, then `gtd history show <ref>`.
|
|
|
740
748
|
gtd history list # one line per kept process
|
|
741
749
|
gtd history show <ref> # replay its steps, judge verdicts and approvals
|
|
742
750
|
gtd history metrics [ref...] # how the kept processes went, per process and in total
|
|
751
|
+
gtd history judges [ref...] # how often each judge's verdicts were overturned
|
|
743
752
|
gtd history fetch | sh # pull every kept ref from origin
|
|
744
753
|
```
|
|
745
754
|
|
|
@@ -772,6 +781,45 @@ process kept from that branch. A rate is `null` without a decided slice. Each
|
|
|
772
781
|
process names its `workflow` (`null` for the default one); the total spans every
|
|
773
782
|
ref measured, so name the refs to leave another workflow's processes out.
|
|
774
783
|
|
|
784
|
+
`gtd history judges` calibrates three of the bundled `feature` workflow's judges
|
|
785
|
+
from the `Gtd-Judge` verdicts the kept refs recorded. It never replays and asks
|
|
786
|
+
no model. A verdict is **overturned** when later history contradicts it:
|
|
787
|
+
|
|
788
|
+
- **retry** (`health.judge`) — an `identical` verdict, when the next fix turn's
|
|
789
|
+
check went green anyway (no outcome when a person edited code in between)
|
|
790
|
+
- **triage** (`quality.triage`) — a `mechanical` or `noise` verdict the review
|
|
791
|
+
listed under _Fixed automatically_ or _Dropped as noise_, when the human
|
|
792
|
+
edited that finding's line there or wrote under it
|
|
793
|
+
- **clear** (`design.clear`) — a `yes` that skipped design (landed on
|
|
794
|
+
`design.normalise`), when a later human design step in the same process
|
|
795
|
+
changed the acceptance criteria
|
|
796
|
+
|
|
797
|
+
Any other verdict has no outcome. The report counts verdicts, scored verdicts
|
|
798
|
+
and overturns per judge and per `p` bucket (`0.8-0.9: 1/2` — one of two
|
|
799
|
+
overturned). `--var <threshold>=<value>` re-decides every past verdict under
|
|
800
|
+
another threshold and prints the decisions that would change, against the
|
|
801
|
+
threshold each process recorded (a process that recorded none is left out):
|
|
802
|
+
|
|
803
|
+
```sh
|
|
804
|
+
gtd history judges --var qualityTriageMinP=0.9
|
|
805
|
+
# what-if qualityTriageMinP=0.9: 7 noise drops become notes (2 overturned)
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
The thresholds are `judgeIdenticalMinP` (retry) and `qualityTriageMinP`
|
|
809
|
+
(triage). The judge's evidence-budget cut is not recorded, so a cut verdict is
|
|
810
|
+
scored and re-decides as if it was whole. A retry verdict on an empty report is
|
|
811
|
+
not scored, as the flow drops it. `--json` prints
|
|
812
|
+
`{ processes, total, whatIf }`, each process and the total with its `judges`.
|
|
813
|
+
`--jsonl` prints one line per verdict instead — `process`, `judge`, `commit`,
|
|
814
|
+
`step`, `question`, `state` (the evidence the tree still shows: the finding, the
|
|
815
|
+
two check reports, the sketch without its numbered criteria), `answer`, `p` and
|
|
816
|
+
`outcome` (`overturned`, `held` or `null`) — ready to fine-tune a local model
|
|
817
|
+
on:
|
|
818
|
+
|
|
819
|
+
```sh
|
|
820
|
+
gtd history judges --jsonl > judges.jsonl
|
|
821
|
+
```
|
|
822
|
+
|
|
775
823
|
`gtd history fetch` only prints a fetch script; pipe it into `sh` to run it.
|
|
776
824
|
Beyond that one ship push, refs are not pushed or fetched by default. The ref
|
|
777
825
|
holds every step commit, including steering files and code a later step removed,
|
|
@@ -828,8 +876,8 @@ install for any of these but `./testkit`: gtd resolves a `gtd.config.ts`'s
|
|
|
828
876
|
The `gtd` bin's commands — `next`, `land`, `abandon`, `restore`, `validate`,
|
|
829
877
|
`check`, `uncheck`, `install`, `summary`, `base`, `door`, `doors`, `slices`,
|
|
830
878
|
`version`, `help`, `history list`, `history show`, `history metrics`,
|
|
831
|
-
`history fetch`, `strays`, and the tools `ui`, `lsp`, `loop`,
|
|
832
|
-
`judge` (`judge answer`, `judge run`) — are listed in the
|
|
879
|
+
`history judges`, `history fetch`, `strays`, and the tools `ui`, `lsp`, `loop`,
|
|
880
|
+
`context-hook`, `judge` (`judge answer`, `judge run`) — are listed in the
|
|
833
881
|
[CLI reference](https://github.com/pmelab/gtd/blob/main/docs/cli.md).
|
|
834
882
|
`gtd slices` lists the spans a flow's `slice()` calls mark. It refuses (exit 1)
|
|
835
883
|
when HEAD neither closes nor sits inside a process, or when today's config
|
package/claude/hooks/sketch.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// New requirements go above what the steering file already holds, untouched:
|
|
2
|
-
//
|
|
2
|
+
// anything already there that the start diff alters reads as the human's edit.
|
|
3
3
|
export function withRequirements(existing: string, requirements: string): string {
|
|
4
4
|
const head = `${requirements.trimEnd()}\n`
|
|
5
5
|
return existing.trim() ? `${head}\n${existing}` : head
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { accessFor, agent, answered, changesSince, check, codeThreads, defineWorkflow, displayPath, env, head, human, judge, mark, numeric, quote, read, refuse, scope, skillsFor, start, vars } from "../flows.mjs";
|
|
2
|
+
import { d as ANSWER_FIX, f as ANSWER_LEAD, p as ANSWER_WONT_FIX, v as FREE_TEXT_PLACEHOLDER } from "./steering-D4LUQ-av.mjs";
|
|
2
3
|
import { escalation as escalation$1, gate as gate$1, healthy as healthy$1, lensScopes, lensesOf, removedAssertions } from "../workflow-kit.mjs";
|
|
3
4
|
import { c as String$, g as standardSchemaV1, h as optionalWith, l as Struct, n as Boolean$, s as Number$ } from "./Schema-C6fLlQzx.mjs";
|
|
4
5
|
//#region src/workflows/shared/registry.ts
|
|
@@ -437,9 +438,9 @@ const SUMMARY_TITLES = [
|
|
|
437
438
|
const unboxed = (body) => body.replace(/^(\s*(?:>\s*)*(?:[-*+]|\d+[.)])\s+)\[[ xX]\]\s?/gm, "$1");
|
|
438
439
|
const short = (hash) => hash.slice(0, 7);
|
|
439
440
|
/**
|
|
440
|
-
* \`.gtd/REVIEW.md\` as gtd writes it: one chunk per note,
|
|
441
|
-
*
|
|
442
|
-
* matched to their chunk by title.
|
|
441
|
+
* \`.gtd/REVIEW.md\` as gtd writes it: one chunk per note, each ending in its
|
|
442
|
+
* answer boxes, then the fixed and noise lists. Chunk titles are made unique —
|
|
443
|
+
* notes and answers are matched to their chunk by title.
|
|
443
444
|
*/
|
|
444
445
|
const composedReview = (base, report) => {
|
|
445
446
|
const used = new Set(SUMMARY_TITLES);
|
|
@@ -450,11 +451,16 @@ const composedReview = (base, report) => {
|
|
|
450
451
|
return name;
|
|
451
452
|
};
|
|
452
453
|
const chunk = (title, body) => `## ${title}\n\n${body.trim()}`;
|
|
454
|
+
const answerBoxes = `${ANSWER_LEAD}\n\n- [ ] ${ANSWER_FIX}\n- [ ] ${ANSWER_WONT_FIX}\n- [ ] ${FREE_TEXT_PLACEHOLDER}`;
|
|
455
|
+
const notes = report.notes.map((n) => ({
|
|
456
|
+
...n,
|
|
457
|
+
title: unique(n.title)
|
|
458
|
+
})).filter((n) => report.answered?.has(n.title) !== true);
|
|
453
459
|
const weakened = (report.weakened ?? []).flatMap(({ path, lines }) => lines.map((a) => `- ${path} — ${a.scenario} — ${a.line}`));
|
|
454
460
|
const chunks = [
|
|
455
|
-
...report.nitFix === void 0 ? [] : [chunk("Fixed from your notes", `Your nits were
|
|
461
|
+
...report.nitFix === void 0 ? [] : [chunk("Fixed from your notes", `Your nits and fix-as-suggested notes were worked on in ${short(report.nitFix)}; the notes you answered are left out — check the diff.`)],
|
|
456
462
|
...weakened.length === 0 ? [] : [chunk("Weakened scenarios", `These scenarios lost an assertion the review base had:\n\n${weakened.join("\n")}`)],
|
|
457
|
-
...
|
|
463
|
+
...notes.map((n) => chunk(n.title, `${n.unresolved === true ? `_The fixer left this unresolved._\n\n${unboxed(n.body)}` : n.unclear === true ? `_A fix turn removed this while renaming another finding — check whether it was fixed._\n\n${unboxed(n.body)}` : unboxed(n.body) || "_No detail given._"}\n\n${answerBoxes}`)),
|
|
458
464
|
...report.fixed.length === 0 ? [] : [chunk("Fixed automatically", report.fixed.map((f) => `- ${f.title} (${short(f.commit)})`).join("\n"))],
|
|
459
465
|
...report.noise.length === 0 ? [] : [chunk("Dropped as noise", report.noise.map((f) => `- ${f.title} (p=${f.p})`).join("\n"))]
|
|
460
466
|
];
|
|
@@ -537,10 +543,6 @@ ${stateFileRules}
|
|
|
537
543
|
${footnoteFoldIn}${codeThreadReplies()}
|
|
538
544
|
- The only state file this turn touches is \`.gtd/REQUIREMENTS.md\`
|
|
539
545
|
— no other files for notes or output
|
|
540
|
-
- Lines of a \`## Deferred review notes\` section of \`.gtd/TODO.md\`
|
|
541
|
-
that the start diff does not touch are parked by review, never folded;
|
|
542
|
-
the human's edits to such a line (or its removal) are input like any
|
|
543
|
-
other part of the start diff
|
|
544
546
|
- \`.gtd/TODO.md\` is the likely home of the sketch that started
|
|
545
547
|
this process — the human's input, folded into the concerns
|
|
546
548
|
below like any other part of the start diff; never a state
|
|
@@ -833,8 +835,12 @@ const buildReviewAwaitReviewMessage = (base) => `\`.gtd/REVIEW.md\` holds what t
|
|
|
833
835
|
per finding that needs your judgement — it questions a criterion or the
|
|
834
836
|
approach, or the fixer could not resolve it — then, for reference only, what
|
|
835
837
|
was **fixed automatically** (with the fixing commit) and what was **dropped
|
|
836
|
-
as noise**.
|
|
837
|
-
|
|
838
|
+
as noise**. Each finding chunk ends in three boxes — tick exactly one:
|
|
839
|
+
\`fix as suggested\` (fixed in the nit batch below), \`won't fix\` (dropped,
|
|
840
|
+
recorded in the commit), or write your own answer over \`_your answer_\` and
|
|
841
|
+
tick it (judged like a note). Every landing here is refused until each
|
|
842
|
+
finding holds exactly one tick. The fixed and noise lists need no answer;
|
|
843
|
+
disagreeing with a fix or a drop means writing a note of your own.
|
|
838
844
|
|
|
839
845
|
Review the diff yourself, with whatever tool you like — gtd checks
|
|
840
846
|
nothing out and touches no ref; \`gtd base\` prints this same hash any
|
|
@@ -844,8 +850,8 @@ working tree:
|
|
|
844
850
|
git diff ${base}
|
|
845
851
|
When you've been through the whole diff, run \`gtd land\`:
|
|
846
852
|
|
|
847
|
-
- **Sign off** — leave no comment — no note
|
|
848
|
-
\`.gtd/REVIEW.md\`, no code edit — to close the process.
|
|
853
|
+
- **Sign off** — answer every finding, leave no other comment — no note
|
|
854
|
+
in \`.gtd/REVIEW.md\`, no code edit — to close the process.
|
|
849
855
|
Every turn commit stays on the
|
|
850
856
|
branch; run \`gtd summary\` afterward for a closing-message
|
|
851
857
|
prompt.
|
|
@@ -972,6 +978,15 @@ ${notes.length === 0 ? "(none)\n" : notesCapture(notes)}
|
|
|
972
978
|
The reviewer's risks are:
|
|
973
979
|
|
|
974
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
|
+
`;
|
|
975
990
|
const buildReviewReviewMissingMessage = (commit) => `The review round committed no \`.gtd/REVIEW.md\` (at ${commit}), so there is
|
|
976
991
|
nothing to sign off on.
|
|
977
992
|
|
|
@@ -992,27 +1007,6 @@ const wrapItem = (item) => {
|
|
|
992
1007
|
} else line = line === "" ? word : `${line} ${word}`;
|
|
993
1008
|
return [...out, line];
|
|
994
1009
|
};
|
|
995
|
-
/** Appends one section of `notes` to `.gtd/TODO.md`, wrapped the way oxfmt writes it; an existing file is only ever appended to. */
|
|
996
|
-
const deferNotesScript = (notes, round) => {
|
|
997
|
-
return `mkdir -p .gtd
|
|
998
|
-
if [ -s .gtd/TODO.md ]; then
|
|
999
|
-
[ -z "$(tail -c1 .gtd/TODO.md)" ] || printf '\\n' >> .gtd/TODO.md
|
|
1000
|
-
printf '\\n' >> .gtd/TODO.md
|
|
1001
|
-
fi
|
|
1002
|
-
printf '%s\\n' ${[
|
|
1003
|
-
`## Deferred review notes (${round.slice(0, 7)})`,
|
|
1004
|
-
"",
|
|
1005
|
-
...notes.flatMap((n) => wrapItem(`- ${noteHead(n)}: ${n.text.replace(/\s+/g, " ")}`))
|
|
1006
|
-
].map(quote).join(" ")} >> .gtd/TODO.md
|
|
1007
|
-
`;
|
|
1008
|
-
};
|
|
1009
|
-
/** The gate right after a deferral: landing clean signs off without these notes. */
|
|
1010
|
-
const buildReviewAwaitAfterDeferMessage = (base, deferred) => `${buildReviewAwaitReviewMessage(base)}
|
|
1011
|
-
Deferred to \`.gtd/TODO.md\` just now, NOT acted on in this process — landing
|
|
1012
|
-
clean signs off without them:
|
|
1013
|
-
|
|
1014
|
-
${deferred.map((n) => `- ${noteHead(n)}: ${n.text.replace(/\s*\n\s*/g, " ")}`).join("\n")}
|
|
1015
|
-
`;
|
|
1016
1010
|
const buildReviewTriageMessage = () => `Judging each note the human added to \`.gtd/REVIEW.md\` (a line note, a
|
|
1017
1011
|
footnote, or prose under a chunk) with one verdict: \`edit\` (a change
|
|
1018
1012
|
request), \`question\`, \`nit\` (a small fix needing no re-plan) or
|
|
@@ -1022,16 +1016,18 @@ default; a skipped judgment never dismisses a note).
|
|
|
1022
1016
|
|
|
1023
1017
|
Also judge each note's severity, one id per note: \`note-<n>.security\`,
|
|
1024
1018
|
\`note-<n>.reversibility\`, \`note-<n>.blastRadius\` and
|
|
1025
|
-
\`note-<n>.newConcept\`.
|
|
1019
|
+
\`note-<n>.newConcept\`. An own answer under a finding's boxes is judged
|
|
1020
|
+
the same way, keyed \`answer-<n>\`.
|
|
1026
1021
|
`;
|
|
1027
1022
|
const closedFold = (round, target) => round === "edits" ? `- Fold every hand-edit and every note the human left that round into
|
|
1028
|
-
\`${target}\`
|
|
1023
|
+
\`${target}\` — a note ticked \`fix as suggested\` or answered in its
|
|
1024
|
+
own words included; one ticked \`won't fix\` is dropped
|
|
1029
1025
|
` : round === "notes" ? `- Fold each note that asks for a change into \`${target}\`
|
|
1030
1026
|
` : `- Fold each \`edit\` note the capture lists, no other note, and every
|
|
1031
1027
|
hand-edit, into \`${target}\`
|
|
1032
1028
|
`;
|
|
1033
1029
|
/** `refactor` retargets the prompt at `.gtd/REFACTOR.md` and fences it to the strays. A concern outside the fence is not folded; if nothing at all is written the landing is refused, but a partly buildable round drops the rest silently. */
|
|
1034
|
-
const buildReviewCollectingPrompt = (capture, refactor) => {
|
|
1030
|
+
const buildReviewCollectingPrompt = (capture, refactor, round) => {
|
|
1035
1031
|
const target = refactor ? ".gtd/REFACTOR.md" : ".gtd/REQUIREMENTS.md";
|
|
1036
1032
|
const fence = refactor ? `- The next refactor may change only these paths, plus an existing test
|
|
1037
1033
|
file's call sites, and never a flag read:
|
|
@@ -1106,12 +1102,16 @@ ${refactor ? refactor.round === "reply" ? `- This turn replies inside \`.gtd/REV
|
|
|
1106
1102
|
${closedFold(refactor.round, target)}` : `- This turn writes \`${target}\` (the folded concerns) and
|
|
1107
1103
|
replies inside \`.gtd/REVIEW.md\` (thread replies only; the file stays
|
|
1108
1104
|
in the tree) — you classify, you do not build
|
|
1109
|
-
- Fold every concluded thread
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1105
|
+
- Fold every concluded thread and hand-edit — and each \`edit\` note
|
|
1106
|
+
the capture lists, no other note — into \`${target}\` and
|
|
1107
|
+
delete the folded threads from \`.gtd/REVIEW.md\`; append one \`- A:\`
|
|
1108
|
+
reply to each thread whose last entry is a \`- H:\` question
|
|
1109
|
+
${round === "edits" ? `- A hand-edit round routes no answer box itself: fold each note
|
|
1110
|
+
ticked \`fix as suggested\` or answered in its own words, too; drop one
|
|
1111
|
+
ticked \`won't fix\`
|
|
1112
|
+
` : `- Never fold a note's ticked answer box the capture does not list: gtd
|
|
1113
|
+
routes those itself
|
|
1114
|
+
`}`}- When the capture names an answering commit, carry each answered
|
|
1115
1115
|
question into \`${target}\`'s \`## Answered Questions\` as
|
|
1116
1116
|
\`### <the note>\` plus its answer — the lap replaces \`.gtd/REVIEW.md\`
|
|
1117
1117
|
- Keep capture order, and carry each note's \`(tier: reason)\`
|
|
@@ -1615,6 +1615,36 @@ const awaitReview = (base, message = buildReviewAwaitReviewMessage(base)) => hum
|
|
|
1615
1615
|
base,
|
|
1616
1616
|
acceptClean: true
|
|
1617
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
|
+
});
|
|
1618
1648
|
/** The round at commit `round` left no `.gtd/REVIEW.md`. */
|
|
1619
1649
|
const reviewMissing = (round) => human("review.review-missing", {
|
|
1620
1650
|
message: buildReviewReviewMissingMessage(round),
|
|
@@ -1811,6 +1841,7 @@ const skills = (vars) => {
|
|
|
1811
1841
|
return {
|
|
1812
1842
|
design: ["spec-driven-development", "planning-and-task-breakdown"],
|
|
1813
1843
|
notes: ["incremental-implementation", "code-simplification"],
|
|
1844
|
+
feedback: ["test-driven-development", "incremental-implementation"],
|
|
1814
1845
|
...build,
|
|
1815
1846
|
...Object.fromEntries(Object.entries(build).map(([name, list]) => [`refactor.${name}`, list])),
|
|
1816
1847
|
refactor: ["ponytail"],
|
|
@@ -1829,11 +1860,9 @@ const skills = (vars) => {
|
|
|
1829
1860
|
const access = (vars) => ({
|
|
1830
1861
|
design: { write: [] },
|
|
1831
1862
|
"build.review": { write: [".gtd/REVIEW.md"] },
|
|
1832
|
-
"build.review.defer": { write: [".gtd/TODO.md"] },
|
|
1833
1863
|
"build.review.fix.nits": {},
|
|
1834
1864
|
"build.review.fix.risks": {},
|
|
1835
1865
|
"refactor.build.review": { write: [".gtd/REVIEW.md"] },
|
|
1836
|
-
"refactor.build.review.defer": { write: [".gtd/TODO.md"] },
|
|
1837
1866
|
...lensScopes("build.quality", lensesOf(vars.qualityReviews ?? ""), () => ({ write: [] })),
|
|
1838
1867
|
...lensScopes("refactor.build.quality", lensesOf(vars.refactorQualityReviews ?? ""), () => ({ write: [] }))
|
|
1839
1868
|
});
|
|
@@ -1845,4 +1874,4 @@ const steering = {
|
|
|
1845
1874
|
[REVIEW]: "review"
|
|
1846
1875
|
};
|
|
1847
1876
|
//#endregion
|
|
1848
|
-
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 };
|