@pmelab/gtd 38.1.0 → 39.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "38.1.0",
3
+ "version": "39.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
 
@@ -508,7 +509,10 @@ you open the `review` door.
508
509
  as-is, handing that document to the next fix turn as its instruction. Two
509
510
  such rounds with no fix is the terminal stop: the last analysis stays on disk
510
511
  instead of a third being written, and editing it is what gets the next
511
- attempt anywhere new.
512
+ attempt anywhere new. Before any escalation or red-baseline stop the suite
513
+ runs once more; a pass there continues without you and is recorded as
514
+ `Gtd-Mark: flaky=<check>` (see
515
+ [Re-check before a person](docs/configuration.md#re-check-before-a-person)).
512
516
 
513
517
  4. **You review — the refactor, or on request.** The feature workflow asks for
514
518
  one review only on `refactor-first`: of the refactor slice, before the
@@ -723,9 +727,39 @@ full trail with `gtd history fetch | sh`, then `gtd history show <ref>`.
723
727
  ```sh
724
728
  gtd history list # one line per kept process
725
729
  gtd history show <ref> # replay its steps, judge verdicts and approvals
730
+ gtd history metrics [ref...] # how the kept processes went, per process and in total
726
731
  gtd history fetch | sh # pull every kept ref from origin
727
732
  ```
728
733
 
734
+ `gtd history metrics` measures the bundled `feature` workflow from the kept refs
735
+ — every ref, or the ones named. It reads commit subjects and trailers only, so
736
+ it never replays and a config changed since is no refusal:
737
+
738
+ - **refactor-first rate** — of the slices whose naive pass was decided, the
739
+ share rebuilt rather than landed as it is; a landed pass that no longer
740
+ applies and is built afresh counts as rebuilt (`refactored` counts the
741
+ rebuilds a refactor came first for)
742
+ - **continuations** — agent turns that picked up a turn stopped at its context
743
+ limit, per phase (`seam.naive`, `implement.building`, `refactor.building`, …)
744
+ - **cost** — the `Gtd-Cost` a driver recorded with `gtd land --cost --model`, by
745
+ model and by phase (`naive`, `refactor`, `implement`, `other`), and the
746
+ **double work** of a rebuild: its naive pass plus the implement turns that
747
+ rebuilt it. By-model costs make two coder models comparable over the same kind
748
+ of work
749
+ - **findings** — the net count of `## ` findings each quality-lap lens added to
750
+ `.gtd/QUALITY.md`, keyed by the lap's lens path (`build.quality.correctness`,
751
+ `refactor.build.quality.correctness`, …)
752
+ - **flaky** — red checks that passed on their re-check (`Gtd-Mark: flaky=…`),
753
+ keyed by the re-check's full step name (`start-gate.recheck`,
754
+ `build.health.recheck`, …)
755
+
756
+ `--json` prints `{ processes, total }`; `--json=total.refactorFirstRate` prints
757
+ one value. Model, phase and lens keys may hold dots a path cannot reach: select
758
+ their record whole (`--json=total.cost.byModel`). A branch name selects every
759
+ process kept from that branch. A rate is `null` without a decided slice. Each
760
+ process names its `workflow` (`null` for the default one); the total spans every
761
+ ref measured, so name the refs to leave another workflow's processes out.
762
+
729
763
  `gtd history fetch` only prints a fetch script; pipe it into `sh` to run it.
730
764
  Beyond that one ship push, refs are not pushed or fetched by default. The ref
731
765
  holds every step commit, including steering files and code a later step removed,
@@ -781,9 +815,9 @@ install for any of these but `./testkit`: gtd resolves a `gtd.config.ts`'s
781
815
 
782
816
  The `gtd` bin's commands — `next`, `land`, `abandon`, `restore`, `validate`,
783
817
  `check`, `uncheck`, `install`, `summary`, `base`, `door`, `doors`, `slices`,
