@pmelab/gtd 38.2.0 → 40.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.
Files changed (46) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +66 -58
  3. package/dist/chunks/{reserved-C3PgP7XB.mjs → reserved-Qt5FM6Ia.mjs} +29 -10
  4. package/dist/chunks/{tail-Bkxki2nK.mjs → tail-tAHx_AHe.mjs} +1 -1
  5. package/dist/gtd.bundle.mjs +14 -5
  6. package/dist/testkit.mjs +14 -5
  7. package/dist/workflow-kit.mjs +12 -1
  8. package/dist/workflows/cleanup.mjs +2 -2
  9. package/dist/workflows/feature.mjs +98 -37
  10. package/dist/workflows/fix.mjs +2 -2
  11. package/dist/workflows/review.mjs +2 -2
  12. package/dist/workflows/rollout.mjs +1 -1
  13. package/dist/workflows.mjs +136 -45
  14. package/package.json +1 -1
  15. package/skills/authoring/SKILL.md +3 -2
  16. package/src/cli/Cli.ts +4 -3
  17. package/src/cli/Metrics.test.ts +31 -0
  18. package/src/cli/Metrics.ts +8 -0
  19. package/src/workflow-kit/health.ts +15 -1
  20. package/src/workflows/cleanup/cleanup.test.ts +2 -0
  21. package/src/workflows/feature/coverage.feature +5 -5
  22. package/src/workflows/feature/criteria.test.ts +20 -1
  23. package/src/workflows/feature/criteria.ts +24 -0
  24. package/src/workflows/feature/fast-path.feature +4 -4
  25. package/src/workflows/feature/feature.ts +82 -22
  26. package/src/workflows/feature/flag-mode.feature +2 -2
  27. package/src/workflows/feature/one-step-land.feature +4 -4
  28. package/src/workflows/feature/planning.ts +3 -2
  29. package/src/workflows/feature/quality-unresolved.feature +6 -6
  30. package/src/workflows/feature/refactor-absorbs.feature +3 -3
  31. package/src/workflows/feature/refactor-first.feature +5 -5
  32. package/src/workflows/feature/refactor-rebuild.feature +2 -2
  33. package/src/workflows/feature/refactor-review.feature +8 -8
  34. package/src/workflows/feature/refactor.test.ts +4 -0
  35. package/src/workflows/feature/review-gate.feature +271 -0
  36. package/src/workflows/feature/seam-rejection.feature +4 -4
  37. package/src/workflows/feature/seam.feature +2 -2
  38. package/src/workflows/feature/seam.ts +17 -7
  39. package/src/workflows/feature/slicing.feature +63 -4
  40. package/src/workflows/rollout/rollout.feature +16 -0
  41. package/src/workflows/rollout/rollout.test.ts +2 -0
  42. package/src/workflows/shared/health.test.ts +2 -0
  43. package/src/workflows/shared/health.ts +34 -5
  44. package/src/workflows/shared/recheck.feature +60 -0
  45. package/src/workflows/shared/text.ts +7 -5
  46. package/src/workflows/review/review-door.feature +0 -121
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "38.2.0",
3
+ "version": "40.0.0",
4
4
  "description": "Drive the gtd loop inside Claude Code: agent turns run as subagents, gtd decides every beat",
