@pmelab/gtd 38.0.0 → 38.2.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 (39) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +36 -3
  3. package/dist/chunks/{Schema-DO5ttJaI.mjs → Schema-C6fLlQzx.mjs} +1 -1
  4. package/dist/chunks/{reserved-sfMdy7v7.mjs → reserved-C3PgP7XB.mjs} +56 -12
  5. package/dist/chunks/{tail-Cgca5Gp-.mjs → tail-Bkxki2nK.mjs} +1 -1
  6. package/dist/chunks/{wire-CSsa3Wxd.mjs → wire-BJuZWR3C.mjs} +1 -1
  7. package/dist/gtd.bundle.mjs +292 -53
  8. package/dist/testkit.mjs +257 -21
  9. package/dist/wire.mjs +1 -1
  10. package/dist/workflows/cleanup.mjs +2 -2
  11. package/dist/workflows/feature.mjs +158 -42
  12. package/dist/workflows/fix.mjs +2 -2
  13. package/dist/workflows/review.mjs +2 -2
  14. package/dist/workflows/rollout.mjs +1 -1
  15. package/dist/workflows.mjs +230 -70
  16. package/package.json +1 -1
  17. package/src/cli/Cli.test.ts +15 -3
  18. package/src/cli/Cli.ts +61 -28
  19. package/src/cli/History.ts +1 -19
  20. package/src/cli/Metrics.test.ts +224 -0
  21. package/src/cli/Metrics.ts +310 -0
  22. package/src/cli/Output.ts +13 -0
  23. package/src/cli/Program.test.ts +3 -1
  24. package/src/cli/Program.ts +4 -0
  25. package/src/testkit/steps.ts +4 -0
  26. package/src/wire/ContextLimit.ts +1 -1
  27. package/src/workflows/feature/continuation-cap.feature +65 -0
  28. package/src/workflows/feature/continuation-rebuild.feature +88 -0
  29. package/src/workflows/feature/continuation-slices.feature +64 -0
  30. package/src/workflows/feature/continuation.feature +205 -0
  31. package/src/workflows/feature/criteria.ts +11 -0
  32. package/src/workflows/feature/refactor.ts +64 -30
  33. package/src/workflows/feature/seam.ts +102 -30
  34. package/src/workflows/shared/continuation.ts +92 -0
  35. package/src/workflows/shared/options.ts +2 -0
  36. package/src/workflows/shared/steps.ts +1 -0
  37. package/src/workflows/shared/text.test.ts +6 -2
  38. package/src/workflows/shared/text.ts +104 -8
  39. package/src/workflows/shared/vars.ts +5 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "38.0.0",
3
+ "version": "38.2.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
@@ -313,6 +313,12 @@ note, past 70% (or when the context is about to be compacted) the turn stops and
313
313
  nothing lands. A step's `context` option, or the bundled workflows'
314
314
  `contextLimit` option, moves the two limits — see
