@pmelab/gtd 52.1.0 → 54.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 (94) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +91 -129
  3. package/claude/hooks/drive.ts +0 -3
  4. package/claude/hooks/register.tsx +3 -68
  5. package/claude/hooks/ship.ts +85 -1595
  6. package/dist/chunks/{reserved-DxB5CROj.mjs → reserved-CQ7amcKG.mjs} +45 -132
  7. package/dist/chunks/{review-CKMSdvgW.mjs → review-CXzUXJHL.mjs} +12 -5
  8. package/dist/chunks/{tail-QZc7Uz2U.mjs → tail-JZ9PAOwp.mjs} +2 -14
  9. package/dist/chunks/{wire-BJuZWR3C.mjs → wire-C12dahrH.mjs} +2 -130
  10. package/dist/flows.mjs +1 -16
  11. package/dist/gtd.bundle.mjs +476 -982
  12. package/dist/testkit.mjs +58 -417
  13. package/dist/wire.mjs +2 -2
  14. package/dist/workflows/cleanup.mjs +2 -2
  15. package/dist/workflows/feature.mjs +112 -363
  16. package/dist/workflows/fix.mjs +2 -2
  17. package/dist/workflows/review.mjs +3 -3
  18. package/dist/workflows/rollout.mjs +1 -1
  19. package/dist/workflows.mjs +321 -677
  20. package/package.json +1 -1
  21. package/src/cli/Cli.test.ts +1 -86
  22. package/src/cli/Cli.ts +13 -133
  23. package/src/cli/Metrics.test.ts +21 -31
  24. package/src/cli/Metrics.ts +25 -36
  25. package/src/cli/Program.test.ts +1 -35
  26. package/src/cli/Program.ts +5 -64
  27. package/src/cli/ToolCommands.ts +0 -3
  28. package/src/edge/Edge.test.ts +20 -322
  29. package/src/edge/Edge.ts +10 -181
  30. package/src/edge/index.ts +0 -1
  31. package/src/flows/runtime.test.ts +0 -27
  32. package/src/flows/runtime.ts +1 -49
  33. package/src/install/Install.ts +15 -24
  34. package/src/replay/Replay.test.ts +0 -169
  35. package/src/replay/Replay.ts +4 -93
  36. package/src/replay/index.ts +0 -1
  37. package/src/testkit/Layers.ts +0 -1
  38. package/src/testkit/steps.ts +23 -0
  39. package/src/wire/BeatDocument.ts +2 -12
  40. package/src/wire/BeatStatus.ts +0 -3
  41. package/src/wire/WireSchema.test.ts +0 -18
  42. package/src/wire/index.ts +0 -2
  43. package/src/wire/types.ts +0 -7
  44. package/src/workflow/Workflow.ts +1 -9
  45. package/src/workflows/feature/criteria.test.ts +1 -14
  46. package/src/workflows/feature/criteria.ts +6 -38
  47. package/src/workflows/feature/feature.ts +34 -118
  48. package/src/workflows/feature/hands-on-skip.feature +2 -70
  49. package/src/workflows/feature/index.ts +0 -1
  50. package/src/workflows/feature/lens-review.feature +50 -0
  51. package/src/workflows/feature/one-step-land.feature +0 -4
  52. package/src/workflows/feature/prototype-rounds.feature +1 -1
  53. package/src/workflows/feature/prototype.feature +3 -3
  54. package/src/workflows/feature/prototype.ts +18 -29
  55. package/src/workflows/feature/refactor-absorbs.feature +3 -8
  56. package/src/workflows/feature/refactor-first.feature +0 -5
  57. package/src/workflows/feature/refactor-rebuild.feature +0 -6
  58. package/src/workflows/feature/refactor-review.feature +49 -11
  59. package/src/workflows/feature/refactor.ts +30 -64
  60. package/src/workflows/feature/review-gate.feature +1 -1
  61. package/src/workflows/feature/reviewer-walkthrough.feature +0 -31
  62. package/src/workflows/feature/seam-rejection.feature +0 -4
  63. package/src/workflows/feature/seam.test.ts +7 -26
  64. package/src/workflows/feature/seam.ts +52 -165
  65. package/src/workflows/index.ts +0 -1
  66. package/src/workflows/shared/lenses.ts +0 -13
  67. package/src/workflows/shared/options.test.ts +2 -11
  68. package/src/workflows/shared/options.ts +4 -21
  69. package/src/workflows/shared/review.test.ts +3 -3
  70. package/src/workflows/shared/review.ts +12 -2
  71. package/src/workflows/shared/steps.ts +0 -2
  72. package/src/workflows/shared/text.test.ts +10 -80
  73. package/src/workflows/shared/text.ts +35 -211
  74. package/src/workflows/shared/vars.ts +2 -8
  75. package/src/workflows/shared/workflows.steps.ts +0 -13
  76. package/claude/hooks/context.ts +0 -66
  77. package/src/replay/Slices.test.ts +0 -73
  78. package/src/replay/Slices.ts +0 -45
  79. package/src/wire/ContextLimit.fixture.ts +0 -30
  80. package/src/wire/ContextLimit.test.ts +0 -96
  81. package/src/wire/ContextLimit.ts +0 -169
  82. package/src/wire/Slices.ts +0 -25
  83. package/src/wire/golden/1/slices.json +0 -11
  84. package/src/workflows/feature/context-limit.feature +0 -37
  85. package/src/workflows/feature/continuation-cap.feature +0 -69
  86. package/src/workflows/feature/continuation-rebuild.feature +0 -92
  87. package/src/workflows/feature/continuation-slices.feature +0 -68
  88. package/src/workflows/feature/continuation.feature +0 -209
  89. package/src/workflows/feature/slice-findings.feature +0 -256
  90. package/src/workflows/feature/slicing-cap.feature +0 -23
  91. package/src/workflows/feature/slicing.feature +0 -297
  92. package/src/workflows/feature/slicing.test.ts +0 -49
  93. package/src/workflows/feature/slicing.ts +0 -105
  94. package/src/workflows/shared/continuation.ts +0 -92
package/dist/testkit.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { answered, applyScript, checkScript, configProblems, configure, installContext, isFactoryWorkflow, moveScript, numeric, protocolMismatch, removeScript, restoreScript, revertScript, showScript, t as flows_exports, writeScript } from "./flows.mjs";
2
- import { A as beatKindOf, B as ACCESS_REFUSAL, C as renderLandPlain, D as renderBeatJson, E as beatDocument, O as statusOf, S as renderLandJson, T as renderBeatPlain, U as renderFormat, _ as selectPath, a as contextLimitFault, g as resolveSession, h as globMatches, j as demandOf, l as parseContextWindow, m as slicesDocument, o as contextShapeFault, p as renderSlicesJson, v as judgeJson, x as noopText, y as landFields } from "./chunks/wire-BJuZWR3C.mjs";
2
+ import { E as ACCESS_REFUSAL, _ as demandOf, a as landFields, c as renderLandJson, d as renderBeatPlain, f as beatDocument, g as beatKindOf, i as judgeJson, k as renderFormat, l as renderLandPlain, m as statusOf, n as resolveSession, p as renderBeatJson, r as selectPath, s as noopText, t as globMatches } from "./chunks/wire-C12dahrH.mjs";
3
3
  import { A as parseThreadsWithFindings, M as headingSectionBodies, N as headingSections, P as formatFinding, a as unansweredQuestions, n as openThreads, r as steeringFormatFor, s as parseCodeThreads, t as BUILT_IN_MODE_NAMES } from "./chunks/steering-CLhu8C7q.mjs";