5
5
  "author": {
6
6
  "name": "Philipp Melab"
package/README.md CHANGED
@@ -213,9 +213,10 @@ Label: Checking the baseline
213
213
  ```
214
214
 
215
215
  A green suite leaves the tree clean, so the process moves on to triage. A red
216
- suite instead writes `.gtd/FEEDBACK.md`, and the process goes to
217
- `start-gate.blocked` — same two commands, different outcome, decided entirely by
218
- what the beat left in the tree.
216
+ suite instead writes `.gtd/FEEDBACK.md`; the process runs it once more at
217
+ `start-gate.recheck`, and only a second red goes to `start-gate.blocked` — same
218
+ two commands, different outcome, decided entirely by what the beat left in the
219
+ tree.
219
220
 
220
221
  Land the green one, and the beat after it belongs to an agent.
221
222
 
@@ -418,10 +419,9 @@ phone UI does not show them).
418
419
 
419
420
  ### The workflow it ships with
420
421
 
421
- One built-in workflow drives all of that. From where you sit, it has three
422
+ One built-in workflow drives all of that. From where you sit, it has four
422
423
  moments — everything between them runs without you, with the judged exceptions
423
- noted in step 3 below — plus a fourth, review, only on `refactor-first` or when
424
- you open the `review` door.
424
+ noted in steps 3 and 4 below.
425
425
 
426
426
  1. **You sketch.** Change anything, or write the idea into `.gtd/TODO.md`. Rough
427
427
  is fine; it is treated as a sketch, not as work.
@@ -448,14 +448,12 @@ you open the `review` door.
448
448
  against the full suite (`testCommand`) and fixed until it passes — so keep
449
449
  that suite fast; gtd has no lighter one. Scenario wording is not frozen: a
450
450
  review's `## Scenarios` chunk marks any assertion an existing scenario lost
451
- as `Weakened:` — **risk: `feature` has no review, so a build turn that
452
- weakens a scenario reaches `idle` unflagged unless you open the `review`
453
- door.** After the build the quality lap runs, and the process ends: the
454
- feature workflow has no human review of the finished change. Two points along
455
- the process (one with flag mode off) are judged rather than always asking you
456
- outright — each stops and hands you a verdict to make (`gtd judge answer`, or
457
- land with a clean tree to accept the conservative default, which never skips
458
- 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
459
457
  `GTD_JUDGE_ANSWERS` env var, inline JSON — answers one from a file, piped
460
458
  between `gtd judge --json` and `gtd judge answer`;
461
459
  `gtd judge run --provider jev` asks TypeSafe's Jev instead, with the key in
@@ -473,6 +471,8 @@ you open the `review` door.
473
471
  or get rejected and retried?
474
472
  - Every red round after the first: was the failure identical, new, or
475
473
  progress?
474
+ - After you review: is each of your notes an edit, a question, a nit or
475
+ praise?
476
476
 
477
477
  gtd itself never calls a judge: `gtd loop`, the Claude Code mod and the
478
478
  reference driver answer these through `gtd judge run` (auto selection: jev
@@ -484,22 +484,20 @@ you open the `review` door.
484
484
  and skip a gate unattended.**
485
485
 
486
486
  Once the change is built, the whole change goes through a qualitative review
487
- lap: six lenses, one turn each, in order — `correctness`, `owasp-security`,
488
- `ponytail-review`, `test-audit`, `conventions`, `spec-challenge`.
489
- `correctness`, `owasp-security` and `spec-challenge` run on the planner model
490
- (`smart`), the other three on the coder model (`base`); a `qualityReviews`
491
- entry `<lens>=<model>` picks a lens's own. Each traces the change from its
492
- own angle and records every finding, blocking or not; one fix turn then fixes
493
- ALL of them once, with no re-review after the fix. A clean turn means
494
- approval only when that lens found nothing at all. This lap is where code
495
- quality is looked at, and every round pays for it. It is the last look the
496
- feature workflow takes: the process ends at `idle` once it is clean (and the
497
- suite green), with no review rest — **risk: nothing re-reads the code the
498
- single fix turn writes; only the suite gates it.** Findings the fix cap
499
- leaves in `.gtd/QUALITY.md` rest at `build.quality-unresolved` until you
500
- delete or empty the file; the suite then runs on any hand edits. **Nobody
501
- signs the finished change off unless you open the `review` door** (below).
502
- 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.
503
501
 
504
502
  A red suite that keeps failing past a few fix attempts escalates instead of
505
503
  retrying forever: an agent turn reads the failing output and writes
@@ -508,30 +506,35 @@ you open the `review` door.
508
506
  as-is, handing that document to the next fix turn as its instruction. Two
509
507
  such rounds with no fix is the terminal stop: the last analysis stays on disk
510
508
  instead of a third being written, and editing it is what gets the next
511
- attempt anywhere new.
512
-
513
- 4. **You review — the refactor, or on request.** The feature workflow asks for
514
- one review only on `refactor-first`: of the refactor slice, before the
515
- feature is built (see [the naive pass](#the-naive-pass)). For the finished
516
- change run `gtd door review <commitish>` (`/gtd review [base]`); that review
517
- hands notes to `fix` instead of re-planning (below). Either way you get a
518
- review document listing what changed and what to look at. Before you see it,
509
+ attempt anywhere new. Before any escalation or red-baseline stop the suite
510
+ runs once more; a pass there continues without you and is recorded as
511
+ `Gtd-Mark: flaky=<check>` (see
512
+ [Re-check before a person](docs/configuration.md#re-check-before-a-person)).
513
+
514
+ 4. **You review.** After the last slice's quality lap you get a review document
515
+ over the whole change, listing what changed and what to look at (on
516
+ `refactor-first` the refactor slice gets one of its own first, before the
517
+ feature is built; see [the naive pass](#the-naive-pass)). Before you see it,
519
518
  an automatic risk-fix pass (`build.review.fix.risks.fixing`) runs. Any risk
520
519
  the reviewer names (a note opening with `Risk:`) is fixed first, the suite
521
520
  kept green, and the review rewritten — once per review round, so a risk the
522
521
  rewrite still names reaches you unfixed; risk: a fix lands with no check that
523
- the risk was real. Tick the boxes to approve, or write what is wrong. At the
524
- refactor review, approving moves on to the feature; feedback is judged note
525
- by note (a third judged point), each as `edit`, `question`, `nit` or
526
- `praise`. An `edit` re-derives the refactor (in a custom workflow built on
527
- `buildTail`, it sends the process back to step 2 for re-derived
528
- requirements). A `question` is answered inline under your note and the
529
- process stops at the review again — no new plan. A `nit` is fixed in one
530
- batch, the suite must go green (a red one gets fix turns first), then a fresh
531
- review of the change stops at the review again. `praise` is dropped; a round
532
- of only praise signs off. When a round mixes them, questions are answered and
533
- nits fixed first, then the edits are planned — risk: the planning lap may
534
- redo nit fixes it touches.
522
+ the risk was real. Tick the boxes to approve, or write what is wrong.
523
+ Approving ends the process (at the refactor review, it moves on to the
524
+ feature); feedback is judged note by note, each as `edit`, `question`, `nit`
525
+ or `praise`. An `edit` sends the process back to step 2 for re-derived
526
+ requirements — the built criteria keep their IDs (reworded in place when
527
+ their behaviour changes) and new behaviour gets new ones — then the
528
+ implementation is amended to meet them, with no new naive pass or seam judge,
529
+ every criterion's coverage is judged again, the quality lap runs, and you
530
+ review again. At the refactor review an `edit` re-derives the refactor
531
+ instead. A `question` is answered inline under your note and the process
532
+ stops at the review again — no new plan. A `nit` is fixed in one batch, the
533
+ suite must go green (a red one gets fix turns first), then a fresh review of
534
+ the change stops at the review again. `praise` is dropped; a round of only
535
+ praise signs off. When a round mixes them, questions are answered and nits
536
+ fixed first, then the edits are planned — risk: the planning lap may redo nit
537
+ fixes it touches.
535
538
 
536
539
  Each `edit` note also gets a severity tier from four judge questions
537
540
  (security, reversibility, blast radius, new concept). `critical` and `should`
@@ -551,7 +554,7 @@ you open the `review` door.
551
554
  You never talk to it. Every exchange is a file in `.gtd/` that you edit in your
552
555
  own editor, and every answer you give is a commit. Your test suite is the gate
553
556
  throughout: a red baseline stops the process before it starts, and a red build
554
- is fixed before the process ends.
557
+ is fixed before review.
555
558
 
556
559
  Prefer a phone or another device over your editor? `gtd ui` opens the file whose
557
560
  turn it is as a small web app, over Tailscale — including a step that names no
@@ -631,14 +634,15 @@ criterion ID the naive pass's tests named, and the suite must be green with
631
634
  them. A wrong approach is retried twice, then rests for you to steer or overrule
632
635
  it. `gtd slices` lists the `refactor` and `feature` slices. A refactor that
633
636
  already carried the whole feature leaves the rebuild only the carried-over
634
- tests, and the feature's quality lap covers the whole change. Once the
637
+ tests, and the feature's quality lap and review cover the whole change. Once the
635
638
  implementation is green, a judge checks every criterion ID has a covering test:
636
639
  an uncovered one goes back to the agent (twice, then to you) instead of ending
637
640
  the feature, and each covered ID is recorded as a `Gtd-Mark: covers=<id>`
638
641
  trailer. See
639
642
  [Acceptance-criteria coverage](docs/configuration.md#acceptance-criteria-coverage).
640
- Feedback at the refactor review re-runs the refactor, never design; a
641
- refactor-review change the refactor cannot build is refused, and the only way
643
+ Feedback at the feature review re-runs design and the implementation, never the
644
+ naive pass; feedback at the refactor review re-runs the refactor, never design;
645
+ a refactor-review change the refactor cannot build is refused, and the only way
642
646
  out is `gtd abandon`. A process from 27.5.0 or later whose judge ruled
643
647
  `refactor-first` must `gtd abandon` too. Each slice's quality lap reviews only
644
648
  that slice. Details in
@@ -648,10 +652,11 @@ that slice. Details in
648
652
  criteria than the cap is built in slices: after design an agent groups related
649
653
  criteria (same flow or subsystem) into `.gtd/SLICES.md`, as few slices as the
650
654
  cap allows, and each slice — naive pass, refactor and rebuild when judged so,
651
- health loop, quality lap — runs in order from where the previous one landed.
652
- `gtd slices` lists them as `feature-1`, `feature-2`, … (`refactor-<n>` ahead of
653
- a refactored one). A change within the cap is one `feature` slice, as before.
654
- See [Coarse slices](docs/configuration.md#coarse-slices).
655
+ health loop, quality lap — runs in order from where the previous one landed; the
656
+ review follows the last one and covers the whole change. `gtd slices` lists them
657
+ as `feature-1`, `feature-2`, … (`refactor-<n>` ahead of a refactored one). A
658
+ change within the cap is one `feature` slice, as before. See
659
+ [Coarse slices](docs/configuration.md#coarse-slices).
655
660
 
656
661
  **Flag mode (`flagMode`, on by default).** On, the naive pass's judge runs as
657
662
  above and a rebuilt implementation is asked for a new release flag (none at a
@@ -745,6 +750,9 @@ it never replays and a config changed since is no refusal:
745
750
  - **findings** — the net count of `## ` findings each quality-lap lens added to
746
751
  `.gtd/QUALITY.md`, keyed by the lap's lens path (`build.quality.correctness`,
747
752
  `refactor.build.quality.correctness`, …)
753
+ - **flaky** — red checks that passed on their re-check (`Gtd-Mark: flaky=…`),
754
+ keyed by the re-check's full step name (`start-gate.recheck`,
755
+ `build.health.recheck`, …)
748
756
 
749
757
  `--json` prints `{ processes, total }`; `--json=total.refactorFirstRate` prints
750
758
  one value. Model, phase and lens keys may hold dots a path cannot reach: select
@@ -1,4 +1,4 @@
1
- import { accessFor, agent, answered, changesSince, check, codeThreads, defineWorkflow, displayPath, env, head, human, judge, numeric, quote, read, refuse, scope, skillsFor, start, vars } from "../flows.mjs";
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
2
  import { escalation as escalation$1, gate as gate$1, healthy as healthy$1, lensScopes, lensesOf, removedAssertions } from "../workflow-kit.mjs";
3
3
  import { c as String$, g as standardSchemaV1, h as optionalWith, l as Struct, n as Boolean$, s as Number$ } from "./Schema-C6fLlQzx.mjs";
4
4
  //#region src/workflows/shared/registry.ts
@@ -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 reviews the change after this, so the process
425
- will not end with them open.
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 process ends (**idle**).
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. Landing is refused while a criterion has no ID or
470
- two share one.
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
@@ -1580,6 +1582,19 @@ const baseline = (name, label = "Checking the baseline") => check(name, env.test
1580
1582
  report: FEEDBACK,
1581
1583
  label
1582
1584
  });
1585
+ /**
1586
+ * Re-run a red check as step `name` before a person sees it. Green records
1587
+ * `Gtd-Mark: flaky=<red>` on the re-check's landing and resolves `true`.
1588
+ */
1589
+ const recheck = async (name, red, options = {}) => {
1590
+ const green = await check(name, env.testCommand ?? "", {
1591
+ ...options,
1592
+ report: FEEDBACK,
1593
+ label: "Re-running checks"
1594
+ });
1595
+ if (green) mark("flaky", red);
1596
+ return green;
1597
+ };
1583
1598
  const escalationSteps = (rules) => ({
1584
1599
  limit: 2,
1585
1600
  describe: () => describeEscalation(rules),
@@ -1630,15 +1645,17 @@ const sameFailure = async (previous, current) => {
1630
1645
  });
1631
1646
  return comparable && truncated.length === 0 && answered(answers.verdict, "identical", numeric(vars.judgeIdenticalMinP, Infinity));
1632
1647
  };
1648
+ const healthSweep = (options) => [ESCALATION, ...options.sweepOnGreen ?? []];
1633
1649
  const runSuite = (options) => check("health.check", env.testCommand ?? "", {
1634
1650
  report: FEEDBACK,
1635
1651
  label: "Running checks",
1636
- sweepOnGreen: [ESCALATION, ...options.sweepOnGreen ?? []]
1652
+ sweepOnGreen: healthSweep(options)
1637
1653
  });
1638
1654
  /**
1639
1655
  * Loop until the suite is green: check, and on red run `fix` — escalating
1640
1656
  * first once `FIX_CAP` fixes are spent, or a judge calls the failure
1641
- * identical to the previous round's.
1657
+ * identical to the previous round's. An escalation re-runs the suite once
1658
+ * first; a green re-run ends the loop.
1642
1659
  */
1643
1660
  const healthy = (fix, options = {}) => healthy$1(guarded(fix, options.guard), {
1644
1661
  check: () => runSuite(options),
@@ -1647,14 +1664,16 @@ const healthy = (fix, options = {}) => healthy$1(guarded(fix, options.guard), {
1647
1664
  fixesSoFar: options.fixesSoFar ?? 0,
1648
1665
  sameFailure,
1649
1666
  escalations: options.escalations ?? { rounds: 0 },
1667
+ recheck: () => recheck("health.recheck", "health.check", { sweepOnGreen: healthSweep(options) }),
1650
1668
  escalation: guardedSteps(options.guard, options.describeGuard, options.guardRests, options.rules)
1651
1669
  });
1652
1670
  /**
1653
- * Hold the process at `<name>.blocked` until the suite is green. `guard` runs
1671
+ * Hold the process at `<name>.blocked` until the suite is green, re-running a
1672
+ * red check once as `<name>.recheck` before resting. `guard` runs
1654
1673
  * after each `blocked` landing only — never after a check, whose refusal would
1655
1674
  * re-run the same script forever.
1656
1675
  */
1657
- const gate = (name, message, guard) => gate$1(name, () => baseline("check"), guarded(() => human("blocked", {
1676
+ const gate = (name, message, guard) => gate$1(name, async () => await baseline("check") || recheck("recheck", `${name}.check`), guarded(() => human("blocked", {
1658
1677
  message,
1659
1678
  label: "Baseline is red",
1660
1679
  file: FEEDBACK
@@ -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-C3PgP7XB.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-Qt5FM6Ia.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";
@@ -41406,7 +41406,8 @@ const emptyTally = () => ({
41406
41406
  naive: 0,
41407
41407
  rebuild: 0
41408
41408
  },
41409
- byLens: record()
41409
+ byLens: record(),
41410
+ flaky: record()
41410
41411
  });
41411
41412
  const sum = (record) => Object.values(record).reduce((a, b) => a + b, 0);
41412
41413
  const finish = (t) => ({
@@ -41426,6 +41427,10 @@ const finish = (t) => ({
41426
41427
  findings: {
41427
41428
  total: sum(t.byLens),
41428
41429
  byLens: t.byLens
41430
+ },
41431
+ flaky: {
41432
+ total: sum(t.flaky),
41433
+ byStep: t.flaky
41429
41434
  }
41430
41435
  });
41431
41436
  const tallyCost = (t, open, costs, phase) => {
@@ -41482,6 +41487,7 @@ const processMetrics = (commits) => {
41482
41487
  if (subject.actor === "agent" && CONTINUE.test(at)) add(t.continuations, at.replace(CONTINUE, "$1"), 1);
41483
41488
  const lens = LENS.exec(at)?.[1];
41484
41489
  if (lens !== void 0 && commit.findings > 0) add(t.byLens, lens, commit.findings);
41490
+ if (message.marks.some((m) => m.name === "flaky")) add(t.flaky, at, 1);
41485
41491
  tallyDecision(t, open, at, subject.to);
41486
41492
  }
41487
41493
  return finish(t);
@@ -41496,6 +41502,7 @@ const totalMetrics = (all) => {
41496
41502
  t.doubleWork.naive += m.cost.doubleWork.naive;
41497
41503
  t.doubleWork.rebuild += m.cost.doubleWork.rebuild;
41498
41504
  for (const [k, v] of Object.entries(m.findings.byLens)) add(t.byLens, k, v);
41505
+ for (const [k, v] of Object.entries(m.flaky.byStep)) add(t.flaky, k, v);
41499
41506
  }
41500
41507
  return {
41501
41508
  processes: all.length,
@@ -41533,7 +41540,8 @@ const line = (label, m) => {
41533
41540
  `${label}: ${slices.decided} slices, ${slices.rebuilt} rebuilt (refactor-first ${percent}), ${slices.refactored} refactored`,
41534
41541
  `${continued} continuations${continued > 0 ? ` (${listed(m.continuations)})` : ""}`,
41535
41542
  `cost ${cost.total} (${listed(cost.byPhase)}; by model ${listed(cost.byModel) || "-"}), double work ${cost.doubleWork.total}`,
41536
- `${m.findings.total} findings${m.findings.total > 0 ? ` (${listed(m.findings.byLens)})` : ""}`
41543
+ `${m.findings.total} findings${m.findings.total > 0 ? ` (${listed(m.findings.byLens)})` : ""}`,
41544
+ `${m.flaky.total} flaky${m.flaky.total > 0 ? ` (${listed(m.flaky.byStep)})` : ""}`
41537
41545
  ].join("; ");
41538
41546
  };
41539
41547
  const runHistoryMetricsCommand = (refs, json, out) => gen(function* () {
@@ -42943,9 +42951,10 @@ const COMMAND_ROWS = [
42943
42951
  "Report how the kept processes went: slices whose naive pass",
42944
42952
  "landed or was rebuilt (the refactor-first rate), continuations",
42945
42953
  "per phase, Gtd-Cost by model and phase with the double work of",
42946
- "a rebuild, and quality-lap findings per lens — per process and",
42947
- "in total. Each <ref> is read as by history show, but a branch",
42948
- "name means every process kept from it; none means every ref.",
42954
+ "a rebuild, quality-lap findings per lens, and red checks that",
42955
+ "passed on their re-check — per process and in total. Each <ref>",
42956
+ "is read as by history show, but a branch name means every",
42957
+ "process kept from it; none means every ref.",
42949
42958
  "Reads commit subjects and trailers, never replays, so a",
42950
42959
  "changed config is no refusal. --json prints { processes, total",
42951
42960
  "} as one document; writes nothing"
package/dist/testkit.mjs CHANGED
@@ -10327,7 +10327,8 @@ const emptyTally = () => ({
10327
10327
  naive: 0,
10328
10328
  rebuild: 0
10329
10329
  },
10330
- byLens: record()
10330
+ byLens: record(),
10331
+ flaky: record()
10331
10332
  });
10332
10333
  const sum = (record) => Object.values(record).reduce((a, b) => a + b, 0);
10333
10334
  const finish = (t) => ({
@@ -10347,6 +10348,10 @@ const finish = (t) => ({
10347
10348
  findings: {
10348
10349
  total: sum(t.byLens),
10349
10350
  byLens: t.byLens
10351
+ },
10352
+ flaky: {
10353
+ total: sum(t.flaky),
10354
+ byStep: t.flaky
10350
10355
  }
10351
10356
  });
10352
10357
  const tallyCost = (t, open, costs, phase) => {
@@ -10403,6 +10408,7 @@ const processMetrics = (commits) => {
10403
10408
  if (subject.actor === "agent" && CONTINUE.test(at)) add(t.continuations, at.replace(CONTINUE, "$1"), 1);
10404
10409
  const lens = LENS.exec(at)?.[1];
10405
10410
  if (lens !== void 0 && commit.findings > 0) add(t.byLens, lens, commit.findings);
10411
+ if (message.marks.some((m) => m.name === "flaky")) add(t.flaky, at, 1);
10406
10412
  tallyDecision(t, open, at, subject.to);
10407
10413
  }
10408
10414
  return finish(t);
@@ -10417,6 +10423,7 @@ const totalMetrics = (all) => {
10417
10423
  t.doubleWork.naive += m.cost.doubleWork.naive;
10418
10424
  t.doubleWork.rebuild += m.cost.doubleWork.rebuild;
10419
10425
  for (const [k, v] of Object.entries(m.findings.byLens)) add(t.byLens, k, v);
10426
+ for (const [k, v] of Object.entries(m.flaky.byStep)) add(t.flaky, k, v);
10420
10427
  }
10421
10428
  return {
10422
10429
  processes: all.length,
@@ -10454,7 +10461,8 @@ const line = (label, m) => {
10454
10461
  `${label}: ${slices.decided} slices, ${slices.rebuilt} rebuilt (refactor-first ${percent}), ${slices.refactored} refactored`,
10455
10462
  `${continued} continuations${continued > 0 ? ` (${listed(m.continuations)})` : ""}`,
10456
10463
  `cost ${cost.total} (${listed(cost.byPhase)}; by model ${listed(cost.byModel) || "-"}), double work ${cost.doubleWork.total}`,
10457
- `${m.findings.total} findings${m.findings.total > 0 ? ` (${listed(m.findings.byLens)})` : ""}`
10464
+ `${m.findings.total} findings${m.findings.total > 0 ? ` (${listed(m.findings.byLens)})` : ""}`,
10465
+ `${m.flaky.total} flaky${m.flaky.total > 0 ? ` (${listed(m.flaky.byStep)})` : ""}`
10458
10466
  ].join("; ");
10459
10467
  };
10460
10468
  const runHistoryMetricsCommand = (refs, json, out) => gen(function* () {
@@ -11855,9 +11863,10 @@ const COMMAND_ROWS = [
11855
11863
  "Report how the kept processes went: slices whose naive pass",
11856
11864
  "landed or was rebuilt (the refactor-first rate), continuations",
11857
11865
  "per phase, Gtd-Cost by model and phase with the double work of",
11858
- "a rebuild, and quality-lap findings per lens — per process and",
11859
- "in total. Each <ref> is read as by history show, but a branch",
11860
- "name means every process kept from it; none means every ref.",
11866
+ "a rebuild, quality-lap findings per lens, and red checks that",
11867
+ "passed on their re-check — per process and in total. Each <ref>",
11868
+ "is read as by history show, but a branch name means every",
11869
+ "process kept from it; none means every ref.",
11861
11870
  "Reads commit subjects and trailers, never replays, so a",
11862
11871
  "changed config is no refusal. --json prints { processes, total",
11863
11872
  "} as one document; writes nothing"
@@ -17,10 +17,15 @@ const untilSettled = async (check, pending) => {
17
17
  };
18
18
  const STAMP = /\n<!-- gtd check [0-9a-f]+ -->\n?$/;
19
19
  const stripStamp = (text) => text.replace(STAMP, "\n");
20
+ /** `true` when the re-check passed, else the red re-check's report — the one the escalation describes. */
21
+ const recheckReport = async (health) => {
22
+ if (health.recheck === void 0) return void 0;
23
+ return await health.recheck() || stripStamp(read(health.report) ?? "");
24
+ };
20
25
  /**
21
26
  * Loop until the suite is green: check, and on red run `fix` — escalating
22
27
  * first once `fixCap` fixes are spent, or `sameFailure` calls the failure
23
- * identical to the previous round's.
28
+ * identical to the previous round's, unless `recheck` passes.
24
29
  */
25
30
  const healthy = async (fix, health) => {
26
31
  let fixes = health.fixesSoFar ?? 0;
@@ -34,6 +39,12 @@ const healthy = async (fix, health) => {
34
39
  const stuck = previous !== void 0 && await health.sameFailure(previous, current);
35
40
  previous = current;
36
41
  if (stuck || fixes >= health.fixCap) {
42
+ const rechecked = await recheckReport(health);
43
+ if (rechecked === true) {
44
+ health.escalations.rounds = 0;
45
+ return;
46
+ }
47
+ previous = rechecked ?? previous;
37
48
  await escalation(health.escalations, health.escalation);
38
49
  fixes = 0;
39
50
  }
@@ -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-C3PgP7XB.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-Bkxki2nK.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-Qt5FM6Ia.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-tAHx_AHe.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 () => {