315
315
  [Context limits](https://github.com/pmelab/gtd/blob/main/docs/driver.md#context-limits).
316
+ In `feature`, a naive pass, refactor or build turn that wrapped up that way is a
317
+ snapshot: a fresh turn picks up from it with the handoff note and the criteria
318
+ still open, up to 3 times per phase, across its retries (`continuations`), and a
319
+ phase whose continuations keep stopping, or close nothing twice in a row
320
+ (`continuationStall`), rests for a person — see
321
+ [Continuations](https://github.com/pmelab/gtd/blob/main/docs/configuration.md#continuations).
316
322
 
317
323
  ### Or drive it from inside Claude Code
318
324
 
@@ -717,9 +723,36 @@ full trail with `gtd history fetch | sh`, then `gtd history show <ref>`.
717
723
  ```sh
718
724
  gtd history list # one line per kept process
719
725
  gtd history show <ref> # replay its steps, judge verdicts and approvals
726
+ gtd history metrics [ref...] # how the kept processes went, per process and in total
720
727
  gtd history fetch | sh # pull every kept ref from origin
721
728
  ```
722
729
 
730
+ `gtd history metrics` measures the bundled `feature` workflow from the kept refs
731
+ — every ref, or the ones named. It reads commit subjects and trailers only, so
732
+ it never replays and a config changed since is no refusal:
733
+
734
+ - **refactor-first rate** — of the slices whose naive pass was decided, the
735
+ share rebuilt rather than landed as it is; a landed pass that no longer
736
+ applies and is built afresh counts as rebuilt (`refactored` counts the
737
+ rebuilds a refactor came first for)
738
+ - **continuations** — agent turns that picked up a turn stopped at its context
739
+ limit, per phase (`seam.naive`, `implement.building`, `refactor.building`, …)
740
+ - **cost** — the `Gtd-Cost` a driver recorded with `gtd land --cost --model`, by
741
+ model and by phase (`naive`, `refactor`, `implement`, `other`), and the
742
+ **double work** of a rebuild: its naive pass plus the implement turns that
743
+ rebuilt it. By-model costs make two coder models comparable over the same kind
744
+ of work
745
+ - **findings** — the net count of `## ` findings each quality-lap lens added to
746
+ `.gtd/QUALITY.md`, keyed by the lap's lens path (`build.quality.correctness`,
747
+ `refactor.build.quality.correctness`, …)
748
+
749
+ `--json` prints `{ processes, total }`; `--json=total.refactorFirstRate` prints
750
+ one value. Model, phase and lens keys may hold dots a path cannot reach: select
751
+ their record whole (`--json=total.cost.byModel`). A branch name selects every
752
+ process kept from that branch. A rate is `null` without a decided slice. Each
753
+ process names its `workflow` (`null` for the default one); the total spans every
754
+ ref measured, so name the refs to leave another workflow's processes out.
755
+
723
756
  `gtd history fetch` only prints a fetch script; pipe it into `sh` to run it.
724
757
  Beyond that one ship push, refs are not pushed or fetched by default. The ref
725
758
  holds every step commit, including steering files and code a later step removed,
@@ -775,9 +808,9 @@ install for any of these but `./testkit`: gtd resolves a `gtd.config.ts`'s
775
808
 
776
809
  The `gtd` bin's commands — `next`, `land`, `abandon`, `restore`, `validate`,
777
810
  `check`, `uncheck`, `install`, `summary`, `base`, `door`, `doors`, `slices`,
778
- `version`, `help`, `history list`, `history show`, `history fetch`, `strays`,
779
- and the tools `ui`, `lsp`, `loop`, `context-hook`, `judge` (`judge answer`,
780
- `judge run`) — are listed in the
811
+ `version`, `help`, `history list`, `history show`, `history metrics`,
812
+ `history fetch`, `strays`, and the tools `ui`, `lsp`, `loop`, `context-hook`,
813
+ `judge` (`judge answer`, `judge run`) — are listed in the
781
814
  [CLI reference](https://github.com/pmelab/gtd/blob/main/docs/cli.md).
782
815
  `gtd slices` lists the spans a flow's `slice()` calls mark. It refuses (exit 1)
783
816
  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
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";
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) => {
@@ -136,7 +136,9 @@ const defaults = {
136
136
  sliceMaxCriteria: "8",
137
137
  flag_risk_tier: "0",
138
138
  qualityReviews: "correctness, owasp-security, ponytail-review, test-audit, conventions, spec-challenge",
139
- refactorQualityReviews: "correctness, ponytail-review, conventions"
139
+ refactorQualityReviews: "correctness, ponytail-review, conventions",
140
+ continuations: "3",
141
+ continuationStall: "2"
140
142
  };
141
143
  /** Environment settings. */
142
144
  const envDefaults = {
@@ -654,7 +656,7 @@ ${strays.map((path) => ` - \`${displayPath(path)}\``).join("\n")}
654
656
  - Introduce no new flag read and add no second read of an existing one:
655
657
  every match of \`${pattern}\` may move, never increase in number`;
656
658
  const withRules = (rules) => rules === void 0 ? "" : `${rules}\n`;
657
- const refactorBuildingPrompt = ({ pkg, strays, pattern, reference, feedback = false, note = false }) => `${stateFileRules}
659
+ const refactorBuildingPrompt = ({ pkg, strays, pattern, reference, feedback = false, note = false, from }) => `${stateFileRules}
658
660
  - The package to implement is \`${pkg}\`; never delete it
659
661
  ${note ? `- The package's \`## Refactor note\` section names the seams to open and the
660
662
  shape each takes, written from the whole naive pass: build exactly those
@@ -669,8 +671,9 @@ ${reference === void 0 ? "" : `- Reference only: \`git show ${reference} -- ${st
669
671
  restructuring afresh here, minus the feature's own behaviour. A
670
672
  refactor that touches none of it is wrong, however clean — never
671
673
  swap in an unrelated tidy-up
672
- `}- Leave everything uncommitted and finish your turn — do not commit
673
- `;
674
+ `}${handoffRule()}
675
+ - Leave everything uncommitted and finish your turn — do not commit
676
+ ${from === void 0 ? "" : continuationLines(from)}`;
674
677
  const refactorFixPrompt = (strays, pattern) => `${stateFileRules}
675
678
  ${fixFeedbackPrompt}
676
679
  ${refactorRules(strays, pattern)}
@@ -1238,7 +1241,7 @@ Print the closing message and stop — this writes nothing itself.
1238
1241
  const listed = (paths, unreadable = []) => paths.map((path) => ` - \`${displayPath(path)}\`${unreadable.includes(path) ? " (unreadable)" : ""}`).join("\n");
1239
1242
  const isRed = (retry) => "red" in retry;
1240
1243
  const isFault = (retry) => "fault" in retry;
1241
- const pickUp = (hash) => `Pick it up — \`git diff --binary ${hash}^ ${hash} | git apply\``;
1244
+ const pickUp = (hash, exclude) => `Pick it up — \`git diff --binary --no-color --no-ext-diff --no-textconv ${hash}^ ${hash}${exclude === void 0 ? "" : ` -- ${quote(":(top)")} ${quote(`:(top,exclude)${exclude}`)}`} | git apply\``;
1242
1245
  const criteriaBinding = (refused) => `- Its \`## Acceptance Criteria\` (\`AC-<n>\`) bind this turn: re-read them from
1243
1246
  the file, never from memory, and stop only when each one holds. They are
1244
1247
  settled — ${refused ? "a turn that changes them is refused" : "leave them as they are"}`;
@@ -1257,7 +1260,33 @@ re-implement. It changed these files outside the expected set:
1257
1260
  ${listed(retry.strays)}
1258
1261
  Start over and choose a different approach; do not rebuild the same diff.
1259
1262
  `;
1260
- const naivePassPrompt = (retry, slice) => `${stateFileRules}
1263
+ const quoted = (text) => text.trimEnd().split("\n").map((line) => line === "" ? "" : ` > ${line}`).join("\n");
1264
+ /** Every turn a continuation may pick up tells its agent where the note goes. */
1265
+ const handoffRule = () => `- Should you be told to stop at a context limit, write the handoff note to
1266
+ \`.gtd/HANDOFF.md\` — what you touched, what was next — and finish your turn:
1267
+ a fresh turn picks up from what you leave, broken or not. That file is the one
1268
+ exception to any path rule here; write it at no other time`;
1269
+ /** `pickUp`: how the snapshot gets into the tree — absent when it already is. */
1270
+ const continuationLines = (from, pickUp) => `
1271
+ Your previous turn stopped at its context limit; this fresh turn picks up from
1272
+ its snapshot (\`${from.snapshot}\`). ${pickUp === void 0 ? "Its work is already in the tree." : `${pickUp}.`} Its handoff note:
1273
+
1274
+ ${quoted(from.note === "" ? "(empty)" : from.note)}
1275
+
1276
+ Still open: ${from.open.length === 0 ? "nothing the workflow can count — finish what the note says is next" : from.open.join(", ")}.
1277
+ ${pickUp === void 0 ? "Delete `.gtd/HANDOFF.md` once you are done; rewrite it should you be stopped again.\n" : ""}`;
1278
+ const oversizedMessage = (phase, from, why, narrowable = true) => `The ${phase} keeps outgrowing its context: ${"cap" in why ? why.cap === 0 ? "continuations are off (`continuations` is 0) and the turn stopped at its context limit" : `${why.cap} continuation${why.cap === 1 ? "" : "s"} ran and the last still stopped at its context limit` : `the last ${why.stall} continuation${why.stall === 1 ? "" : "s"} closed nothing still open`}. The last snapshot is \`${from.snapshot}\`; still open: ${from.open.length === 0 ? "nothing countable" : from.open.join(", ")}.
1279
+
1280
+ Its handoff note:
1281
+
1282
+ ${quoted(from.note === "" ? "(empty)" : from.note)}
1283
+
1284
+ Land to go on: a fresh round of continuations picks up from that snapshot${narrowable ? ` — narrow
1285
+ \`.gtd/REQUIREMENTS.md\` first if the change is too big, or raise the context limit` : ` — raise
1286
+ the context limit first if the refactor is too big`}. \`gtd abandon\` gives the change up.
1287
+ `;
1288
+ /** `from`: a continuation of a pass that stopped at its context limit, in place of `retry`'s pick-up. */
1289
+ const naivePassPrompt = (retry, slice, from) => `${stateFileRules}
1261
1290
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
1262
1291
  ${sliceLine(slice)}${criteriaBinding(false)}
1263
1292
  - Implement them the quickest way that works — the naive version. Do not
@@ -1274,9 +1303,20 @@ ${sliceLine(slice)}${criteriaBinding(false)}
1274
1303
  - Run \`${env.testCommand}\` and finish only once it is green with every
1275
1304
  criterion's test passing: the next step runs it on your diff and sends a red
1276
1305
  pass back
1277
- - Leave \`.gtd/\` untouched
1306
+ - Leave \`.gtd/\` untouched, a handoff note excepted
1307
+ ${handoffRule()}
1278
1308
  - Leave everything uncommitted and finish your turn — do not commit
1279
- ${retry === void 0 ? "" : naiveRetry(retry)}`;
1309
+ ${from !== void 0 ? `${continuationLines(from, pickUp(from.snapshot, ".gtd/HANDOFF.md"))}${retry === void 0 ? "" : isFault(retry) ? `
1310
+ The pass this continues was a retry: its tests fell short, and still must not:
1311
+ ${retry.fault}
1312
+ ` : isRed(retry) ? `
1313
+ The pass this continues was a retry after a red suite (\`.gtd/FEEDBACK.md\`
1314
+ holds the output): finish only once the suite is green.
1315
+ ` : `
1316
+ The pass this continues was a retry after a \`reject\`: keep away from the
1317
+ approach that changed these files outside the expected set:
1318
+ ${listed(retry.strays)}
1319
+ `}` : retry === void 0 ? "" : naiveRetry(retry)}`;
1280
1320
  const carries = (rebuild) => (rebuild.acceptance?.ids.length ?? 0) > 0;
1281
1321
  const rebuildLines = ({ note, acceptance }) => `${note === void 0 ? "" : `- The refactor note (\`.gtd/REFACTOR-NOTE.md\`) named the seams the refactor
1282
1322
  opened; plug the change into them:
@@ -1313,7 +1353,7 @@ const flagLine = (flagMode, tier, amend, sliced) => !flagMode ? "- Add no releas
1313
1353
  change added; only when none did, behind a new one, following the
1314
1354
  repository's flag conventions` : `- Put the new behaviour behind a new release flag, following the
1315
1355
  repository's flag conventions`;
1316
- const implementBuildingPrompt = (reference, tier, amend, afterRefactor, flagMode = true, rebuild = {}, slice) => `${stateFileRules}
1356
+ const implementBuildingPrompt = (reference, tier, amend, afterRefactor, flagMode = true, rebuild = {}, slice, from) => `${stateFileRules}
1317
1357
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
1318
1358
  ${sliceLine(slice)}${criteriaBinding(true)}
1319
1359
  ${afterRefactor === true ? afterRefactorLine(rebuild) : ""}${amend === true ? `- The change already exists in the tree from an earlier lap; amend it to meet
@@ -1328,9 +1368,10 @@ ${afterRefactor === true ? afterRefactorLine(rebuild) : ""}${amend === true ? `-
1328
1368
  - Write the tests first, then the code that makes them pass. Name each
1329
1369
  criterion's ID in the test that covers it (\`[AC-3] …\`): once the suite is
1330
1370
  green a judge checks every ID has a passing test, and sends uncovered ones back
1371
+ ${handoffRule()}
1331
1372
  - Leave \`.gtd/REQUIREMENTS.md\` in place and everything uncommitted, then
1332
1373
  finish your turn
1333
- `;
1374
+ ${from === void 0 ? "" : continuationLines(from)}`;
1334
1375
  const implementFixPrompt = () => `${stateFileRules}
1335
1376
  ${fixFeedbackPrompt}
1336
1377
  - Leave everything uncommitted and finish your turn — do not commit
@@ -1435,6 +1476,7 @@ const REFACTOR = ".gtd/REFACTOR.md";
1435
1476
  const REFACTOR_NOTE = ".gtd/REFACTOR-NOTE.md";
1436
1477
  const QUALITY = ".gtd/QUALITY.md";
1437
1478
  const SLICES = ".gtd/SLICES.md";
1479
+ const HANDOFF = ".gtd/HANDOFF.md";
1438
1480
  const planner = () => env.plannerModel ?? "";
1439
1481
  const coder = () => env.coderModel ?? "";
1440
1482
  const fixCheck = (rules) => agentWithSkills("fix", buildFixPrompt(rules), {
@@ -1631,6 +1673,8 @@ const ShippedOptions = Struct({
1631
1673
  sliceMaxCriteria: setting,
1632
1674
  qualityReviews: setting,
1633
1675
  refactorQualityReviews: setting,
1676
+ continuations: setting,
1677
+ continuationStall: setting,
1634
1678
  testCommand: setting,
1635
1679
  plannerModel: setting,
1636
1680
  coderModel: setting,
@@ -1734,4 +1778,4 @@ const steering = {
1734
1778
  [REVIEW]: "review"
1735
1779
  };
1736
1780
  //#endregion
1737
- export { naivePassPrompt as $, buildReviewAnswerQuestionsPrompt as A, coveringPrompt as B, fixQuality as C, flagReferences as Ct, reviewQuality as D, reviewMissing as E, requireFlag as Et, builderSystem as F, designTriagePrompt as G, deferNotesScript as H, clearCriteriaMessage as I, handOffAwaitReviewMessage as J, finisherSystem as K, clearCriteriaQuestion as L, buildReviewCollectingPrompt as M, buildReviewFixNitsPrompt as N, reviewing as O, buildReviewTriageMessage as P, naiveApplyFailure as Q, coverageMessage as R, fixCheck as S, flagMentions as St, planner as T, registryEntry as Tt, designGateAnswerMessage as U, criterionLine as V, designSystem as W, implementBuildingPrompt as X, idleMessage as Y, implementFixPrompt as Z, REQUIREMENTS as _, unwindFailedMessage as _t, flagModeOn as a, refactorNotePrompt as at, awaitReview as b, envDefaults as bt, baseline as c, reviewEditsCapture as ct, guarded as d, reviewerSystem as dt, normaliseCriteriaScript as et, healthy as f, seamByHandMessage as ft, REFACTOR_NOTE as g, uncoveredMessage as gt, REFACTOR as h, startGateBlockedMessage as ht, skills as i, refactorFixPrompt as it, buildReviewAwaitAfterDeferMessage as j, agentWithSkills as k, escalation as l, reviewGateBlockedMessage as lt, QUALITY as m, slicingPrompt as mt, summary as n, refactorBuildingPrompt as nt, flagShipped as o, refactorRules as ot, FEEDBACK as p, seamSteerMessage as pt, fixReviewNotesPrompt as q, access as r, refactorDroppedRefusal as rt, shipped as s, reviewEditNotesCapture as st, steering as t, qualityUnresolvedMessage as tt, gate as u, reviewNotesCapture as ut, REVIEW as v, unwindFailure as vt, fixRisks as w, flipProblems as wt, coder as x, testFileGlobs as xt, SLICES as y, defaults as yt, coverageQuestion as z };
1781
+ export { naiveApplyFailure as $, agentWithSkills as A, coverageQuestion as B, fixCheck as C, testFileGlobs as Ct, reviewMissing as D, registryEntry as Dt, planner as E, flipProblems as Et, buildReviewTriageMessage as F, designSystem as G, criterionLine as H, builderSystem as I, fixReviewNotesPrompt as J, designTriagePrompt as K, clearCriteriaMessage as L, buildReviewAwaitAfterDeferMessage as M, buildReviewCollectingPrompt as N, reviewQuality as O, requireFlag as Ot, buildReviewFixNitsPrompt as P, implementFixPrompt as Q, clearCriteriaQuestion as R, coder as S, envDefaults as St, fixRisks as T, flagReferences as Tt, deferNotesScript as U, coveringPrompt as V, designGateAnswerMessage as W, idleMessage as X, handOffAwaitReviewMessage as Y, implementBuildingPrompt as Z, REFACTOR_NOTE as _, startGateBlockedMessage as _t, flagModeOn as a, refactorDroppedRefusal as at, SLICES as b, unwindFailure as bt, baseline as c, refactorRules as ct, guarded as d, reviewGateBlockedMessage as dt, naivePassPrompt as et, healthy as f, reviewNotesCapture as ft, REFACTOR as g, slicingPrompt as gt, QUALITY as h, seamSteerMessage as ht, skills as i, refactorBuildingPrompt as it, buildReviewAnswerQuestionsPrompt as j, reviewing as k, escalation as l, reviewEditNotesCapture as lt, HANDOFF as m, seamByHandMessage as mt, summary as n, oversizedMessage as nt, flagShipped as o, refactorFixPrompt as ot, FEEDBACK as p, reviewerSystem as pt, finisherSystem as q, access as r, qualityUnresolvedMessage as rt, shipped as s, refactorNotePrompt as st, steering as t, normaliseCriteriaScript as tt, gate as u, reviewEditsCapture as ut, REQUIREMENTS as v, uncoveredMessage as vt, fixQuality as w, flagMentions as wt, awaitReview as x, defaults as xt, REVIEW as y, unwindFailedMessage as yt, coverageMessage as z };
@@ -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 fixQuality, D as reviewQuality, E as reviewMissing, J as handOffAwaitReviewMessage, O as reviewing, S as fixCheck, b as awaitReview, d as guarded, f as healthy, l as escalation, m as QUALITY, v as REVIEW, w as fixRisks } from "./reserved-sfMdy7v7.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";
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";
@@ -591,7 +591,7 @@ const contextHook = (input) => {
591
591
  };
592
592
  if (tokens >= limit.soft * window) return {
593
593
  kind: "soft",
594
- reason: `Context limit: ${used} used, past the ${percent(limit.soft)} soft limit. Stop working now. Do not start anything new; end your turn with a short handoff note as your answer: what you touched, and what was next.`
594
+ reason: `Context limit: ${used} used, past the ${percent(limit.soft)} soft limit. Stop working now. Do not start anything new; end your turn with a short handoff note as your answer: what you touched, and what was next. Where your instructions name a file for that note, write it there too.`
595
595
  };
596
596
  };
597
597
  /** A decision as Claude Code's settings-hook JSON output. */