4
4
  import { $ as logWarning, $i as BaseProto, $n as map$5, $r as make$19, $t as provide$1, A as catchIf, Ai as add$1, An as scopeMake, Ar as flatMap$3, At as suspend$5, B as flatMap$4, Bi as getOrElse, Bn as get$3, Br as sync$1, Bt as unsafeMakeLatch, C as all$1, Ci as toArray, Cn as isInterrupted, Cr as exitFail, Ct as sandbox, D as async, Di as GenericTag, Dn as squash, Dr as exitZipRight, Dt as scopedWith$1, E as asVoid$1, Ei as unsafeHead, En as reduceWithContext, Er as exitVoid, Et as scoped$4, F as ensuring$2, Fi as fromIterable, Fn as join$3, Fr as map$3, Ft as tryMap, G as forkIn, Gi as match$4, Gn as TypeId$10, Gr as whenEffect, Gt as zipLeft, H as fnUntraced, Hi as isNone, Hn as modify$1, Hr as uninterruptible, Ht as when, I as exit, Ii as splitAt, In as sequential, Ir as onInterrupt, It as tryPromise, J as ignore, Ji as isLeft, Jn as all, Jr as DeferredTypeId$1, Jt as spaced$1, K as forkScoped, Ki as none, Kn as Class, Kr as withFiberRuntime, Kt as zipRight$3, L as fail$5, Li as filter$1, Ln as bind, Lr as scopeClose, Lt as try_, M as die$2, Mi as getOption, Mn as _await$2, Mr as interruptAsFiber, Mt as tap, N as dieMessage, Ni as omit, Nn as inheritAll$1, Nr as interruptFiber, Nt as tapErrorCause, O as catchAll$1, Oi as Reference, On as ensuring$3, Or as fiberId, Ot as serviceOption, P as either, Pi as empty$5, Pn as interruptAllAs$1, Pr as interruptible$1, Pt as timeout, Q as isEffect, Qi as pipeArguments, Qn as isFailure, Qr as ids, Qt as mergeAll$2, R as failCause$4, Ri as flatMap$5, Rn as bindTo, Rr as succeed$4, Rt as uninterruptible$1, S as addFinalizer$1, Si as takeRight, Sn as isDieType, Sr as exitAs, St as runtime, T as as, Ti as unsafeGet, Tn as map$6, Tr as exitSucceed, Tt as scopeWith, U as forEach$2, Ui as isSome, Un as set$1, Ur as uninterruptibleMask$1, Ut as withFiberRuntime$1, V as flatten$4, Vi as getOrUndefined, Vn as make$16, Vr as tap$1, Vt as unsafeMakeSemaphore$1, W as forkDaemon, Wi as map$8, Wn as update$1, Wr as void_$2, Wt as zip$1, X as interruptible, Xi as match$2, Xn as fail$4, Xr as combine, Xt as tagged, Y as interrupt$4, Yi as left, Yn as die, Yr as empty$6, Yt as TaggedError$1, Z as intoDeferred, Zi as right, Zn as failCause$3, Zr as combineAll, Zt as fromEffect$5, _ as decodeSync, _i as of, _n as fail$3, _r as deferredIsDone, _t as raceWith, a as Literal, aa as constFalse, ai as size$2, an as updateContext$1, ar as zipRight$2, at as matchCause, b as acquireRelease, bi as reverse, bn as interrupt$3, br as deferredUnsafeDone, bt as runPromise, c as String$, ca as constVoid, ci as append$1, cn as Scope, cr as asyncInterrupt, ct as negate, d as Union, da as pipe, di as empty$4, dn as close, dr as deferredAwait, dt as orElse, ea as NodeInspectSymbol, ei as none$1, en as provideMerge$1, er as mapBoth, et as makeSemaphore, f as between, fi as filter, fn as extend, fr as deferredComplete, ft as orElseSucceed, gi as map$7, gn as die$1, gr as deferredInterruptWith, gt as race, hi as isEmpty$2, hn as NoSuchElementException, hr as deferredInterrupt, ht as provideService, i as Defect, ia as hasProperty, ii as has, in as unsafeRunPromiseExit, ir as zip$2, it as match$5, j as catchTag, ji as get$5, jn as scopeTag, jr as interrupt$2, jt as sync$2, k as catchAllCause, ki as Tag, kn as forEachConcurrentDiscard, kr as fiberRefLocally, kt as succeed$6, l as Struct, la as dual, li as appendAll, ln as addFinalizer, lr as catchAll, lt as onError, m as optional$1, mi as isChunk, mn as make$18, mr as deferredFailCause, mt as provide$2, n as Boolean$, na as toJSON, ni as make$17, nn as succeed$3, nr as succeed$5, nt as mapBoth$1, o as Never, oa as constTrue, oi as decode, on as forkIn$1, or as as$1, ot as matchCauseEffect, p as decodeUnknown, pi as fromIterable$1, pn as fork, pr as deferredFail, pt as promise, q as gen, qi as some, qn as spanTag, qr as zipRight$1, qt as zipWith, r as Cause, ra as equals, ri as set$2, rn as unsafeFork, rr as void_$3, rt as mapError, s as Number$, sa as constUndefined, si as isFinite, sn as unsafeMakeSemaphore, sr as asVoid, st as matchEffect, t as Array$, ta as format, ti as get$4, tn as scoped$3, tr as match$3, tt as map$4, u as TaggedError, ua as identity, ui as drop, un as addFinalizerExit, ur as currentContext$1, ut as orDie, v as encodeSync, vi as prepend, vn as failureOrCause, vr as deferredMake, vt as repeat, w as annotateLogs, wi as unsafeFromArray, wn as isInterruptedOnly, wr as exitFailCause, wt as scope, x as acquireUseRelease$2, xi as take$2, xn as interruptors, xr as deferredUnsafeMake, xt as runPromiseExit$1, y as _void, yi as reduce, yn as flipCauseOption, yr as deferredSucceed, yt as retry, z as fiberIdWith, zi as fromNullable, zn as sleep, zr as suspend$4, zt as uninterruptibleMask } from "./chunks/Schema-C6fLlQzx.mjs";
5
5
  import { createRequire } from "node:module";
@@ -6224,10 +6224,7 @@ while :; do
6224
6224
  model="$(gtd next --json=model)"
6225
6225
  system="$(gtd next --json=system)"
6226
6226
  validate="$(gtd next --json=validate)"
6227
- context="$(gtd next --json=context)"
6228
6227
  log="$(gtd next --json=log)"
6229
- # Absolute, since the hook runs wherever the agent last cd'ed to.
6230
- case "$log" in /*) ;; *) log="$(git rev-parse --show-toplevel)/$log" ;; esac
6231
6228
  # \`model\` is a hint, not a model name — the bundled workflow ships
6232
6229
  # \`smart\` and \`base\`, which \`claude --model\` rejects. Resolve them
6233
6230
  # from your environment; unset, the hint is dropped and the agent runs
@@ -6236,18 +6233,6 @@ while :; do
6236
6233
  smart) model="\${GTD_LOOP_MODEL_SMART:-}" ;;
6237
6234
  base) model="\${GTD_LOOP_MODEL_BASE:-}" ;;
6238
6235
  esac
6239
- # The step's context limit, as a Claude Code hook after every tool
6240
- # call and before compacting. Past \`hard\` it stops the turn and
6241
- # appends why to the stop file, so we land nothing and stop too.
6242
- soft="$(printf '%s' "$context" | sed -n 's/.*"soft":\\([0-9.]*\\).*/\\1/p')"
6243
- hard="$(printf '%s' "$context" | sed -n 's/.*"hard":\\([0-9.]*\\).*/\\1/p')"
6244
- window="\${GTD_LOOP_CONTEXT_WINDOW:-}"
6245
- case "$model" in *'[1m]'* | *'[1M]'*) window="\${window:-1000000}" ;; esac
6246
- hook="gtd context-hook\${soft:+ --soft $soft}\${hard:+ --hard $hard}\${window:+ --window $window}"
6247
- hooks="[{\\"hooks\\":[{\\"type\\":\\"command\\",\\"command\\":\\"$hook\\"}]}]"
6248
- settings="{\\"hooks\\":{\\"PostToolUse\\":$hooks,\\"PreCompact\\":$hooks}}"
6249
- stop="$log.context-stop"
6250
- rm -f "$stop"
6251
6236
  # One turn, its prompt on stdin. The prompt embeds a full diff, so it