784
- `version`, `help`, `history list`, `history show`, `history fetch`, `strays`,
785
- and the tools `ui`, `lsp`, `loop`, `context-hook`, `judge` (`judge answer`,
786
- `judge run`) — are listed in the
818
+ `version`, `help`, `history list`, `history show`, `history metrics`,
819
+ `history fetch`, `strays`, and the tools `ui`, `lsp`, `loop`, `context-hook`,
820
+ `judge` (`judge answer`, `judge run`) — are listed in the
787
821
  [CLI reference](https://github.com/pmelab/gtd/blob/main/docs/cli.md).
788
822
  `gtd slices` lists the spans a flow's `slice()` calls mark. It refuses (exit 1)
789
823
  when HEAD neither closes nor sits inside a process, or when today's config
@@ -25449,4 +25449,4 @@ Struct({
25449
25449
  description: "Represents an issue returned by the ArrayFormatter formatter"
25450
25450
  });
25451
25451
  //#endregion
25452
- export { logWarning as $, NodeInspectSymbol as $i, map$4 as $n, make$20 as $r, provide$1 as $t, catchIf as A, add$2 as Ai, scopeMake as An, flatMap$3 as Ar, suspend$1 as At, flatMap$1 as B, getOrElse as Bi, get as Bn, sync$1 as Br, unsafeMakeLatch as Bt, all as C, toArray$1 as Ci, isInterrupted as Cn, exitFail as Cr, sandbox as Ct, async as D, GenericTag as Di, squash as Dn, exitZipRight as Dr, scopedWith as Dt, asVoid as E, unsafeHead as Ei, reduceWithContext as En, exitVoid$1 as Er, scoped as Et, ensuring as F, fromIterable$6 as Fi, join as Fn, map$5 as Fr, tryMap as Ft, forkIn as G, none$4 as Gi, TypeId$4 as Gn, whenEffect as Gr, zipLeft as Gt, fnUntraced as H, isSome as Hi, modify as Hn, uninterruptible$1 as Hr, when as Ht, exit as I, splitAt as Ii, sequential$1 as In, onInterrupt as Ir, tryPromise as It, ignore as J, left as Ji, all$2 as Jn, DeferredTypeId as Jr, spaced as Jt, forkScoped as K, some as Ki, Class as Kn, withFiberRuntime$1 as Kr, zipRight as Kt, fail$1 as L, filter$3 as Li, bind as Ln, scopeClose as Lr, try_ as Lt, die as M, getOption as Mi, _await as Mn, interruptAsFiber as Mr, tap as Mt, dieMessage as N, omit$1 as Ni, inheritAll as Nn, interruptFiber as Nr, tapErrorCause as Nt, catchAll as O, Reference as Oi, ensuring$1 as On, fiberId as Or, serviceOption as Ot, either as P, empty$22 as Pi, interruptAllAs as Pn, interruptible$2 as Pr, timeout as Pt, isEffect as Q, BaseProto as Qi, isFailure as Qn, ids as Qr, mergeAll as Qt, failCause as R, flatMap$6 as Ri, bindTo as Rn, succeed$5 as Rr, uninterruptible as Rt, addFinalizer as S, takeRight as Si, isDieType as Sn, exitAs as Sr, runtime as St, as as T, unsafeGet as Ti, map$3 as Tn, exitSucceed$1 as Tr, scopeWith as Tt, forEach as U, map$11 as Ui, set as Un, uninterruptibleMask$2 as Ur, withFiberRuntime as Ut, flatten as V, isNone as Vi, make$12 as Vn, tap$1 as Vr, unsafeMakeSemaphore as Vt, forkDaemon as W, match$6 as Wi, update$1 as Wn, void_$2 as Wr, zip as Wt, interruptible as X, right as Xi, fail$4 as Xn, combine$5 as Xr, tagged as Xt, interrupt as Y, match$7 as Yi, die$2 as Yn, empty$15 as Yr, TaggedError$1 as Yt, intoDeferred as Z, pipeArguments as Zi, failCause$2 as Zn, combineAll as Zr, fromEffect as Zt, decodeSync as _, of$1 as _i, fail$2 as _n, deferredIsDone as _r, raceWith as _t, Literal as a, constTrue as aa, size$2 as ai, updateContext as an, zipRight$1 as ar, matchCause as at, acquireRelease as b, reverse$1 as bi, interrupt$1 as bn, deferredUnsafeDone as br, runPromise as bt, String$ as c, dual as ca, append as ci, Scope as cn, asyncInterrupt as cr, negate as ct, Union as d, empty$19 as di, close as dn, deferredAwait as dr, orElse$1 as dt, format$4 as ea, none$2 as ei, provideMerge as en, mapBoth$2 as er, makeSemaphore as et, between as f, filter$1 as fi, extend$1 as fn, deferredComplete as fr, orElseSucceed as ft, standardSchemaV1 as g, map$9 as gi, die$1 as gn, deferredInterruptWith as gr, race as gt, optionalWith as h, isEmpty$5 as hi, NoSuchElementException as hn, deferredInterrupt as hr, provideService as ht, Defect as i, constFalse as ia, has$1 as ii, unsafeRunPromiseExit as in, zip$1 as ir, match as it, catchTag as j, get$7 as ji, scopeTag as jn, interrupt$2 as jr, sync as jt, catchAllCause as k, Tag as ki, forEachConcurrentDiscard as kn, fiberRefLocally as kr, succeed$1 as kt, Struct as l, identity as la, appendAll$1 as li, addFinalizer$1 as ln, catchAll$1 as lr, onError as lt, optional as m, isChunk as mi, make$2 as mn, deferredFailCause as mr, provide as mt, Boolean$ as n, equals$2 as na, make$22 as ni, succeed$2 as nn, succeed$4 as nr, mapBoth$1 as nt, Never as o, constUndefined as oa, decode as oi, forkIn$1 as on, as$1 as or, matchCauseEffect as ot, decodeUnknown as p, fromIterable$5 as pi, fork as pn, deferredFail as pr, promise as pt, gen as q, isLeft as qi, spanTag as qn, zipRight$2 as qr, zipWith as qt, Cause as r, hasProperty as ra, set$3 as ri, unsafeFork as rn, void_$1 as rr, mapError$1 as rt, Number$ as s, constVoid as sa, isFinite as si, unsafeMakeSemaphore$1 as sn, asVoid$1 as sr, matchEffect as st, Array$ as t, toJSON as ta, get$4 as ti, scoped$1 as tn, match$3 as tr, map$1 as tt, TaggedError as u, pipe as ua, drop as ui, addFinalizerExit as un, currentContext as ur, orDie as ut, encodeSync as v, prepend$1 as vi, failureOrCause as vn, deferredMake as vr, repeat as vt, annotateLogs as w, unsafeFromArray as wi, isInterruptedOnly as wn, exitFailCause$1 as wr, scope as wt, acquireUseRelease as x, take as xi, interruptors as xn, deferredUnsafeMake as xr, runPromiseExit as xt, _void as y, reduce$6 as yi, flipCauseOption as yn, deferredSucceed as yr, retry as yt, fiberIdWith as z, fromNullable as zi, sleep as zn, suspend$3 as zr, uninterruptibleMask as zt };
25452
+ export { logWarning as $, BaseProto as $i, map$4 as $n, make$20 as $r, provide$1 as $t, catchIf as A, add$2 as Ai, scopeMake as An, flatMap$3 as Ar, suspend$1 as At, flatMap$1 as B, getOrElse as Bi, get as Bn, sync$1 as Br, unsafeMakeLatch as Bt, all as C, toArray$1 as Ci, isInterrupted as Cn, exitFail as Cr, sandbox as Ct, async as D, GenericTag as Di, squash as Dn, exitZipRight as Dr, scopedWith as Dt, asVoid as E, unsafeHead as Ei, reduceWithContext as En, exitVoid$1 as Er, scoped as Et, ensuring as F, fromIterable$6 as Fi, join as Fn, map$5 as Fr, tryMap as Ft, forkIn as G, match$6 as Gi, TypeId$4 as Gn, whenEffect as Gr, zipLeft as Gt, fnUntraced as H, isNone as Hi, modify as Hn, uninterruptible$1 as Hr, when as Ht, exit as I, splitAt as Ii, sequential$1 as In, onInterrupt as Ir, tryPromise as It, ignore as J, isLeft as Ji, all$2 as Jn, DeferredTypeId as Jr, spaced as Jt, forkScoped as K, none$4 as Ki, Class as Kn, withFiberRuntime$1 as Kr, zipRight as Kt, fail$1 as L, filter$3 as Li, bind as Ln, scopeClose as Lr, try_ as Lt, die as M, getOption as Mi, _await as Mn, interruptAsFiber as Mr, tap as Mt, dieMessage as N, omit$1 as Ni, inheritAll as Nn, interruptFiber as Nr, tapErrorCause as Nt, catchAll as O, Reference as Oi, ensuring$1 as On, fiberId as Or, serviceOption as Ot, either as P, empty$22 as Pi, interruptAllAs as Pn, interruptible$2 as Pr, timeout as Pt, isEffect as Q, pipeArguments as Qi, isFailure as Qn, ids as Qr, mergeAll as Qt, failCause as R, flatMap$6 as Ri, bindTo as Rn, succeed$5 as Rr, uninterruptible as Rt, addFinalizer as S, takeRight as Si, isDieType as Sn, exitAs as Sr, runtime as St, as as T, unsafeGet as Ti, map$3 as Tn, exitSucceed$1 as Tr, scopeWith as Tt, forEach as U, isSome as Ui, set as Un, uninterruptibleMask$2 as Ur, withFiberRuntime as Ut, flatten as V, getOrUndefined as Vi, make$12 as Vn, tap$1 as Vr, unsafeMakeSemaphore as Vt, forkDaemon as W, map$11 as Wi, update$1 as Wn, void_$2 as Wr, zip as Wt, interruptible as X, match$7 as Xi, fail$4 as Xn, combine$5 as Xr, tagged as Xt, interrupt as Y, left as Yi, die$2 as Yn, empty$15 as Yr, TaggedError$1 as Yt, intoDeferred as Z, right as Zi, failCause$2 as Zn, combineAll as Zr, fromEffect as Zt, decodeSync as _, of$1 as _i, fail$2 as _n, deferredIsDone as _r, raceWith as _t, Literal as a, constFalse as aa, size$2 as ai, updateContext as an, zipRight$1 as ar, matchCause as at, acquireRelease as b, reverse$1 as bi, interrupt$1 as bn, deferredUnsafeDone as br, runPromise as bt, String$ as c, constVoid as ca, append as ci, Scope as cn, asyncInterrupt as cr, negate as ct, Union as d, pipe as da, empty$19 as di, close as dn, deferredAwait as dr, orElse$1 as dt, NodeInspectSymbol as ea, none$2 as ei, provideMerge as en, mapBoth$2 as er, makeSemaphore as et, between as f, filter$1 as fi, extend$1 as fn, deferredComplete as fr, orElseSucceed as ft, standardSchemaV1 as g, map$9 as gi, die$1 as gn, deferredInterruptWith as gr, race as gt, optionalWith as h, isEmpty$5 as hi, NoSuchElementException as hn, deferredInterrupt as hr, provideService as ht, Defect as i, hasProperty as ia, has$1 as ii, unsafeRunPromiseExit as in, zip$1 as ir, match as it, catchTag as j, get$7 as ji, scopeTag as jn, interrupt$2 as jr, sync as jt, catchAllCause as k, Tag as ki, forEachConcurrentDiscard as kn, fiberRefLocally as kr, succeed$1 as kt, Struct as l, dual as la, appendAll$1 as li, addFinalizer$1 as ln, catchAll$1 as lr, onError as lt, optional as m, isChunk as mi, make$2 as mn, deferredFailCause as mr, provide as mt, Boolean$ as n, toJSON as na, make$22 as ni, succeed$2 as nn, succeed$4 as nr, mapBoth$1 as nt, Never as o, constTrue as oa, decode as oi, forkIn$1 as on, as$1 as or, matchCauseEffect as ot, decodeUnknown as p, fromIterable$5 as pi, fork as pn, deferredFail as pr, promise as pt, gen as q, some as qi, spanTag as qn, zipRight$2 as qr, zipWith as qt, Cause as r, equals$2 as ra, set$3 as ri, unsafeFork as rn, void_$1 as rr, mapError$1 as rt, Number$ as s, constUndefined as sa, isFinite as si, unsafeMakeSemaphore$1 as sn, asVoid$1 as sr, matchEffect as st, Array$ as t, format$4 as ta, get$4 as ti, scoped$1 as tn, match$3 as tr, map$1 as tt, TaggedError as u, identity as ua, drop as ui, addFinalizerExit as un, currentContext as ur, orDie as ut, encodeSync as v, prepend$1 as vi, failureOrCause as vn, deferredMake as vr, repeat as vt, annotateLogs as w, unsafeFromArray as wi, isInterruptedOnly as wn, exitFailCause$1 as wr, scope as wt, acquireUseRelease as x, take as xi, interruptors as xn, deferredUnsafeMake as xr, runPromiseExit as xt, _void as y, reduce$6 as yi, flipCauseOption as yn, deferredSucceed as yr, retry as yt, fiberIdWith as z, fromNullable as zi, sleep as zn, suspend$3 as zr, uninterruptibleMask as zt };
@@ -1,6 +1,6 @@
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
- import { c as String$, g as standardSchemaV1, h as optionalWith, l as Struct, n as Boolean$, s as Number$ } from "./Schema-DO5ttJaI.mjs";
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
5
5
  const FILL = "";
6
6
  const commentLength = (rest) => {
@@ -1580,6 +1580,19 @@ const baseline = (name, label = "Checking the baseline") => check(name, env.test
1580
1580
  report: FEEDBACK,
1581
1581
  label
1582
1582
  });
1583
+ /**
1584
+ * Re-run a red check as step `name` before a person sees it. Green records
1585
+ * `Gtd-Mark: flaky=<red>` on the re-check's landing and resolves `true`.
1586
+ */
1587
+ const recheck = async (name, red, options = {}) => {
1588
+ const green = await check(name, env.testCommand ?? "", {
1589
+ ...options,
1590
+ report: FEEDBACK,
1591
+ label: "Re-running checks"
1592
+ });
1593
+ if (green) mark("flaky", red);
1594
+ return green;
1595
+ };
1583
1596
  const escalationSteps = (rules) => ({
1584
1597
  limit: 2,
1585
1598
  describe: () => describeEscalation(rules),
@@ -1630,15 +1643,17 @@ const sameFailure = async (previous, current) => {
1630
1643
  });
1631
1644
  return comparable && truncated.length === 0 && answered(answers.verdict, "identical", numeric(vars.judgeIdenticalMinP, Infinity));
1632
1645
  };
1646
+ const healthSweep = (options) => [ESCALATION, ...options.sweepOnGreen ?? []];
1633
1647
  const runSuite = (options) => check("health.check", env.testCommand ?? "", {
1634
1648
  report: FEEDBACK,
1635
1649
  label: "Running checks",
1636
- sweepOnGreen: [ESCALATION, ...options.sweepOnGreen ?? []]
1650
+ sweepOnGreen: healthSweep(options)
1637
1651
  });
1638
1652
  /**
1639
1653
  * Loop until the suite is green: check, and on red run `fix` — escalating
1640
1654
  * first once `FIX_CAP` fixes are spent, or a judge calls the failure
1641
- * identical to the previous round's.
1655
+ * identical to the previous round's. An escalation re-runs the suite once
1656
+ * first; a green re-run ends the loop.
1642
1657
  */
1643
1658
  const healthy = (fix, options = {}) => healthy$1(guarded(fix, options.guard), {
1644
1659
  check: () => runSuite(options),
@@ -1647,14 +1662,16 @@ const healthy = (fix, options = {}) => healthy$1(guarded(fix, options.guard), {
1647
1662
  fixesSoFar: options.fixesSoFar ?? 0,
1648
1663
  sameFailure,
1649
1664
  escalations: options.escalations ?? { rounds: 0 },
1665
+ recheck: () => recheck("health.recheck", "health.check", { sweepOnGreen: healthSweep(options) }),
1650
1666
  escalation: guardedSteps(options.guard, options.describeGuard, options.guardRests, options.rules)
1651
1667
  });
1652
1668
  /**
1653
- * Hold the process at `<name>.blocked` until the suite is green. `guard` runs
1669
+ * Hold the process at `<name>.blocked` until the suite is green, re-running a
1670
+ * red check once as `<name>.recheck` before resting. `guard` runs
1654
1671
  * after each `blocked` landing only — never after a check, whose refusal would
1655
1672
  * re-run the same script forever.
1656
1673
  */
1657
- const gate = (name, message, guard) => gate$1(name, () => baseline("check"), guarded(() => human("blocked", {
1674
+ const gate = (name, message, guard) => gate$1(name, async () => await baseline("check") || recheck("recheck", `${name}.check`), guarded(() => human("blocked", {
1658
1675
  message,
1659
1676
  label: "Baseline is red",
1660
1677
  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-CfQ8eurM.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-B6tTYjeb.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";