@pmelab/gtd 39.0.0 → 41.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 +57 -55
- package/dist/chunks/{reserved-B6tTYjeb.mjs → reserved-Ch4LLrEj.mjs} +15 -9
- package/dist/chunks/{tail-_CZTP7Hu.mjs → tail-lY1UmJrU.mjs} +1 -1
- package/dist/workflow-kit.mjs +29 -11
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +98 -37
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +2 -2
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +140 -55
- package/package.json +1 -1
- package/skills/authoring/SKILL.md +3 -2
- package/src/workflow-kit/health.ts +42 -13
- package/src/workflows/feature/coverage.feature +5 -5
- package/src/workflows/feature/criteria.test.ts +20 -1
- package/src/workflows/feature/criteria.ts +24 -0
- package/src/workflows/feature/fast-path.feature +4 -4
- package/src/workflows/feature/feature.ts +82 -22
- package/src/workflows/feature/flag-mode.feature +2 -2
- package/src/workflows/feature/one-step-land.feature +4 -4
- package/src/workflows/feature/planning.ts +3 -2
- package/src/workflows/feature/quality-unresolved.feature +6 -6
- 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 +2 -2
- package/src/workflows/feature/refactor-review.feature +8 -8
- package/src/workflows/feature/review-gate.feature +271 -0
- package/src/workflows/feature/seam-rejection.feature +4 -4
- package/src/workflows/feature/seam.feature +2 -2
- package/src/workflows/feature/seam.ts +17 -7
- package/src/workflows/feature/slicing.feature +63 -4
- package/src/workflows/shared/health.test.ts +30 -0
- package/src/workflows/shared/health.ts +12 -9
- package/src/workflows/shared/progress.feature +124 -0
- package/src/workflows/shared/text.ts +7 -5
- package/src/workflows/review/review-door.feature +0 -121
package/README.md
CHANGED
|
@@ -419,10 +419,9 @@ phone UI does not show them).
|
|
|
419
419
|
|
|
420
420
|
### The workflow it ships with
|
|
421
421
|
|
|
422
|
-
One built-in workflow drives all of that. From where you sit, it has
|
|
422
|
+
One built-in workflow drives all of that. From where you sit, it has four
|
|
423
423
|
moments — everything between them runs without you, with the judged exceptions
|
|
424
|
-
noted in
|
|
425
|
-
you open the `review` door.
|
|
424
|
+
noted in steps 3 and 4 below.
|
|
426
425
|
|
|
427
426
|
1. **You sketch.** Change anything, or write the idea into `.gtd/TODO.md`. Rough
|
|
428
427
|
is fine; it is treated as a sketch, not as work.
|
|
@@ -449,14 +448,12 @@ you open the `review` door.
|
|
|
449
448
|
against the full suite (`testCommand`) and fixed until it passes — so keep
|
|
450
449
|
that suite fast; gtd has no lighter one. Scenario wording is not frozen: a
|
|
451
450
|
review's `## Scenarios` chunk marks any assertion an existing scenario lost
|
|
452
|
-
as `Weakened
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
land with a clean tree to accept the conservative default, which never skips
|
|
459
|
-
work; `gtd judge run --provider fixed --answers <path>` — or the
|
|
451
|
+
as `Weakened:`, so you sign the scenarios off at step 4. After the build the
|
|
452
|
+
quality lap runs. Three points along the process (two with flag mode off) are
|
|
453
|
+
judged rather than always asking you outright — each stops and hands you a
|
|
454
|
+
verdict to make (`gtd judge answer`, or land with a clean tree to accept the
|
|
455
|
+
conservative default, which never skips work;
|
|
456
|
+
`gtd judge run --provider fixed --answers <path>` — or the
|
|
460
457
|
`GTD_JUDGE_ANSWERS` env var, inline JSON — answers one from a file, piped
|
|
461
458
|
between `gtd judge --json` and `gtd judge answer`;
|
|
462
459
|
`gtd judge run --provider jev` asks TypeSafe's Jev instead, with the key in
|
|
@@ -474,6 +471,8 @@ you open the `review` door.
|
|
|
474
471
|
or get rejected and retried?
|
|
475
472
|
- Every red round after the first: was the failure identical, new, or
|
|
476
473
|
progress?
|
|
474
|
+
- After you review: is each of your notes an edit, a question, a nit or
|
|
475
|
+
praise?
|
|
477
476
|
|
|
478
477
|
gtd itself never calls a judge: `gtd loop`, the Claude Code mod and the
|
|
479
478
|
reference driver answer these through `gtd judge run` (auto selection: jev
|
|
@@ -485,22 +484,20 @@ you open the `review` door.
|
|
|
485
484
|
and skip a gate unattended.**
|
|
486
485
|
|
|
487
486
|
Once the change is built, the whole change goes through a qualitative review
|
|
488
|
-
lap: six lenses, one turn each, in order —
|
|
489
|
-
`
|
|
490
|
-
`correctness`, `owasp-security` and
|
|
491
|
-
|
|
492
|
-
entry `<lens>=<model>` picks a
|
|
493
|
-
own
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
signs the finished change off unless you open the `review` door** (below).
|
|
503
|
-
The `fix` door repairs a red baseline through this same lap.
|
|
487
|
+
lap before you see anything: six lenses, one turn each, in order —
|
|
488
|
+
`correctness`, `owasp-security`, `ponytail-review`, `test-audit`,
|
|
489
|
+
`conventions`, `spec-challenge`. `correctness`, `owasp-security` and
|
|
490
|
+
`spec-challenge` run on the planner model (`smart`), the other three on the
|
|
491
|
+
coder model (`base`); a `qualityReviews` entry `<lens>=<model>` picks a
|
|
492
|
+
lens's own. Each traces the change from its own angle and records every
|
|
493
|
+
finding, blocking or not; one fix turn then fixes ALL of them once, with no
|
|
494
|
+
re-review after the fix. A clean turn means approval only when that lens
|
|
495
|
+
found nothing at all. This lap is where code quality is looked at, and every
|
|
496
|
+
round pays for it. It never replaces step 4 — your review stays the final
|
|
497
|
+
gate, and nothing here skips it. Findings the fix cap leaves in
|
|
498
|
+
`.gtd/QUALITY.md` rest at `build.quality-unresolved` until you delete or
|
|
499
|
+
empty the file; the suite then runs on any hand edits before the review. The
|
|
500
|
+
`fix` door (below) repairs a red baseline through this same lap.
|
|
504
501
|
|
|
505
502
|
A red suite that keeps failing past a few fix attempts escalates instead of
|
|
506
503
|
retrying forever: an agent turn reads the failing output and writes
|
|
@@ -509,33 +506,36 @@ you open the `review` door.
|
|
|
509
506
|
as-is, handing that document to the next fix turn as its instruction. Two
|
|
510
507
|
such rounds with no fix is the terminal stop: the last analysis stays on disk
|
|
511
508
|
instead of a third being written, and editing it is what gets the next
|
|
512
|
-
attempt anywhere new.
|
|
513
|
-
|
|
509
|
+
attempt anywhere new. A loop the retry judge still calls progress at the cap
|
|
510
|
+
gets one extra fix turn first. Before any escalation or red-baseline stop the
|
|
511
|
+
suite runs once more; a pass there continues without you and is recorded as
|
|
514
512
|
`Gtd-Mark: flaky=<check>` (see
|
|
515
513
|
[Re-check before a person](docs/configuration.md#re-check-before-a-person)).
|
|
516
514
|
|
|
517
|
-
4. **You review
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
hands notes to `fix` instead of re-planning (below). Either way you get a
|
|
522
|
-
review document listing what changed and what to look at. Before you see it,
|
|
515
|
+
4. **You review.** After the last slice's quality lap you get a review document
|
|
516
|
+
over the whole change, listing what changed and what to look at (on
|
|
517
|
+
`refactor-first` the refactor slice gets one of its own first, before the
|
|
518
|
+
feature is built; see [the naive pass](#the-naive-pass)). Before you see it,
|
|
523
519
|
an automatic risk-fix pass (`build.review.fix.risks.fixing`) runs. Any risk
|
|
524
520
|
the reviewer names (a note opening with `Risk:`) is fixed first, the suite
|
|
525
521
|
kept green, and the review rewritten — once per review round, so a risk the
|
|
526
522
|
rewrite still names reaches you unfixed; risk: a fix lands with no check that
|
|
527
|
-
the risk was real. Tick the boxes to approve, or write what is wrong.
|
|
528
|
-
refactor review,
|
|
529
|
-
|
|
530
|
-
`praise`. An `edit`
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
review
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
523
|
+
the risk was real. Tick the boxes to approve, or write what is wrong.
|
|
524
|
+
Approving ends the process (at the refactor review, it moves on to the
|
|
525
|
+
feature); feedback is judged note by note, each as `edit`, `question`, `nit`
|
|
526
|
+
or `praise`. An `edit` sends the process back to step 2 for re-derived
|
|
527
|
+
requirements — the built criteria keep their IDs (reworded in place when
|
|
528
|
+
their behaviour changes) and new behaviour gets new ones — then the
|
|
529
|
+
implementation is amended to meet them, with no new naive pass or seam judge,
|
|
530
|
+
every criterion's coverage is judged again, the quality lap runs, and you
|
|
531
|
+
review again. At the refactor review an `edit` re-derives the refactor
|
|
532
|
+
instead. A `question` is answered inline under your note and the process
|
|
533
|
+
stops at the review again — no new plan. A `nit` is fixed in one batch, the
|
|
534
|
+
suite must go green (a red one gets fix turns first), then a fresh review of
|
|
535
|
+
the change stops at the review again. `praise` is dropped; a round of only
|
|
536
|
+
praise signs off. When a round mixes them, questions are answered and nits
|
|
537
|
+
fixed first, then the edits are planned — risk: the planning lap may redo nit
|
|
538
|
+
fixes it touches.
|
|
539
539
|
|
|
540
540
|
Each `edit` note also gets a severity tier from four judge questions
|
|
541
541
|
(security, reversibility, blast radius, new concept). `critical` and `should`
|
|
@@ -555,7 +555,7 @@ you open the `review` door.
|
|
|
555
555
|
You never talk to it. Every exchange is a file in `.gtd/` that you edit in your
|
|
556
556
|
own editor, and every answer you give is a commit. Your test suite is the gate
|
|
557
557
|
throughout: a red baseline stops the process before it starts, and a red build
|
|
558
|
-
is fixed before
|
|
558
|
+
is fixed before review.
|
|
559
559
|
|
|
560
560
|
Prefer a phone or another device over your editor? `gtd ui` opens the file whose
|
|
561
561
|
turn it is as a small web app, over Tailscale — including a step that names no
|
|
@@ -635,14 +635,15 @@ criterion ID the naive pass's tests named, and the suite must be green with
|
|
|
635
635
|
them. A wrong approach is retried twice, then rests for you to steer or overrule
|
|
636
636
|
it. `gtd slices` lists the `refactor` and `feature` slices. A refactor that
|
|
637
637
|
already carried the whole feature leaves the rebuild only the carried-over
|
|
638
|
-
tests, and the feature's quality lap
|
|
638
|
+
tests, and the feature's quality lap and review cover the whole change. Once the
|
|
639
639
|
implementation is green, a judge checks every criterion ID has a covering test:
|
|
640
640
|
an uncovered one goes back to the agent (twice, then to you) instead of ending
|
|
641
641
|
the feature, and each covered ID is recorded as a `Gtd-Mark: covers=<id>`
|
|
642
642
|
trailer. See
|
|
643
643
|
[Acceptance-criteria coverage](docs/configuration.md#acceptance-criteria-coverage).
|
|
644
|
-
Feedback at the
|
|
645
|
-
|
|
644
|
+
Feedback at the feature review re-runs design and the implementation, never the
|
|
645
|
+
naive pass; feedback at the refactor review re-runs the refactor, never design;
|
|
646
|
+
a refactor-review change the refactor cannot build is refused, and the only way
|
|
646
647
|
out is `gtd abandon`. A process from 27.5.0 or later whose judge ruled
|
|
647
648
|
`refactor-first` must `gtd abandon` too. Each slice's quality lap reviews only
|
|
648
649
|
that slice. Details in
|
|
@@ -652,10 +653,11 @@ that slice. Details in
|
|
|
652
653
|
criteria than the cap is built in slices: after design an agent groups related
|
|
653
654
|
criteria (same flow or subsystem) into `.gtd/SLICES.md`, as few slices as the
|
|
654
655
|
cap allows, and each slice — naive pass, refactor and rebuild when judged so,
|
|
655
|
-
health loop, quality lap — runs in order from where the previous one landed
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
656
|
+
health loop, quality lap — runs in order from where the previous one landed; the
|
|
657
|
+
review follows the last one and covers the whole change. `gtd slices` lists them
|
|
658
|
+
as `feature-1`, `feature-2`, … (`refactor-<n>` ahead of a refactored one). A
|
|
659
|
+
change within the cap is one `feature` slice, as before. See
|
|
660
|
+
[Coarse slices](docs/configuration.md#coarse-slices).
|
|
659
661
|
|
|
660
662
|
**Flag mode (`flagMode`, on by default).** On, the naive pass's judge runs as
|
|
661
663
|
above and a rebuilt implementation is asked for a new release flag (none at a
|
|
@@ -421,14 +421,14 @@ What each change does next (then run \`gtd land\`):
|
|
|
421
421
|
- **Continue** — having undone the sketch by hand, check the test baseline is green and start triage (**start-gate.check**).
|
|
422
422
|
`;
|
|
423
423
|
const qualityUnresolvedMessage = () => `The quality lap's fix turns hit their cap and \`.gtd/QUALITY.md\` still
|
|
424
|
-
holds findings. Nothing
|
|
425
|
-
|
|
424
|
+
holds findings. Nothing after this fixes them, so the process will not
|
|
425
|
+
go on with them open.
|
|
426
426
|
|
|
427
427
|
Fix what matters by hand, then delete or empty \`.gtd/QUALITY.md\` —
|
|
428
428
|
dropping a finding is your call to make.
|
|
429
429
|
|
|
430
430
|
What each change does next (then run \`gtd land\`):
|
|
431
|
-
- **Delete or empty \`.gtd/QUALITY.md\`** — the suite runs on your edits, then the
|
|
431
|
+
- **Delete or empty \`.gtd/QUALITY.md\`** — the suite runs on your edits, then the next slice is built, or after the last one you review the change (**build.review.reviewing**).
|
|
432
432
|
- **Anything else** — rests here again.
|
|
433
433
|
`;
|
|
434
434
|
const startGateBlockedMessage = () => `The test baseline is red — gtd will not start new work on a broken suite.
|
|
@@ -466,8 +466,10 @@ reword a criterion in place, append a new one with the next unused
|
|
|
466
466
|
number, delete a dropped one — never renumber, never reuse an ID. When
|
|
467
467
|
the file holds no list (a review loop-back rewrites it), recover the
|
|
468
468
|
last settled one from history (\`git log -p -- .gtd/REQUIREMENTS.md\`)
|
|
469
|
-
and carry its IDs over
|
|
470
|
-
|
|
469
|
+
and carry its IDs over: after a review a built criterion keeps its ID,
|
|
470
|
+
reworded in place when the review changes its behaviour, and new
|
|
471
|
+
behaviour gets a new ID. Landing is refused while a criterion has no ID
|
|
472
|
+
or two share one.
|
|
471
473
|
`;
|
|
472
474
|
const riskTierBlock = `
|
|
473
475
|
## Risk tier and production signals
|
|
@@ -1630,7 +1632,7 @@ const retryQuestion = (comparable) => comparable ? {
|
|
|
1630
1632
|
instructions: "state carries tailsNotComparable: one of the two check reports came back empty. There is nothing to compare here; answer from criteria alone. `identical` is FORBIDDEN here.",
|
|
1631
1633
|
criteria: "new-failure: a materially different failure than before. progress: still red, but closer to green — the default if genuinely unsure."
|
|
1632
1634
|
};
|
|
1633
|
-
const
|
|
1635
|
+
const compare = async (previous, current) => {
|
|
1634
1636
|
const comparable = current.trim() !== "" && previous.trim() !== "";
|
|
1635
1637
|
const { answers, truncated } = await judge("health.judge", {
|
|
1636
1638
|
questions: [retryQuestion(comparable)],
|
|
@@ -1641,7 +1643,10 @@ const sameFailure = async (previous, current) => {
|
|
|
1641
1643
|
message: healthJudgeMessage(),
|
|
1642
1644
|
label: "Judging the retry"
|
|
1643
1645
|
});
|
|
1644
|
-
|
|
1646
|
+
if (!comparable || truncated.length > 0) return void 0;
|
|
1647
|
+
const minP = numeric(vars.judgeIdenticalMinP, Infinity);
|
|
1648
|
+
if (answered(answers.verdict, "identical", minP)) return "identical";
|
|
1649
|
+
return answered(answers.verdict, "progress", minP) ? "progress" : void 0;
|
|
1645
1650
|
};
|
|
1646
1651
|
const healthSweep = (options) => [ESCALATION, ...options.sweepOnGreen ?? []];
|
|
1647
1652
|
const runSuite = (options) => check("health.check", env.testCommand ?? "", {
|
|
@@ -1652,7 +1657,8 @@ const runSuite = (options) => check("health.check", env.testCommand ?? "", {
|
|
|
1652
1657
|
/**
|
|
1653
1658
|
* Loop until the suite is green: check, and on red run `fix` — escalating
|
|
1654
1659
|
* first once `FIX_CAP` fixes are spent, or a judge calls the failure
|
|
1655
|
-
* identical to the previous round's.
|
|
1660
|
+
* identical to the previous round's. A judge calling it progress at the cap
|
|
1661
|
+
* buys one extra fix per run of fixes. An escalation re-runs the suite once
|
|
1656
1662
|
* first; a green re-run ends the loop.
|
|
1657
1663
|
*/
|
|
1658
1664
|
const healthy = (fix, options = {}) => healthy$1(guarded(fix, options.guard), {
|
|
@@ -1660,7 +1666,7 @@ const healthy = (fix, options = {}) => healthy$1(guarded(fix, options.guard), {
|
|
|
1660
1666
|
report: FEEDBACK,
|
|
1661
1667
|
fixCap: 3,
|
|
1662
1668
|
fixesSoFar: options.fixesSoFar ?? 0,
|
|
1663
|
-
|
|
1669
|
+
compare,
|
|
1664
1670
|
escalations: options.escalations ?? { rounds: 0 },
|
|
1665
1671
|
recheck: () => recheck("health.recheck", "health.check", { sweepOnGreen: healthSweep(options) }),
|
|
1666
1672
|
escalation: guardedSteps(options.guard, options.describeGuard, options.guardRests, options.rules)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { read, refuse, removeScript, run, scope, showScript, vars } from "../flows.mjs";
|
|
2
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-
|
|
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
4
|
//#region src/workflows/shared/tail.ts
|
|
5
5
|
/** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
|
|
6
6
|
const REVIEW_ORIG = ".gtd/REVIEW.orig.md";
|
package/dist/workflow-kit.mjs
CHANGED
|
@@ -22,13 +22,33 @@ const recheckReport = async (health) => {
|
|
|
22
22
|
if (health.recheck === void 0) return void 0;
|
|
23
23
|
return await health.recheck() || stripStamp(read(health.report) ?? "");
|
|
24
24
|
};
|
|
25
|
+
/** Escalate unless the re-check passes: `true` then, else the report the next round compares against. */
|
|
26
|
+
const escalated = async (health, current) => {
|
|
27
|
+
const rechecked = await recheckReport(health);
|
|
28
|
+
if (rechecked === true) {
|
|
29
|
+
health.escalations.rounds = 0;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
await escalation(health.escalations, health.escalation);
|
|
33
|
+
return rechecked ?? current;
|
|
34
|
+
};
|
|
35
|
+
const nextMove = async (health, fixes, cap, previous, current) => {
|
|
36
|
+
const capped = fixes >= cap;
|
|
37
|
+
if (capped && cap > health.fixCap) return "escalate";
|
|
38
|
+
const verdict = previous === void 0 ? void 0 : await health.compare(previous, current);
|
|
39
|
+
if (capped && verdict === "progress") return "extend";
|
|
40
|
+
return verdict === "identical" || capped ? "escalate" : void 0;
|
|
41
|
+
};
|
|
25
42
|
/**
|
|
26
43
|
* Loop until the suite is green: check, and on red run `fix` — escalating
|
|
27
|
-
* first once `fixCap` fixes are spent, or `
|
|
28
|
-
* identical to the previous round's, unless `recheck` passes.
|
|
44
|
+
* first once `fixCap` fixes are spent, or `compare` calls the failure
|
|
45
|
+
* identical to the previous round's, unless `recheck` passes. A round that
|
|
46
|
+
* reaches the cap while `compare` calls it progress raises the cap by one fix,
|
|
47
|
+
* once per run of fixes between escalations.
|
|
29
48
|
*/
|
|
30
49
|
const healthy = async (fix, health) => {
|
|
31
50
|
let fixes = health.fixesSoFar ?? 0;
|
|
51
|
+
let cap = health.fixCap;
|
|
32
52
|
let previous;
|
|
33
53
|
for (;;) {
|
|
34
54
|
if (await health.check()) {
|
|
@@ -36,17 +56,15 @@ const healthy = async (fix, health) => {
|
|
|
36
56
|
return;
|
|
37
57
|
}
|
|
38
58
|
const current = stripStamp(read(health.report) ?? "");
|
|
39
|
-
const
|
|
59
|
+
const move = await nextMove(health, fixes, cap, previous, current);
|
|
40
60
|
previous = current;
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
previous = rechecked ?? previous;
|
|
48
|
-
await escalation(health.escalations, health.escalation);
|
|
61
|
+
if (move === "extend") cap++;
|
|
62
|
+
else if (move === "escalate") {
|
|
63
|
+
const after = await escalated(health, current);
|
|
64
|
+
if (after === true) return;
|
|
65
|
+
previous = after;
|
|
49
66
|
fixes = 0;
|
|
67
|
+
cap = health.fixCap;
|
|
50
68
|
}
|
|
51
69
|
fixes++;
|
|
52
70
|
await fix();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
|
|
2
|
-
import { Dt as registryEntry, Ot as requireFlag, S as coder, St as envDefaults, Tt as flagReferences, i as skills, n as summary, o as flagShipped, r as access, t as steering, xt as defaults } from "../chunks/reserved-
|
|
3
|
-
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { Dt as registryEntry, Ot as requireFlag, S as coder, St as envDefaults, Tt as flagReferences, i as skills, n as summary, o as flagShipped, r as access, t as steering, xt as defaults } from "../chunks/reserved-Ch4LLrEj.mjs";
|
|
3
|
+
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-lY1UmJrU.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,7 @@
|
|
|
1
1
|
import { agent, answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, slice, start, vars, wrote } from "../flows.mjs";
|
|
2
2
|
import { lensEntriesOf, review as review$1 } from "../workflow-kit.mjs";
|
|
3
|
-
import { $ as naiveApplyFailure, A as agentWithSkills, B as coverageQuestion, C as fixCheck, Ct as testFileGlobs, D as reviewMissing, E as planner, F as buildReviewTriageMessage, G as designSystem, H as criterionLine, I as builderSystem, K as designTriagePrompt, L as clearCriteriaMessage, M as buildReviewAwaitAfterDeferMessage, N as buildReviewCollectingPrompt, P as buildReviewFixNitsPrompt, Q as implementFixPrompt, R as clearCriteriaQuestion, S as coder, St as envDefaults, T as fixRisks, U as deferNotesScript, V as coveringPrompt, W as designGateAnswerMessage, X as idleMessage, Z as implementBuildingPrompt, _ as REFACTOR_NOTE, _t as startGateBlockedMessage, a as flagModeOn, at as refactorDroppedRefusal, b as SLICES, bt as unwindFailure, ct as refactorRules, d as guarded, et as naivePassPrompt, f as healthy, ft as reviewNotesCapture, g as REFACTOR, gt as slicingPrompt, h as QUALITY, ht as seamSteerMessage, i as skills, it as refactorBuildingPrompt, j as buildReviewAnswerQuestionsPrompt, k as reviewing, lt as reviewEditNotesCapture, m as HANDOFF, mt as seamByHandMessage, n as summary, nt as oversizedMessage, ot as refactorFixPrompt, p as FEEDBACK, pt as reviewerSystem, r as access, rt as qualityUnresolvedMessage, s as shipped, st as refactorNotePrompt, t as steering, tt as normaliseCriteriaScript, u as gate, ut as reviewEditsCapture, v as REQUIREMENTS, vt as uncoveredMessage, x as awaitReview, xt as defaults, y as REVIEW, yt as unwindFailedMessage, z as coverageMessage } from "../chunks/reserved-
|
|
4
|
-
import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-
|
|
3
|
+
import { $ as naiveApplyFailure, A as agentWithSkills, B as coverageQuestion, C as fixCheck, Ct as testFileGlobs, D as reviewMissing, E as planner, F as buildReviewTriageMessage, G as designSystem, H as criterionLine, I as builderSystem, K as designTriagePrompt, L as clearCriteriaMessage, M as buildReviewAwaitAfterDeferMessage, N as buildReviewCollectingPrompt, P as buildReviewFixNitsPrompt, Q as implementFixPrompt, R as clearCriteriaQuestion, S as coder, St as envDefaults, T as fixRisks, U as deferNotesScript, V as coveringPrompt, W as designGateAnswerMessage, X as idleMessage, Z as implementBuildingPrompt, _ as REFACTOR_NOTE, _t as startGateBlockedMessage, a as flagModeOn, at as refactorDroppedRefusal, b as SLICES, bt as unwindFailure, ct as refactorRules, d as guarded, et as naivePassPrompt, f as healthy, ft as reviewNotesCapture, g as REFACTOR, gt as slicingPrompt, h as QUALITY, ht as seamSteerMessage, i as skills, it as refactorBuildingPrompt, j as buildReviewAnswerQuestionsPrompt, k as reviewing, lt as reviewEditNotesCapture, m as HANDOFF, mt as seamByHandMessage, n as summary, nt as oversizedMessage, ot as refactorFixPrompt, p as FEEDBACK, pt as reviewerSystem, r as access, rt as qualityUnresolvedMessage, s as shipped, st as refactorNotePrompt, t as steering, tt as normaliseCriteriaScript, u as gate, ut as reviewEditsCapture, v as REQUIREMENTS, vt as uncoveredMessage, x as awaitReview, xt as defaults, y as REVIEW, yt as unwindFailedMessage, z as coverageMessage } from "../chunks/reserved-Ch4LLrEj.mjs";
|
|
4
|
+
import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-lY1UmJrU.mjs";
|
|
5
5
|
import { posix } from "node:path";
|
|
6
6
|
//#region src/workflows/shared/fences.ts
|
|
7
7
|
const markFenced = (text) => {
|
|
@@ -112,6 +112,19 @@ const requireCriteria = (file) => {
|
|
|
112
112
|
if (criteria.length > 0 && unnamed.length === 0 && twice.length === 0) return;
|
|
113
113
|
refuse(`gtd land: acceptance-criteria: ${file} has ${criteria.length === 0 ? `no "## Acceptance Criteria" list` : unnamed.length > 0 ? `${unnamed.length} criterion without an ID:\n${unnamed.map((c) => ` - ${c.text}`).join("\n")}` : `duplicate IDs: ${[...new Set(twice)].join(", ")}`} — list every criterion under "## Acceptance Criteria" as "- AC-<n>: <checkable criterion>", each ID unique and never reused`);
|
|
114
114
|
};
|
|
115
|
+
const idNumber = (id) => Number(id.slice(3));
|
|
116
|
+
/** Why `listed` renumbers or reuses an earlier ID; `undefined` when it does neither. */
|
|
117
|
+
const unstableIds = (listed, ids) => {
|
|
118
|
+
const top = Math.max(0, ...ids.used.map(idNumber));
|
|
119
|
+
const reused = listed.filter((id) => !ids.closed.includes(id) && idNumber(id) <= top);
|
|
120
|
+
if (reused.length === 0) return void 0;
|
|
121
|
+
return `numbers ${reused.join(", ")} at or below AC-${top}, an ID already handed out — keep a built criterion's ID (reword it in place when the review changes it), and number new ones from AC-${top + 1}`;
|
|
122
|
+
};
|
|
123
|
+
/** Refuse criteria in `file` that renumber or reuse an earlier ID. */
|
|
124
|
+
const requireStableIds = (file, ids) => {
|
|
125
|
+
const why = unstableIds(criterionIds(read(file) ?? ""), ids);
|
|
126
|
+
if (why !== void 0) refuse(`gtd land: criteria-ids: ${file} ${why}`);
|
|
127
|
+
};
|
|
115
128
|
/** Refuse a turn that changed the acceptance criteria in `file`: after the design gate only another design lap may. */
|
|
116
129
|
const requireCriteriaFrozen = (file) => {
|
|
117
130
|
const change = changes().get(file);
|
|
@@ -267,13 +280,14 @@ const noteGate = async (file, answer) => {
|
|
|
267
280
|
if (!hasThreadFor("agent", file)) requireAnswers(file);
|
|
268
281
|
return true;
|
|
269
282
|
};
|
|
270
|
-
/** Triage the sketch since `base` into requirements, until no product question is open. */
|
|
271
|
-
const design = (base) => scope("design", async () => {
|
|
283
|
+
/** Triage the sketch since `base` into requirements, until no product question is open; `guard` holds every round's criteria. */
|
|
284
|
+
const design = (base, guard) => scope("design", async () => {
|
|
272
285
|
do {
|
|
273
286
|
await triage(base);
|
|
274
287
|
requireProgress(REQUIREMENTS);
|
|
275
288
|
requireReplies(REQUIREMENTS);
|
|
276
289
|
requireCriteria(REQUIREMENTS);
|
|
290
|
+
guard?.();
|
|
277
291
|
} while (await noteGate(REQUIREMENTS, answerProductQuestions));
|
|
278
292
|
});
|
|
279
293
|
//#endregion
|
|
@@ -1085,27 +1099,31 @@ const build = (options) => continuing(phase("build", requireCriteriaIfEdited), a
|
|
|
1085
1099
|
}
|
|
1086
1100
|
};
|
|
1087
1101
|
});
|
|
1088
|
-
function implement(first, tier, amend) {
|
|
1089
|
-
|
|
1102
|
+
async function implement(first, tier, amend) {
|
|
1103
|
+
await implementing(typeof first === "object" && first !== null ? first : {
|
|
1090
1104
|
reference: first,
|
|
1091
1105
|
tier,
|
|
1092
1106
|
amend
|
|
1093
|
-
};
|
|
1094
|
-
return scope("implement", async () => {
|
|
1095
|
-
const since = head();
|
|
1096
|
-
const acceptance = options.rebuild?.acceptance;
|
|
1097
|
-
const built = options.naive === void 0 || !await applied(options.naive);
|
|
1098
|
-
if (built) await build(options);
|
|
1099
|
-
else requireCriteriaFrozen(REQUIREMENTS);
|
|
1100
|
-
if (built && acceptance !== void 0) requireAcceptance({
|
|
1101
|
-
...acceptance,
|
|
1102
|
-
ids: settled(acceptance.ids, options.slice)
|
|
1103
|
-
}, since);
|
|
1104
|
-
await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
|
|
1105
|
-
await requireCoverage(options.base, options.slice);
|
|
1106
|
-
await closing(options.more === true);
|
|
1107
1107
|
});
|
|
1108
1108
|
}
|
|
1109
|
+
/** `implement`, resolving with the criterion IDs the requirements listed when it closed. */
|
|
1110
|
+
const implementing = (options) => scope("implement", async () => {
|
|
1111
|
+
const since = head();
|
|
1112
|
+
const acceptance = options.rebuild?.acceptance;
|
|
1113
|
+
const built = options.naive === void 0 || !await applied(options.naive);
|
|
1114
|
+
if (built) await build(options);
|
|
1115
|
+
else requireCriteriaFrozen(REQUIREMENTS);
|
|
1116
|
+
if (built && acceptance !== void 0) requireAcceptance({
|
|
1117
|
+
...acceptance,
|
|
1118
|
+
ids: settled(acceptance.ids, options.slice)
|
|
1119
|
+
}, since);
|
|
1120
|
+
await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
|
|
1121
|
+
await requireCoverage(options.base, options.slice);
|
|
1122
|
+
const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
1123
|
+
await closing(moreToBuild(options.more));
|
|
1124
|
+
return ids;
|
|
1125
|
+
});
|
|
1126
|
+
const moreToBuild = (more) => typeof more === "function" ? more() : more === true;
|
|
1109
1127
|
/** The last slice's closing, for a last slice a steer left with no criterion to build. */
|
|
1110
1128
|
const closeOut = () => scope("implement", () => closing(false));
|
|
1111
1129
|
/** A slice with `more` after it keeps the criteria and the slicing for the next one. */
|
|
@@ -1340,32 +1358,37 @@ const unwind = () => {
|
|
|
1340
1358
|
const buildFeature = async (firstBase) => {
|
|
1341
1359
|
const tier = riskTierOn() ? recordRisk() : void 0;
|
|
1342
1360
|
const slices = await slicing();
|
|
1361
|
+
const handedOut = new Set(listedIds());
|
|
1343
1362
|
const one = slices.length === 1;
|
|
1363
|
+
const built = (i) => sliceIds(sliceCriteria(slices, i))?.length !== 0;
|
|
1344
1364
|
for (const i of slices.keys()) {
|
|
1345
|
-
|
|
1346
|
-
const criteria = sliceCriteria(slices, i);
|
|
1347
|
-
if (sliceIds(criteria)?.length === 0) {
|
|
1348
|
-
if (!more) await closeOut();
|
|
1349
|
-
continue;
|
|
1350
|
-
}
|
|
1365
|
+
if (!built(i)) continue;
|
|
1351
1366
|
const n = one ? "" : `-${i + 1}`;
|
|
1352
1367
|
await buildSlice(i === 0 ? firstBase : head(), {
|
|
1353
1368
|
tier,
|
|
1354
|
-
criteria,
|
|
1369
|
+
criteria: sliceCriteria(slices, i),
|
|
1355
1370
|
refactor: `refactor${n}`,
|
|
1356
1371
|
feature: `feature${n}`,
|
|
1357
|
-
more
|
|
1372
|
+
more: () => slices.some((_, j) => j > i && built(j)),
|
|
1373
|
+
reviewBase: one ? void 0 : firstBase,
|
|
1374
|
+
handedOut
|
|
1358
1375
|
});
|
|
1359
1376
|
}
|
|
1360
1377
|
};
|
|
1361
|
-
|
|
1378
|
+
const listedIds = () => criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
1379
|
+
const gather = (handedOut) => {
|
|
1380
|
+
for (const id of listedIds()) handedOut.add(id);
|
|
1381
|
+
return [...handedOut];
|
|
1382
|
+
};
|
|
1383
|
+
/** A naive pass (judged only with `flagMode` on), then implement and the quality lap; after the last slice, the review. */
|
|
1362
1384
|
const buildSlice = async (sliceBase, build) => {
|
|
1363
1385
|
const seam = await seamBuild(sliceBase, {
|
|
1364
1386
|
criteria: build.criteria,
|
|
1365
1387
|
refactor: build.refactor
|
|
1366
1388
|
});
|
|
1389
|
+
const used = gather(build.handedOut);
|
|
1367
1390
|
await slice(build.feature, async () => {
|
|
1368
|
-
await
|
|
1391
|
+
const closed = await implementing({
|
|
1369
1392
|
reference: seam.reference,
|
|
1370
1393
|
tier: build.tier,
|
|
1371
1394
|
afterRefactor: seam.refactored,
|
|
@@ -1375,14 +1398,23 @@ const buildSlice = async (sliceBase, build) => {
|
|
|
1375
1398
|
base: build.criteria === void 0 ? void 0 : sliceBase,
|
|
1376
1399
|
more: build.more
|
|
1377
1400
|
});
|
|
1401
|
+
const more = build.more();
|
|
1378
1402
|
const carried = seam.rebuild?.acceptance?.tests ?? [];
|
|
1379
1403
|
const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path) && !carried.includes(c.path)) ? sliceBase : seam.base;
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1404
|
+
if (more) {
|
|
1405
|
+
const guard = () => requireCriteriaFrozen(REQUIREMENTS);
|
|
1406
|
+
await tail(settleQuality, {
|
|
1407
|
+
green: true,
|
|
1408
|
+
lensBase: base,
|
|
1409
|
+
guard,
|
|
1410
|
+
guardRests: false
|
|
1411
|
+
});
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
const reviewBase = build.reviewBase ?? base;
|
|
1415
|
+
await featureFeedback(await reviewedTail(reviewBase, base), base, {
|
|
1416
|
+
closed,
|
|
1417
|
+
used
|
|
1386
1418
|
});
|
|
1387
1419
|
});
|
|
1388
1420
|
};
|
|
@@ -1398,6 +1430,35 @@ const settleQuality = async () => {
|
|
|
1398
1430
|
}
|
|
1399
1431
|
if (rested) await healthy(() => fixCheck());
|
|
1400
1432
|
};
|
|
1433
|
+
/** The quality lap, then the human review since `base`. */
|
|
1434
|
+
const reviewedTail = (base, lensBase, lap) => tail(async (escalations) => {
|
|
1435
|
+
await settleQuality();
|
|
1436
|
+
return review(base, escalations, { lap });
|
|
1437
|
+
}, {
|
|
1438
|
+
green: true,
|
|
1439
|
+
lensBase
|
|
1440
|
+
});
|
|
1441
|
+
const featureFeedback = async (first, lensBase, ids) => {
|
|
1442
|
+
let outcome = first;
|
|
1443
|
+
let closed = ids.closed;
|
|
1444
|
+
const handedOut = /* @__PURE__ */ new Set([...ids.used, ...ids.closed]);
|
|
1445
|
+
while (outcome.verdict === "feedback") {
|
|
1446
|
+
await reUnwind(outcome);
|
|
1447
|
+
const lap = {
|
|
1448
|
+
closed,
|
|
1449
|
+
used: [...handedOut]
|
|
1450
|
+
};
|
|
1451
|
+
await design(outcome.base, () => {
|
|
1452
|
+
requireStableIds(REQUIREMENTS, lap);
|
|
1453
|
+
gather(handedOut);
|
|
1454
|
+
});
|
|
1455
|
+
closed = await implementing({
|
|
1456
|
+
tier: riskTierOn() ? recordRisk() : void 0,
|
|
1457
|
+
amend: true
|
|
1458
|
+
});
|
|
1459
|
+
outcome = await reviewedTail(outcome.base, lensBase, "feedback");
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1401
1462
|
/** Design — skipped when the sketch's own criteria are judged clear — then `buildFeature`. */
|
|
1402
1463
|
const planAndBuild = async (firstBase, stated) => {
|
|
1403
1464
|
if (!await skipDesign(riskTierOn() ? void 0 : stated)) await design(firstBase);
|
|
@@ -1444,4 +1505,4 @@ const feature = shipped(ordinaryStart);
|
|
|
1444
1505
|
//#region src/workflows/feature/index.ts
|
|
1445
1506
|
var feature_default = feature;
|
|
1446
1507
|
//#endregion
|
|
1447
|
-
export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, closeOut, collecting, criteriaOf, criterionIds, feature_default as default, defaults, deferNotes, design, envDefaults, feature, fixNits, holdsCallSites, implement, isTestFile, judgeSeam, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireTestCallSitesOnly, requireWithin, reviewedRefactor, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, triage, unwind };
|
|
1508
|
+
export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, closeOut, collecting, criteriaOf, criterionIds, feature_default as default, defaults, deferNotes, 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
2
|
import { d as reviewNotes, f as reviewRisks } from "../chunks/steering-DVILanVf.mjs";
|
|
3
|
-
import { A as agentWithSkills, J as fixReviewNotesPrompt, S as coder, St as envDefaults, c as baseline, i as skills, n as summary, q as finisherSystem, r as access, s as shipped, t as steering, xt as defaults, y as REVIEW } from "../chunks/reserved-
|
|
4
|
-
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-
|
|
3
|
+
import { A as agentWithSkills, J as fixReviewNotesPrompt, S as coder, St as envDefaults, c as baseline, i as skills, n as summary, q as finisherSystem, r as access, s as shipped, t as steering, xt as defaults, y as REVIEW } from "../chunks/reserved-Ch4LLrEj.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-lY1UmJrU.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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { start } from "../flows.mjs";
|
|
2
|
-
import { St as envDefaults, dt as reviewGateBlockedMessage, i as skills, n as summary, r as access, s as shipped, t as steering, u as gate, xt as defaults } from "../chunks/reserved-
|
|
3
|
-
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { St as envDefaults, dt as reviewGateBlockedMessage, i as skills, n as summary, r as access, s as shipped, t as steering, u as gate, xt as defaults } from "../chunks/reserved-Ch4LLrEj.mjs";
|
|
3
|
+
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-lY1UmJrU.mjs";
|
|
4
4
|
//#region src/workflows/review/review.ts
|
|
5
5
|
/**
|
|
6
6
|
* Pure review of everything since `reviewBase`: a green gate, the quality
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
|
|
2
|
-
import { Et as flipProblems, Ot as requireFlag, S as coder, St as envDefaults, i as skills, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate, wt as flagMentions, xt as defaults } from "../chunks/reserved-
|
|
2
|
+
import { Et as flipProblems, Ot as requireFlag, S as coder, St as envDefaults, i as skills, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate, wt as flagMentions, xt as defaults } from "../chunks/reserved-Ch4LLrEj.mjs";
|
|
3
3
|
//#region src/workflows/rollout/rollout.ts
|
|
4
4
|
const flippingPrompt = (name, registry) => `Roll out the release flag "${name}": in ${registry}, set \`defaultOn: true\` on its entry. Change nothing else — no other entry, no other field, no other file, no comments.`;
|
|
5
5
|
const suiteBlockedMessage = () => "The suite is red after the rollout. Fix it by hand — a rollout may only touch the flag's registry entry — then land.";
|