6252
6237
  # never goes on argv — argv is capped (~1 MB on macOS, and POSIX
6253
6238
  # guarantees only 4 KB, ARG_MAX), and a diff crosses that far sooner
@@ -6255,15 +6240,15 @@ while :; do
6255
6240
  # whether \`claude --resume\` re-applies the original session's is a
6256
6241
  # harness detail gtd cannot verify from outside. Its JSON envelope
6257
6242
  # goes to the log, and its \`total_cost_usd\` adds up into $cost. An
6258
- # \`is_error\` envelope (max turns, an error mid-turn) or one another
6259
- # hook stopped fails the turn even at exit 0: landing it would land
6260
- # the failure as work. Only a turn that never ran (no envelope: the
6243
+ # \`is_error\` envelope (max turns, an error mid-turn) or one a hook
6244
+ # stopped fails the turn even at exit 0: landing it would land the
6245
+ # failure as work. Only a turn that never ran (no envelope: the
6261
6246
  # session flag was refused) is worth retrying under the other flag.
6262
6247
  turn() {
6263
- out="$(GTD_LOOP_CONTEXT_STOP_FILE="$stop" claude -p "$@" \\
6248
+ out="$(claude -p "$@" \\
6264
6249
  \${model:+--model "$model"} \\
6265
6250
  \${system:+--system-prompt "$system"} \\
6266
- --settings "$settings" --output-format json \\
6251
+ --output-format json \\
6267
6252
  --dangerously-skip-permissions 2>>"$log")" && ok=0 || ok=$?
6268
6253
  printf '%s\\n' "$out" >>"$log"
6269
6254
  case "$out" in *'"type":"result"'*) ran=true ;; *) ran=false ;; esac
@@ -6273,10 +6258,6 @@ while :; do
6273
6258
  usd="$(printf '%s\\n' "$out" | sed -n 's/.*"total_cost_usd":\\([0-9.eE+-]*\\).*/\\1/p' | tail -n 1)"
6274
6259
  [ -z "$usd" ] || cost="$(awk -v a="\${cost:-0}" -v b="$usd" 'BEGIN { printf "%.6f", a + b }')"
6275
6260
  [ "\${cost:-}" != 0.000000 ] || cost=
6276
- if [ -s "$stop" ]; then
6277
- cat "$stop" >&2
6278
- exit 1
6279
- fi
6280
6261
  return "$ok"
6281
6262
  }
6282
6263
  # Plain \`gtd next\` (not --json=content) renders the diff once.
@@ -6745,6 +6726,16 @@ way — an existing \`gtd-loop\` survives untouched beside the new
6745
6726
  \`gtd-door review [base]\`). When either is still present beside the suite,
6746
6727
  name it to the user as superseded and ask before removing it — never remove
6747
6728
  one unasked.
6729
+
6730
+ An older \`gtd-build\` passed a \`gtd context-hook\` hook to every turn, and
6731
+ an older setup guide had it registered in Claude Code's own settings
6732
+ (\`PostToolUse\`/\`PreCompact\`). That command no longer exists: a hook
6733
+ still calling it fails on every tool call. Replace such a \`gtd-build\` as a
6734
+ differing file above, and look for a \`gtd context-hook\` entry in the user's
6735
+ Claude Code settings files (\`~/.claude/settings.json\`, the repository's
6736
+ \`.claude/settings.json\` and \`.claude/settings.local.json\`): name each file
6737
+ to the user, then remove that entry alone, leaving every other key
6738
+ byte-identical.
6748
6739
  `;
6749
6740
  const EDITOR_INTEGRATION = `
6750
6741
  ## Editor integration: offer it, don't bury it
@@ -6872,32 +6863,6 @@ const refNameFault = (name) => {
6872
6863
  }
6873
6864
  };
6874
6865
  //#endregion
6875
- //#region src/replay/Slices.ts
6876
- const IMPLICIT_SLICE = "main";
6877
- /** Same shape as mark()'s name rule; kept local so @pmelab/gtd/flows publishes no regex. */
6878
- const SLICE_NAME = /^[a-z][a-z0-9-]*$/;
6879
- /**
6880
- * Folds boundaries into ranges: each kept span's base is the previous kept
6881
- * span's `leftAt`, so a gap before a span belongs to it, and the last span
6882
- * runs to the episode tip. A closed span that landed nothing is dropped.
6883
- */
6884
- const sliceRanges = (spans, episode) => {
6885
- if (spans.some((s) => s.enteredAt === "" || s.leftAt === "")) throw new Error("sliceRanges: a pending turn's \"\" hash is not a commit");
6886
- const kept = spans.filter((s) => s.leftAt === void 0 || s.enteredAt !== s.leftAt);
6887
- if (kept.length === 0) return [{
6888
- name: IMPLICIT_SLICE,
6889
- base: episode.base,
6890
- tip: episode.tip,
6891
- open: true
6892
- }];
6893
- return kept.map((s, i) => ({
6894
- name: s.name,
6895
- base: i === 0 ? episode.base : kept[i - 1].leftAt ?? episode.base,
6896
- tip: i === kept.length - 1 ? episode.tip : s.leftAt ?? episode.tip,
6897
- open: i === kept.length - 1
6898
- }));
6899
- };
6900
- //#endregion
6901
6866
  //#region src/replay/Tree.ts
6902
6867
  const treeFromRecord = (files) => ({
6903
6868
  paths: () => Object.keys(files).sort(),
@@ -7204,15 +7169,6 @@ const budgeted = (evidence, budgetBytes) => {
7204
7169
  truncated
7205
7170
  };
7206
7171
  };
7207
- const scopeLabel = (chain) => {
7208
- const path = chain.flatMap((o) => o.name === void 0 ? [] : [o.name]).join(".");
7209
- if (chain.at(-1)?.name !== void 0) return `scope "${path}"`;
7210
- return path === "" ? "an unnamed scope" : `an unnamed scope inside "${path}"`;
7211
- };
7212
- const inheritedContext = (layers) => layers.reduce((merged, layer) => layer === void 0 ? merged : {
7213
- ...merged,
7214
- ...Object.fromEntries(Object.entries(layer).filter(([, value]) => value !== void 0))
7215
- }, void 0);
7216
7172
  const sameSkills = (a, b) => (a?.length ?? 0) === (b?.length ?? 0) && (a ?? []).every((skill, i) => skill === b?.[i]);
7217
7173
  const samePersona = (a, b) => a.model === b.model && a.system === b.system && sameSkills(a.skills, b.skills) && sameAccess(a.access, b.access);
7218
7174
  /**
@@ -7232,7 +7188,6 @@ const KNOWN_OPTIONS = {
7232
7188
  ...STEERING_OPTIONS,
7233
7189
  "model",
7234
7190
  "system",
7235
- "context",
7236
7191
  "allowEmpty",
7237
7192
  "keep"
7238
7193
  ]),
@@ -7277,7 +7232,6 @@ const replay = async (input) => {
7277
7232
  const keptTips = /* @__PURE__ */ new Map();
7278
7233
  let landed;
7279
7234
  const marks = [];
7280
- const slices = [];
7281
7235
  let outcome;
7282
7236
  let signal;
7283
7237
  const settled = new Promise((resolve) => {
@@ -7347,13 +7301,11 @@ const replay = async (input) => {
7347
7301
  if (s.model !== void 0) persona.model = s.model;
7348
7302
  if (s.system !== void 0) persona.system = s.system;
7349
7303
  }
7350
- const context = inheritedContext([...scopes.map((s) => s.context), request.options.context]);
7351
7304
  return {
7352
7305
  ...request,
7353
7306
  options: {
7354
7307
  ...persona,
7355
- ...request.options,
7356
- ...context === void 0 ? {} : { context }
7308
+ ...request.options
7357
7309
  }
7358
7310
  };
7359
7311
  }
@@ -7487,31 +7439,14 @@ const replay = async (input) => {
7487
7439
  rest: step,
7488
7440
  trace,
7489
7441
  landed,
7490
- marks,
7491
- slices: slices.map((s) => ({ ...s }))
7442
+ marks
7492
7443
  });
7493
7444
  return landPending(step, input.pending);
7494
7445
  };
7495
- const scopeContextFault = () => {
7496
- for (const [i, s] of scopes.entries()) {
7497
- const fault = s.context === void 0 ? void 0 : contextShapeFault(s.context);
7498
- if (fault !== void 0) return `the context of ${scopeLabel(scopes.slice(0, i + 1))}: ${fault}`;
7499
- }
7500
- };
7501
- const resolvedContextFault = (request) => {
7502
- const own = request.options.context;
7503
- const layered = scopeContextFault() ?? (own === void 0 ? void 0 : contextShapeFault(own));
7504
- if (layered !== void 0) return layered;
7505
- const resolved = resolve(request);
7506
- const context = resolved.kind === "agent" ? resolved.options.context : void 0;
7507
- return context === void 0 ? void 0 : contextLimitFault(context);
7508
- };
7509
7446
  const requestFault = (request) => {
7510
7447
  if (request.kind === "run" && typeof request.body !== "string") return `gtd: step "${scoped(request.name)}": a run() body is a shell script string — decide in flow code, then render the script (see checkScript and friends)`;
7511
7448
  if (request.kind !== "agent") return void 0;
7512
7449
  const name = scoped(request.name);
7513
- const contextFault = resolvedContextFault(request);
7514
- if (contextFault !== void 0) return `gtd: step "${name}": ${contextFault}`;
7515
7450
  if (request.options.keep === true) {
7516
7451
  if (request.options.allowEmpty === true) return `gtd: step "${name}": keep: true cannot be combined with allowEmpty — a turn that changes nothing has nothing to keep`;
7517
7452
  const refFault = name.includes("/") ? "it contains \"/\"" : refNameFault(name);
@@ -7543,8 +7478,7 @@ const replay = async (input) => {
7543
7478
  via,
7544
7479
  trace,
7545
7480
  landed,
7546
- marks,
7547
- slices: slices.map((s) => ({ ...s }))
7481
+ marks
7548
7482
  };
7549
7483
  const changesSince = (hash) => {
7550
7484
  const tree = treeAt(hash);
@@ -7569,31 +7503,6 @@ const replay = async (input) => {
7569
7503
  scopes.pop();
7570
7504
  scopeCallIds.pop();
7571
7505
  },
7572
- enterSlice: (name) => {
7573
- if (outcome !== void 0) return;
7574
- const fail = (message) => {
7575
- finish({
7576
- kind: "failed",
7577
- message
7578
- });
7579
- throw new Stop();
7580
- };
7581
- if (typeof name !== "string" || !SLICE_NAME.test(name)) fail(`gtd: slice "${name}": name must match ${SLICE_NAME}`);
7582
- if (name === "main") fail(`gtd: slice "${IMPLICIT_SLICE}" is reserved for the implicit slice`);
7583
- const open = slices.find((s) => s.leftAt === void 0);
7584
- if (open !== void 0) fail(`gtd: slice "${name}" is entered inside slice "${open.name}" — slices do not nest`);
7585
- if (slices.some((s) => s.name === name)) fail(`gtd: slice "${name}" is entered twice in this episode — a loop must sit inside its slice`);
7586
- slices.push({
7587
- name,
7588
- enteredAt: position.hash,
7589
- leftAt: void 0
7590
- });
7591
- },
7592
- leaveSlice: () => {
7593
- if (outcome !== void 0) return;
7594
- const open = slices.find((s) => s.leftAt === void 0);
7595
- if (open !== void 0) open.leftAt = position.hash;
7596
- },
7597
7506
  read: (path) => position.tree.read(path),
7598
7507
  glob: (pattern) => position.tree.paths().filter((path) => globMatches(path, pattern)),
7599
7508
  changes: () => changesBetween(previousPosition.tree, position.tree),
@@ -9121,10 +9030,6 @@ const commonOf = (step) => {
9121
9030
  ...optional("mode", options.mode)
9122
9031
  };
9123
9032
  };
9124
- const stepContext = (context) => context === void 0 ? void 0 : {
9125
- ...optional("soft", context.soft),
9126
- ...optional("hard", context.hard)
9127
- };
9128
9033
  const promptDef = (common, request, skills, access) => ({
9129
9034
  ...common,
9130
9035
  kind: "prompt",
@@ -9133,7 +9038,6 @@ const promptDef = (common, request, skills, access) => ({
9133
9038
  ...optional("system", request.options.system),
9134
9039
  ...optional("allowEmpty", request.options.allowEmpty),
9135
9040
  ...optional("keep", request.options.keep),
9136
- ...optional("context", stepContext(request.options.context)),
9137
9041
  ...optional("skills", skills),
9138
9042
  ...optional("access", access)
9139
9043
  });
@@ -9197,8 +9101,7 @@ const hintsOf = (def) => ({
9197
9101
  ...optional("system", def.system),
9198
9102
  ...optional("mode", def.mode),
9199
9103
  ...optional("skills", def.skills),
9200
- ...optional("access", def.access),
9201
- ...optional("context", def.context)
9104
+ ...optional("access", def.access)
9202
9105
  });
9203
9106
  const normalizeStatus = (raw) => raw === "A" ? "A" : raw === "D" ? "D" : "M";
9204
9107
  /** Per-model token totals, highest-cost first (ties broken by model name). */
@@ -9275,10 +9178,6 @@ const pendingChanges = (git, withIndex) => gen(function* () {
9275
9178
  const seen = new Set(worktree.map((e) => e.path));
9276
9179
  return [...worktree, ...(yield* git.stagedPaths()).filter((e) => !seen.has(e.path))];
9277
9180
  });
9278
- const sliceRangesOf = (slices, run) => sliceRanges(slices, {
9279
- base: run.startParentHash,
9280
- tip: run.trace.at(-1)?.hash ?? run.startParentHash
9281
- });
9282
9181
  /** Resolve the rest at `ref`, or at HEAD when `ref` is `undefined`. */
9283
9182
  const restAt = (ref, startOverrides = {}) => gen(function* () {
9284
9183
  const git = yield* GitService;
@@ -9322,64 +9221,6 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
9322
9221
  };
9323
9222
  });
9324
9223
  const currentRest = restAt(void 0);
9325
- /** Every slice refusal reads `<command>: refused — <reason>`; replay's own `gtd: ` lead is dropped. */
9326
- const slicesDivergence = (command, run, outcome) => {
9327
- const reason = outcome.kind === "rest" && run.closingHash !== void 0 ? `${run.closingHash.slice(0, 7)} closes a process that does not end under today's config` : replayError(outcome)?.message.replace(/^gtd: /, "") ?? "replay failed";
9328
- return /* @__PURE__ */ new Error(`${command}: refused — ${reason}`);
9329
- };
9330
- /**
9331
- * The replay `gtd slices` and `gtd summary --slice` share: the slice ranges of
9332
- * `run`, plus whether HEAD closes it. Reads like `historyTrail`, never
9333
- * `restAt`: it narrates and writes nothing.
9334
- */
9335
- const replayedSlices = (config, run, how) => gen(function* () {
9336
- const workspace = yield* Workspace;
9337
- const host = yield* Host;
9338
- if (run.trace.length === 0) return yield* fail$5(/* @__PURE__ */ new Error(`${how.command}: refused — HEAD neither closes nor sits inside a process`));
9339
- const closing = run.closingHash;
9340
- let outcome;
9341
- let versionWarning;
9342
- if (closing !== void 0) {
9343
- const replayed = yield* replayFinishedProcess(config, run, closing, {
9344
- command: how.command,
9345
- configReason: how.configReason,
9346
- notEnded: (o) => slicesDivergence(how.command, run, o)
9347
- });
9348
- outcome = replayed.outcome;
9349
- versionWarning = replayed.versionWarning;
9350
- } else {
9351
- const { setup } = yield* setupFor(config, run, workspace, host);
9352
- outcome = yield* promise(() => replayFor(setup));
9353
- if (outcome.kind !== "rest") return yield* fail$5(slicesDivergence(how.command, run, outcome));
9354
- }
9355
- return {
9356
- ranges: sliceRangesOf(outcome.slices, run),
9357
- closed: closing !== void 0,
9358
- ...versionWarning !== void 0 ? { versionWarning } : {}
9359
- };
9360
- });
9361
- /**
9362
- * The slices of the process HEAD closes or sits inside, current episode only,
9363
- * stack order. Returns a version warning for a closed run.
9364
- */
9365
- const processSlices = gen(function* () {
9366
- const git = yield* GitService;
9367
- const config = yield* (yield* ConfigService).load;
9368
- const run = yield* runAtHead(git, config.workflow);
9369
- const { ranges, closed, versionWarning } = yield* replayedSlices(config, run, {
9370
- command: "gtd slices",
9371
- configReason: "HEAD's process replays under today's config; revert or stash the edit to list its slices"
9372
- });
9373
- return {
9374
- slices: ranges.map(({ name, base, tip, open }) => ({
9375
- name,
9376
- base,
9377
- tip,
9378
- closed: closed || !open
9379
- })),
9380
- ...versionWarning !== void 0 ? { versionWarning } : {}
9381
- };
9382
- });
9383
9224
  /**
9384
9225
  * Whether `gtd --workflow <name>` can start a process here: the workflow must
9385
9226
  * reach a step from the tree the opening commit would capture. That step is
@@ -9586,85 +9427,31 @@ const snapshotFromRest = (rest, verdicts) => gen(function* () {
9586
9427
  landing
9587
9428
  };
9588
9429
  });
9589
- const narrowToSlice = (run, slice) => {
9590
- if (slice === void 0) return {
9591
- trace: run.trace,
9592
- costEntries: run.costEntries
9593
- };
9594
- const from = slice.base === run.startParentHash ? -1 : run.trace.findIndex((e) => e.hash === slice.base);
9595
- const to = run.trace.findIndex((e) => e.hash === slice.tip);
9596
- const trace = run.trace.slice(from + 1, to + 1);
9597
- const hashes = new Set(trace.map((e) => e.hash));
9598
- return {
9599
- trace,
9600
- costEntries: run.costEntries.filter((c) => hashes.has(c.hash))
9601
- };
9602
- };
9603
9430
  const humanCommitsOf = (trace, entryCommit) => trace.filter((entry) => entry.actor === "human" && entry.hash !== entryCommit).map((entry) => ({
9604
9431
  hash: entry.hash,
9605
9432
  state: entry.state
9606
9433
  }));
9607
- const summaryInput = (run, slice, settings) => {
9434
+ const summaryInput = (run, settings) => {
9608
9435
  const entryCommit = run.trace[0].hash;
9609
- const { trace, costEntries } = narrowToSlice(run, slice);
9610
- const { total, byModel } = costTotals(costEntries);
9436
+ const { total, byModel } = costTotals(run.costEntries);
9611
9437
  return {
9612
9438
  entryCommit,
9613
- processBase: slice?.base ?? run.startParentHash,
9614
- processTip: slice?.tip ?? run.trace[run.trace.length - 1].hash,
9615
- humanCommits: humanCommitsOf(trace, entryCommit),
9439
+ processBase: run.startParentHash,
9440
+ processTip: run.trace[run.trace.length - 1].hash,
9441
+ humanCommits: humanCommitsOf(run.trace, entryCommit),
9616
9442
  processCost: total,
9617
9443
  processCostByModel: byModel,
9618
9444
  marks: run.marks,
9619
- ...settings,
9620
- ...slice !== void 0 ? { slice } : {}
9445
+ ...settings
9621
9446
  };
9622
9447
  };
9623
- const locateSlice = (config, run, sliceName) => gen(function* () {
9624
- if (sliceName === void 0) return {};
9625
- const { ranges, versionWarning } = yield* replayedSlices(config, run, {
9626
- command: "gtd summary",
9627
- configReason: "HEAD's process replays under today's config; revert or stash the edit to summarize its slices"
9628
- });
9629
- const warn = versionWarning !== void 0 ? { versionWarning } : {};
9630
- const at = ranges.findIndex((r) => r.name === sliceName);
9631
- if (at < 0) return {
9632
- unknownSlice: `gtd summary: refused — no slice named "${sliceName}"; the slices, in stack order: ${ranges.map((r) => r.name).join(", ")}`,
9633
- ...warn
9634
- };
9635
- const { base, tip } = ranges[at];
9636
- return {
9637
- slice: {
9638
- name: sliceName,
9639
- base,
9640
- tip,
9641
- index: at + 1,
9642
- count: ranges.length
9643
- },
9644
- ...warn
9645
- };
9646
- });
9647
- /**
9648
- * A plain call never replays; under a slice, `entryCommit` and `marks` stay
9649
- * whole-process.
9650
- */
9651
- const summaryFor = (run, sliceName) => gen(function* () {
9448
+ const summaryFor = (run) => gen(function* () {
9652
9449
  const config = yield* (yield* ConfigService).load;
9653
9450
  const host = yield* Host;
9654
9451
  const resolved = yield* workflowOf(config, run);
9655
9452
  const summary = resolved.def.summary;
9656
- if (summary === void 0) return { prompt: void 0 };
9657
- if (sliceName === void 0 && run.trace.length === 0) return { prompt: void 0 };
9658
- const located = yield* locateSlice(config, run, sliceName);
9659
- if ("unknownSlice" in located) return {
9660
- prompt: void 0,
9661
- ...located
9662
- };
9663
- const { slice, versionWarning } = located;
9664
- return {
9665
- prompt: summary(summaryInput(run, slice, settingsFor(resolved, run.pinnedVars, host.env, {}, {}))),
9666
- ...versionWarning !== void 0 ? { versionWarning } : {}
9667
- };
9453
+ if (summary === void 0 || run.trace.length === 0) return { prompt: void 0 };
9454
+ return { prompt: summary(summaryInput(run, settingsFor(resolved, run.pinnedVars, host.env, {}, {}))) };
9668
9455
  });
9669
9456
  /** Repository-relative paths of the config files today's config and the process's own record name; one outside the repository cannot be read at a ref, so it is left out. */
9670
9457
  const inRepoConfigPaths = (root, files, recorded) => {
@@ -10352,7 +10139,6 @@ const runHistoryFetchCommand = (remote, out) => sync$2(() => {
10352
10139
  //#region src/cli/Metrics.ts
10353
10140
  const QUALITY$1 = ".gtd/QUALITY.md";
10354
10141
  const LENS = /^((?:.*\.)?quality\.[^.]+)\.reviewing$/;
10355
- const CONTINUE = /(^|\.)continue\./;
10356
10142
  const inSeam = (step) => step.startsWith("seam.");
10357
10143
  const phaseOf = (step) => {
10358
10144
  const scope = step.split(".")[0];
@@ -10371,13 +10157,12 @@ const add = (into, key, n) => {
10371
10157
  };
10372
10158
  const roundAll = (record) => Object.fromEntries(Object.entries(record).map(([k, v]) => [k, round(v)]));
10373
10159
  const emptyTally = () => ({
10374
- slices: {
10160
+ builds: {
10375
10161
  decided: 0,
10376
10162
  landed: 0,
10377
10163
  rebuilt: 0,
10378
10164
  refactored: 0
10379
10165
  },
10380
- continuations: record(),
10381
10166
  byModel: record(),
10382
10167
  byPhase: {
10383
10168
  naive: 0,
@@ -10394,9 +10179,8 @@ const emptyTally = () => ({
10394
10179
  });
10395
10180
  const sum = (record) => Object.values(record).reduce((a, b) => a + b, 0);
10396
10181
  const finish = (t) => ({
10397
- slices: t.slices,
10398
- refactorFirstRate: t.slices.decided === 0 ? null : round(t.slices.rebuilt / t.slices.decided, 3),
10399
- continuations: t.continuations,
10182
+ builds: t.builds,
10183
+ refactorFirstRate: t.builds.decided === 0 ? null : round(t.builds.rebuilt / t.builds.decided, 3),
10400
10184
  cost: {
10401
10185
  total: round(sum(t.byModel)),
10402
10186
  byModel: roundAll(t.byModel),
@@ -10424,33 +10208,33 @@ const tallyCost = (t, open, costs, phase) => {
10424
10208
  if (phase === "implement" && open.rebuilding) t.doubleWork.rebuild += cost;
10425
10209
  }
10426
10210
  };
10427
- /** A slice is decided where its turns leave the seam: for `implement.applying` the naive pass landed — unless it fails to apply and is built afresh — anywhere else it is rebuilt. */
10211
+ /** A build is decided where its turns leave the seam: for `implement.applying` the naive pass landed — unless it fails to apply and is built afresh — anywhere else it is rebuilt. */
10428
10212
  const tallyDecision = (t, open, at, to) => {
10429
10213
  if (at === "implement.closing") open.rebuilding = false;
10430
10214
  if (open.applying !== void 0 && at === "implement.applying") {
10431
10215
  const naive = open.applying;
10432
10216
  open.applying = void 0;
10433
10217
  if (to !== "implement.building") return;
10434
- t.slices.landed--;
10218
+ t.builds.landed--;
10435
10219
  rebuild(t, open, naive);
10436
10220
  return;
10437
10221
  }
10438
10222
  if (!inSeam(at) || inSeam(to)) return;
10439
- t.slices.decided++;
10223
+ t.builds.decided++;
10440
10224
  const naive = open.naive;
10441
10225
  open.naive = 0;
10442
10226
  if (to === "implement.applying") {
10443
10227
  open.applying = naive;
10444
10228
  open.rebuilding = false;
10445
- t.slices.landed++;
10229
+ t.builds.landed++;
10446
10230
  return;
10447
10231
  }
10448
10232
  rebuild(t, open, naive);
10449
- if (at === "seam.note") t.slices.refactored++;
10233
+ if (at === "seam.note") t.builds.refactored++;
10450
10234
  };
10451
10235
  const rebuild = (t, open, naive) => {
10452
10236
  open.rebuilding = true;
10453
- t.slices.rebuilt++;
10237
+ t.builds.rebuilt++;
10454
10238
  t.doubleWork.naive += naive;
10455
10239
  };
10456
10240
  /** Oldest first; a commit gtd did not write is skipped. */
@@ -10467,7 +10251,6 @@ const processMetrics = (commits) => {
10467
10251
  if (subject === void 0) continue;
10468
10252
  const at = subject.from ?? subject.to;
10469
10253
  tallyCost(t, open, message.cost, phaseOf(at));
10470
- if (subject.actor === "agent" && CONTINUE.test(at)) add(t.continuations, at.replace(CONTINUE, "$1"), 1);
10471
10254
  const lens = LENS.exec(at)?.[1];
10472
10255
  if (lens !== void 0 && commit.findings > 0) add(t.byLens, lens, commit.findings);
10473
10256
  if (message.marks.some((m) => m.name === "flaky")) add(t.flaky, at, 1);
@@ -10478,8 +10261,7 @@ const processMetrics = (commits) => {
10478
10261
  const totalMetrics = (all) => {
10479
10262
  const t = emptyTally();
10480
10263
  for (const m of all) {
10481
- for (const key of Object.keys(t.slices)) t.slices[key] += m.slices[key];
10482
- for (const [k, v] of Object.entries(m.continuations)) add(t.continuations, k, v);
10264
+ for (const key of Object.keys(t.builds)) t.builds[key] += m.builds[key];
10483
10265
  for (const [k, v] of Object.entries(m.cost.byModel)) add(t.byModel, k, v);
10484
10266
  for (const key of Object.keys(t.byPhase)) t.byPhase[key] += m.cost.byPhase[key];
10485
10267
  t.doubleWork.naive += m.cost.doubleWork.naive;
@@ -10516,12 +10298,10 @@ const readProcessMetrics = (entry) => gen(function* () {
10516
10298
  const printable = (s) => s.replace(/[\x00-\x1f\x7f-\x9f\u2028\u2029]/g, "?");
10517
10299
  const listed = (record) => Object.entries(record).map(([k, v]) => `${printable(k)} ${v}`).join(", ");
10518
10300
  const line = (label, m) => {
10519
- const { slices, cost } = m;
10301
+ const { builds, cost } = m;
10520
10302
  const percent = m.refactorFirstRate === null ? "-" : `${Math.round(m.refactorFirstRate * 100)}%`;
10521
- const continued = sum(m.continuations);
10522
10303
  return [
10523
- `${label}: ${slices.decided} slices, ${slices.rebuilt} rebuilt (refactor-first ${percent}), ${slices.refactored} refactored`,
10524
- `${continued} continuations${continued > 0 ? ` (${listed(m.continuations)})` : ""}`,
10304
+ `${label}: ${builds.decided} builds, ${builds.rebuilt} rebuilt (refactor-first ${percent}), ${builds.refactored} refactored`,
10525
10305
  `cost ${cost.total} (${listed(cost.byPhase)}; by model ${listed(cost.byModel) || "-"}), double work ${cost.doubleWork.total}`,
10526
10306
  `${m.findings.total} findings${m.findings.total > 0 ? ` (${listed(m.findings.byLens)})` : ""}`,
10527
10307
  `${m.flaky.total} flaky${m.flaky.total > 0 ? ` (${listed(m.flaky.byStep)})` : ""}`
@@ -11005,16 +10785,10 @@ const planLanding = (opts = {}, current) => gen(function* () {
11005
10785
  * closes or sits inside its own closing message. Writes nothing — no git, no
11006
10786
  * state transition, no file. Refuses (throws, mapped to the runtime-error
11007
10787
  * exit code) when the workflow declares no `summary` prompt, or the resolved
11008
- * run has no commits to name. `--slice` also refuses an unknown slice name
11009
- * (listing the valid slices), a config edited since the process closed, and a
11010
- * replay that diverged. The edge never narrates: the CLI prints the
11011
- * version warning, before the unknown-slice refusal; a replay refusal carries
11012
- * it in its message.
11013
- */
11014
- const runSummaryCommand = (out, sliceName) => gen(function* () {
11015
- const { prompt, versionWarning, unknownSlice } = yield* summaryFor(yield* summaryRun, sliceName);
11016
- if (versionWarning !== void 0) yield* (yield* Narrator).warn(versionWarning);
11017
- if (unknownSlice !== void 0) return yield* fail$5(new Error(unknownSlice));
10788
+ * run has no commits to name.
10789
+ */
10790
+ const runSummaryCommand = (out) => gen(function* () {
10791
+ const { prompt } = yield* summaryFor(yield* summaryRun);
11018
10792
  if (prompt === void 0) return yield* fail$5(/* @__PURE__ */ new Error("gtd summary: refused — either this workflow declares no \"summary:\" template, or there is no finished/in-flight process to summarize at HEAD"));
11019
10793
  out.write(prompt.endsWith("\n") ? prompt : prompt + "\n");
11020
10794
  });
@@ -11059,17 +10833,6 @@ const readStdin = () => tryPromise({
11059
10833
  },
11060
10834
  catch: (e) => e instanceof Error ? e : new Error(String(e))
11061
10835
  });
11062
- /** Stdout is written once: a hook reads one JSON document, or none. */
11063
- const runContextHookCommand = (command, out) => gen(function* () {
11064
- const tools = yield* ToolCommands;
11065
- const input = yield* readStdin();
11066
- out.write(yield* tools.contextHook({
11067
- input,
11068
- ...command.soft !== void 0 ? { soft: command.soft } : {},
11069
- ...command.hard !== void 0 ? { hard: command.hard } : {},
11070
- ...command.window !== void 0 ? { window: command.window } : {}
11071
- }));
11072
- });
11073
10836
  /** Stdout is written once, after the answerer resolves — a rejection leaves it empty. */
11074
10837
  const runJudgeRunCommand = (command, out) => gen(function* () {
11075
10838
  const host = yield* Host;
@@ -11208,17 +10971,6 @@ const runDoorsCommand = (json, out) => gen(function* () {
11208
10971
  if (json.kind === "document") out.write(`${JSON.stringify(document)}\n`);
11209
10972
  else yield* writeSelection(out, document, json.path);
11210
10973
  });
11211
- const runSlicesCommand = (json, out) => gen(function* () {
11212
- const { slices, versionWarning } = yield* processSlices;
11213
- if (versionWarning !== void 0) yield* (yield* Narrator).warn(versionWarning);
11214
- if (json.kind === "off") {
11215
- out.write(slices.map((s) => `${s.name} ${s.base}..${s.tip}\n`).join(""));
11216
- return;
11217
- }
11218
- const document = slicesDocument(slices);
11219
- if (json.kind === "document") out.write(renderSlicesJson(document));
11220
- else yield* writeSelection(out, document, json.path);
11221
- });
11222
10974
  /**
11223
10975
  * Ends the process underway without completing it. Nothing is discarded: a
11224
10976
  * `git reset --mixed` to the process's start commit leaves every turn
@@ -11525,7 +11277,6 @@ const needsOf = (kind) => {
11525
11277
  switch (kind) {
11526
11278
  case "lsp":
11527
11279
  case "install":
11528
- case "contextHook":
11529
11280
  case "judgeRun":
11530
11281
  case "strays": return "none";
11531
11282
  case "check":
@@ -11554,7 +11305,6 @@ const dispatchVoidCommand = (command, json, out) => {
11554
11305
  case "start": return runStartCommand(command.actor, command.workflow, command.vars, out, command.label);
11555
11306
  case "door": return runDoorCommand(command.name, command.args, out);
11556
11307
  case "doors": return runDoorsCommand(json, out);
11557
- case "slices": return runSlicesCommand(json, out);
11558
11308
  case "abandon": return runAbandonCommand(out);
11559
11309
  case "restore": return runRestoreCommand(out);
11560
11310
  case "next": return runNextCommand(json, out);
@@ -11564,8 +11314,7 @@ const dispatchVoidCommand = (command, json, out) => {
11564
11314
  return runCheckCommand(command.mode, command.file, command.openQuestions ?? false, command.openThreads ?? false);
11565
11315
  case "uncheck": return runUncheckCommand(command.file);
11566
11316
  case "install": return runInstallCommand(out);
11567
- case "contextHook": return runContextHookCommand(command, out);
11568
- case "summary": return runSummaryCommand(out, command.slice);
11317
+ case "summary": return runSummaryCommand(out);
11569
11318
  case "base": return runBaseCommand(out);
11570
11319
  case "judge": return runJudgeCommand(json, out);
11571
11320
  case "judgeAnswer": return runJudgeAnswerCommand(json, out);
@@ -11608,17 +11357,12 @@ const nonNegativeNumber = (raw, flag) => {
11608
11357
  if (raw.trim() === "" || !Number.isFinite(n) || n < 0) return left(`gtd: ${flag} must be a non-negative number — got "${raw}"`);
11609
11358
  return right(n);
11610
11359
  };
11611
- const share = (raw, flag) => {
11612
- const n = Number(raw);
11613
- return raw.trim() !== "" && n > 0 && n <= 1 ? right(n) : left(`gtd: ${flag} must be a share of the context window in (0, 1] — got "${raw}"`);
11614
- };
11615
11360
  const JSON_KINDS = /* @__PURE__ */ new Set([
11616
11361
  "next",
11617
11362
  "land",
11618
11363
  "judge",
11619
11364
  "judgeAnswer",
11620
11365
  "doors",
11621
- "slices",
11622
11366
  "historyList",
11623
11367
  "historyShow",
11624
11368
  "historyMetrics",
@@ -11632,10 +11376,10 @@ const FLAGS = [
11632
11376
  repeatable: false,
11633
11377
  scope: (kind) => JSON_KINDS.has(kind),
11634
11378
  decode: ([raw]) => right(raw ?? ""),
11635
- scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd slices`/`gtd history list`/`gtd history show`/`gtd history metrics`/`gtd history judges`/`gtd strays` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
11379
+ scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd history list`/`gtd history show`/`gtd history metrics`/`gtd history judges`/`gtd strays` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
11636
11380
  valueHint: "<path>",
11637
11381
  help: [
11638
- "(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd slices/gtd history",
11382
+ "(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd history",
11639
11383
  "list/gtd history show/gtd history metrics/gtd history judges/gtd strays only) output structured JSON. Bare --json prints the whole document;",
11640
11384
  "--json=<path> (a dotted key path into that document, e.g.",
11641
11385
  "kind, content, session.id) prints just that value: a",
@@ -11752,54 +11496,6 @@ const FLAGS = [
11752
11496
  "GTD_JUDGE_MODEL, then judge.json, then sonnet"
11753
11497
  ]
11754
11498
  },
11755
- {
11756
- name: "--soft",
11757
- arity: 1,
11758
- repeatable: false,
11759
- scope: (kind) => kind === "contextHook",
11760
- decode: ([raw]) => share(raw ?? "", "--soft"),
11761
- scopeError: "gtd: --soft is only valid for `gtd context-hook`",
11762
- valueHint: "<share>",
11763
- help: ["(gtd context-hook only) the soft limit; default 0.6"]
11764
- },
11765
- {
11766
- name: "--hard",
11767
- arity: 1,
11768
- repeatable: false,
11769
- scope: (kind) => kind === "contextHook",
11770
- decode: ([raw]) => share(raw ?? "", "--hard"),
11771
- scopeError: "gtd: --hard is only valid for `gtd context-hook`",
11772
- valueHint: "<share>",
11773
- help: ["(gtd context-hook only) the hard limit; default 0.7"]
11774
- },
11775
- {
11776
- name: "--window",
11777
- arity: 1,
11778
- repeatable: false,
11779
- scope: (kind) => kind === "contextHook",
11780
- decode: ([raw]) => {
11781
- const window = parseContextWindow(raw);
11782
- return window !== void 0 ? right(window) : left(`gtd: --window must be a positive integer — got "${raw ?? ""}"`);
11783
- },
11784
- scopeError: "gtd: --window is only valid for `gtd context-hook`",
11785
- valueHint: "<tokens>",
11786
- help: ["(gtd context-hook only) the model's context window in", "tokens; default 200000"]
11787
- },
11788
- {
11789
- name: "--slice",
11790
- arity: 1,
11791
- repeatable: false,
11792
- scope: (kind) => kind === "summary",
11793
- decode: ([raw]) => right(raw),
11794
- scopeError: "gtd: --slice is only valid for `gtd summary`",
11795
- valueHint: "<name>",
11796
- help: [
11797
- "(gtd summary only) the closing-message prompt for one",
11798
- "slice, named as `gtd slices` lists it: its own range, human",
11799
- "commits and cost. An unknown name is refused (exit 1) with",
11800
- "every valid slice listed"
11801
- ]
11802
- },
11803
11499
  {
11804
11500
  name: "--workflow",
11805
11501
  arity: 1,
@@ -12090,22 +11786,6 @@ const COMMAND_ROWS = [
12090
11786
  "else it writes nothing."
12091
11787
  ]
12092
11788
  },
12093
- {
12094
- token: "context-hook",
12095
- kind: "contextHook",
12096
- arity: "none",
12097
- details: [
12098
- "A Claude Code PostToolUse and PreCompact hook that stops an",
12099
- "agent turn at a share of its context window. Reads the",
12100
- "hook's input on stdin and the context the transcript's",
12101
- "latest assistant message used. Past --soft it blocks with a",
12102
- "request to end the turn with a handoff note; past --hard,",
12103
- "or at PreCompact, it stops the turn with a stopReason",
12104
- "starting \"gtd: context limit\". Prints nothing otherwise,",
12105
- "and for a subagent's tool call. Always exits 0 on valid",
12106
- "input; see docs/driver.md"
12107
- ]
12108
- },
12109
11789
  {
12110
11790
  token: "summary",
12111
11791
  kind: "summary",
@@ -12119,15 +11799,10 @@ const COMMAND_ROWS = [
12119
11799
  "no state transition, no file, no session identity — the",
12120
11800
  "driver pipes the output to a cold agent and does what it",
12121
11801
  "wants with the result (a squash, an amend, a PR body).",
12122
- "--slice <name> prints it for one slice instead: its own",
12123
- "range, human commits and cost (see `gtd slices`); it also",
12124
- "refuses an unknown slice name, and a HEAD-closing process",
12125
- "whose config files differ from the working tree or whose",
12126
- "replay diverged; an open last slice is provisional. Refuses",
12127
- "(exit 1) when the workflow declares no summary: template,",
12128
- "or when the resolved run has no commits to name — plain",
12129
- "gtd summary is runnable any time before the next thing",
12130
- "lands on the branch"
11802
+ "Refuses (exit 1) when the workflow declares no summary:",
11803
+ "template, or when the resolved run has no commits to name —",
11804
+ "it is runnable any time before the next thing lands on the",
11805
+ "branch"
12131
11806
  ]
12132
11807
  },
12133
11808
  {
@@ -12173,25 +11848,6 @@ const COMMAND_ROWS = [
12173
11848
  "value off it. Writes nothing"
12174
11849
  ]
12175
11850
  },
12176
- {
12177
- token: "slices",
12178
- kind: "slices",
12179
- arity: "none",
12180
- details: [
12181
- "List the slices of the process HEAD closes or sits inside,",
12182
- "first slice first: one `name base..tip` line each (full",
12183
- "hashes; plain output carries no promise). --json prints",
12184
- "{ schema: 1, slices: [{ name, base, tip, closed }] };",
12185
- "--json=<path> reads one value off it. Writes nothing: no",
12186
- "git, no state transition, no session identity. When HEAD",
12187
- "closes a process, every slice is listed as closed; a gtd",
12188
- "version mismatch only warns on stderr. Refuses (exit 1)",
12189
- "when HEAD neither closes nor sits inside a process, or when",
12190
- "today's config disagrees with history about the process:",
12191
- "HEAD closes it and its config files differ from the working",
12192
- "tree, or the replay diverged"
12193
- ]
12194
- },
12195
11851
  {
12196
11852
  token: "judge",
12197
11853
  kind: "judge",
@@ -12293,11 +11949,11 @@ const COMMAND_ROWS = [
12293
11949
  many: true
12294
11950
  },
12295
11951
  details: [
12296
- "Report how the kept processes went: slices whose naive pass",
12297
- "landed or was rebuilt (the refactor-first rate), continuations",
12298
- "per phase, Gtd-Cost by model and phase with the double work of",
12299
- "a rebuild, quality-lap findings per lens, and red checks that",
12300
- "passed on their re-check — per process and in total. Each <ref>",
11952
+ "Report how the kept processes went: builds whose naive pass",
11953
+ "landed or was rebuilt (the refactor-first rate), Gtd-Cost by",
11954
+ "model and phase with the double work of a rebuild, quality-lap",
11955
+ "findings per lens, and red checks that passed on their",
11956
+ "re-check — per process and in total. Each <ref>",
12301
11957
  "is read as by history show, but a branch name means every",
12302
11958
  "process kept from it; none means every ref.",
12303
11959
  "Reads commit subjects and trailers, never replays, so a",
@@ -12626,23 +12282,9 @@ const parseArgv = (argv) => {
12626
12282
  verbose
12627
12283
  };
12628
12284
  }
12629
- if (kind === "contextHook") return {
12630
- kind: "command",
12631
- command: {
12632
- kind: "contextHook",
12633
- ...bag["--soft"] !== void 0 ? { soft: bag["--soft"] } : {},
12634
- ...bag["--hard"] !== void 0 ? { hard: bag["--hard"] } : {},
12635
- ...bag["--window"] !== void 0 ? { window: bag["--window"] } : {}
12636
- },
12637
- json,
12638
- verbose
12639
- };
12640
12285
  if (kind === "summary") return {
12641
12286
  kind: "command",
12642
- command: {
12643
- kind: "summary",
12644
- ...bag["--slice"] !== void 0 ? { slice: bag["--slice"] } : {}
12645
- },
12287
+ command: { kind: "summary" },
12646
12288
  json,
12647
12289
  verbose
12648
12290
  };
@@ -13441,7 +13083,6 @@ const missingTool = (name) => () => fail$5(/* @__PURE__ */ new Error(`gtd ${name
13441
13083
  const missingToolCommands = {
13442
13084
  ui: missingTool("ui"),
13443
13085
  lsp: missingTool("lsp"),
13444
- contextHook: missingTool("context-hook"),
13445
13086
  judge: () => Promise.reject(/* @__PURE__ */ new Error("gtd judge run: ToolCommands has no test double wired into testLayers()"))
13446
13087
  };
13447
13088
  /** The fine-grained `GitService` layer alone — for unit tests that need only git. */