@pmelab/gtd 52.0.0 → 53.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 (108) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +96 -131
  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-D0MKVFFa.mjs → reserved-CQ7amcKG.mjs} +87 -133
  7. package/dist/chunks/{review-ZzUQH1KZ.mjs → review-CNgN_V8T.mjs} +2 -2
  8. package/dist/chunks/{tail-I1-vVq0P.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 +159 -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 +400 -675
  20. package/package.json +5 -4
  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 +13 -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/acceptance-criteria.feature +159 -3
  46. package/src/workflows/feature/criteria.test.ts +23 -15
  47. package/src/workflows/feature/criteria.ts +72 -39
  48. package/src/workflows/feature/fast-path.feature +4 -0
  49. package/src/workflows/feature/feature.test.ts +6 -5
  50. package/src/workflows/feature/feature.ts +34 -118
  51. package/src/workflows/feature/flag-mode.feature +4 -0
  52. package/src/workflows/feature/hands-on-skip.feature +37 -65
  53. package/src/workflows/feature/index.ts +0 -1
  54. package/src/workflows/feature/lens-models.feature +4 -0
  55. package/src/workflows/feature/lens-review.feature +4 -0
  56. package/src/workflows/feature/naive-acceptance.feature +4 -0
  57. package/src/workflows/feature/one-step-land.feature +4 -4
  58. package/src/workflows/feature/planning.ts +2 -1
  59. package/src/workflows/feature/prototype-rounds.feature +5 -1
  60. package/src/workflows/feature/prototype.feature +11 -3
  61. package/src/workflows/feature/prototype.ts +18 -29
  62. package/src/workflows/feature/refactor-absorbs.feature +7 -8
  63. package/src/workflows/feature/refactor-first.feature +4 -5
  64. package/src/workflows/feature/refactor-lens-models.feature +4 -0
  65. package/src/workflows/feature/refactor-rebuild.feature +4 -6
  66. package/src/workflows/feature/refactor-review.feature +31 -11
  67. package/src/workflows/feature/refactor.ts +30 -64
  68. package/src/workflows/feature/review-answers.feature +4 -0
  69. package/src/workflows/feature/review-gate.feature +13 -1
  70. package/src/workflows/feature/reviewer-walkthrough.feature +4 -0
  71. package/src/workflows/feature/risk-tier.feature +32 -0
  72. package/src/workflows/feature/seam-rejection.feature +8 -4
  73. package/src/workflows/feature/seam.feature +8 -0
  74. package/src/workflows/feature/seam.test.ts +11 -28
  75. package/src/workflows/feature/seam.ts +52 -165
  76. package/src/workflows/feature/severity-budget.feature +4 -0
  77. package/src/workflows/feature/severity-sorted-review.feature +4 -0
  78. package/src/workflows/feature/standards.feature +4 -0
  79. package/src/workflows/feature/tier-pin.feature +4 -0
  80. package/src/workflows/index.ts +0 -1
  81. package/src/workflows/shared/lenses.ts +0 -13
  82. package/src/workflows/shared/options.test.ts +2 -11
  83. package/src/workflows/shared/options.ts +4 -21
  84. package/src/workflows/shared/review.test.ts +3 -3
  85. package/src/workflows/shared/steps.ts +0 -2
  86. package/src/workflows/shared/text.test.ts +41 -71
  87. package/src/workflows/shared/text.ts +82 -212
  88. package/src/workflows/shared/vars.ts +2 -8
  89. package/src/workflows/shared/workflows.steps.ts +0 -13
  90. package/claude/hooks/context.ts +0 -66
  91. package/src/replay/Slices.test.ts +0 -73
  92. package/src/replay/Slices.ts +0 -45
  93. package/src/wire/ContextLimit.fixture.ts +0 -30
  94. package/src/wire/ContextLimit.test.ts +0 -96
  95. package/src/wire/ContextLimit.ts +0 -169
  96. package/src/wire/Slices.ts +0 -25
  97. package/src/wire/golden/1/slices.json +0 -11
  98. package/src/workflows/feature/context-limit.feature +0 -37
  99. package/src/workflows/feature/continuation-cap.feature +0 -65
  100. package/src/workflows/feature/continuation-rebuild.feature +0 -88
  101. package/src/workflows/feature/continuation-slices.feature +0 -64
  102. package/src/workflows/feature/continuation.feature +0 -205
  103. package/src/workflows/feature/slice-findings.feature +0 -252
  104. package/src/workflows/feature/slicing-cap.feature +0 -23
  105. package/src/workflows/feature/slicing.feature +0 -265
  106. package/src/workflows/feature/slicing.test.ts +0 -49
  107. package/src/workflows/feature/slicing.ts +0 -105
  108. package/src/workflows/shared/continuation.ts +0 -92
@@ -7,7 +7,7 @@ import { isIP } from "node:net";
7
7
  import * as Crypto from "node:crypto";
8
8
  import { createHash, randomUUID } from "node:crypto";
9
9
  import * as NFS from "node:fs";
10
- import { appendFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
10
+ import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
11
11
  import * as OS from "node:os";
12
12
  import { homedir, networkInterfaces, tmpdir } from "node:os";
13
13
  import * as Path from "node:path";
@@ -6766,7 +6766,7 @@ var init_label = __esmMin((() => {
6766
6766
  }));
6767
6767
  //#endregion
6768
6768
  //#region node_modules/effect/dist/esm/internal/core-effect.js
6769
- var annotateLogs$1, asSome, try_$1, catchAllDefect$1, catchTag$1, diffFiberRefs, bind, bindTo, match$4, fiberRefs, ignore$1, logWithLevel, logWarning$1, logError$1, mapErrorCause, negate$1, orElseSucceed$1, patchFiberRefs, promise$1, provideService$1, provideServiceEffect, sandbox$1, sleep$1, succeedNone, summarized, tapErrorCause$1, tryPromise$1, tryMap$1, updateFiberRefs, when$1, serviceOption$1;
6769
+ var annotateLogs$1, asSome, try_$1, catchTag$1, diffFiberRefs, bind, bindTo, match$4, fiberRefs, ignore$1, logWithLevel, logWarning$1, logError$1, mapErrorCause, negate$1, orElseSucceed$1, patchFiberRefs, promise$1, provideService$1, provideServiceEffect, sandbox$1, sleep$1, succeedNone, summarized, tapErrorCause$1, tryPromise$1, tryMap$1, updateFiberRefs, when$1, serviceOption$1;
6770
6770
  var init_core_effect = __esmMin((() => {
6771
6771
  init_Clock();
6772
6772
  init_Context();
@@ -6801,13 +6801,6 @@ var init_core_effect = __esmMin((() => {
6801
6801
  }
6802
6802
  });
6803
6803
  };
6804
- catchAllDefect$1 = /*#__PURE__*/ dual(2, (self, f) => catchAllCause$1(self, (cause) => {
6805
- const option = find(cause, (_) => isDieType$1(_) ? some(_) : none$4());
6806
- switch (option._tag) {
6807
- case "None": return failCause$6(cause);
6808
- case "Some": return f(option.value.defect);
6809
- }
6810
- }));
6811
6804
  catchTag$1 = /*#__PURE__*/ dual((args) => isEffect$1(args[0]), (self, ...args) => {
6812
6805
  const f = args[args.length - 1];
6813
6806
  let predicate;
@@ -7656,7 +7649,7 @@ _A: (_) => _ };
7656
7649
  //#endregion
7657
7650
  //#region node_modules/effect/dist/esm/internal/metric/hook.js
7658
7651
  var MetricHookSymbolKey, MetricHookTypeId, metricHookVariance, make$25, bigint0$1, counter$1, frequency, gauge, histogram$1, summary, calculateQuantiles, resolveQuantile;
7659
- var init_hook$1 = __esmMin((() => {
7652
+ var init_hook = __esmMin((() => {
7660
7653
  init_Array();
7661
7654
  init_Duration();
7662
7655
  init_Function();
@@ -7953,7 +7946,7 @@ var init_registry = __esmMin((() => {
7953
7946
  init_Function();
7954
7947
  init_MutableHashMap();
7955
7948
  init_Option();
7956
- init_hook$1();
7949
+ init_hook();
7957
7950
  init_keyType();
7958
7951
  init_pair();
7959
7952
  MetricRegistrySymbolKey = "effect/MetricRegistry";
@@ -11382,7 +11375,7 @@ var init_MutableQueue = __esmMin((() => {
11382
11375
  }));
11383
11376
  //#endregion
11384
11377
  //#region node_modules/effect/dist/esm/Effect.js
11385
- var isEffect, all$1, forEach$2, async, withFiberRuntime, fail$7, failCause$2, die, dieMessage, gen, promise, succeed$3, suspend$5, sync$1, _void, catchAll, catchAllCause, catchAllDefect, catchIf, catchTag, ignore, sandbox, retry, try_, tryMap, tryPromise, interrupt, interruptible, uninterruptible, uninterruptibleMask, as, asVoid, map$4, mapBoth$1, mapError$1, negate, acquireRelease, acquireUseRelease$2, addFinalizer, ensuring$2, onError, scope$1, scopeWith, scopedWith$1, scoped$3, fiberIdWith, forkDaemon, forkIn, forkScoped, timeout, provide$1, provideService, serviceOption, either, exit$1, intoDeferred, when, flatMap$4, andThen, flatten$5, race, raceWith, tap, tapErrorCause, repeat, match$2, matchCause, matchCauseEffect, matchEffect, logWarning, logError, annotateLogs, orDie, orElse$1, orElseSucceed, runtime, unsafeMakeSemaphore, makeSemaphore, unsafeMakeLatch, runFork$1, runPromise, runSync, zip$1, zipLeft, zipRight$1, zipWith, fnUntraced;
11378
+ var isEffect, all$1, forEach$2, async, withFiberRuntime, fail$7, failCause$2, die, dieMessage, gen, promise, succeed$3, suspend$5, sync$1, _void, catchAll, catchAllCause, catchIf, catchTag, ignore, sandbox, retry, try_, tryMap, tryPromise, interrupt, interruptible, uninterruptible, uninterruptibleMask, as, asVoid, map$4, mapBoth$1, mapError$1, negate, acquireRelease, acquireUseRelease$2, addFinalizer, ensuring$2, onError, scope$1, scopeWith, scopedWith$1, scoped$3, fiberIdWith, forkDaemon, forkIn, forkScoped, timeout, provide$1, provideService, serviceOption, either, exit$1, intoDeferred, when, flatMap$4, andThen, flatten$5, race, raceWith, tap, tapErrorCause, repeat, match$2, matchCause, matchCauseEffect, matchEffect, logWarning, logError, annotateLogs, orDie, orElse$1, orElseSucceed, runtime, unsafeMakeSemaphore, makeSemaphore, unsafeMakeLatch, runFork$1, runPromise, runSync, zip$1, zipLeft, zipRight$1, zipWith, fnUntraced;
11386
11379
  var init_Effect = __esmMin((() => {
11387
11380
  init_core_effect();
11388
11381
  init_core();
@@ -11408,7 +11401,6 @@ var init_Effect = __esmMin((() => {
11408
11401
  _void = void_$4;
11409
11402
  catchAll = catchAll$1;
11410
11403
  catchAllCause = catchAllCause$1;
11411
- catchAllDefect = catchAllDefect$1;
11412
11404
  catchIf = catchIf$1;
11413
11405
  catchTag = catchTag$1;
11414
11406
  ignore = ignore$1;
@@ -14226,7 +14218,7 @@ function getBrands(ast) {
14226
14218
  onSome: (brands) => brands.map((brand) => ` & Brand<${formatUnknown(brand)}>`).join("")
14227
14219
  });
14228
14220
  }
14229
- var TypeConstructorAnnotationId, BrandAnnotationId, SchemaIdAnnotationId, MessageAnnotationId, MissingMessageAnnotationId, IdentifierAnnotationId, TitleAnnotationId, AutoTitleAnnotationId, DescriptionAnnotationId, ExamplesAnnotationId, DefaultAnnotationId, JSONSchemaAnnotationId, ArbitraryAnnotationId, PrettyAnnotationId, EquivalenceAnnotationId, DocumentationAnnotationId, ConcurrencyAnnotationId, BatchingAnnotationId, ParseIssueTitleAnnotationId, ParseOptionsAnnotationId, DecodingFallbackAnnotationId, SurrogateAnnotationId, StableFilterAnnotationId, getAnnotation, getBrandAnnotation, getMessageAnnotation, getMissingMessageAnnotation, getTitleAnnotation, getAutoTitleAnnotation, getIdentifierAnnotation, getDescriptionAnnotation, getConcurrencyAnnotation, getBatchingAnnotation, getParseIssueTitleAnnotation$1, getParseOptionsAnnotation, getDecodingFallbackAnnotation, getSurrogateAnnotation, getStableFilterAnnotation, hasStableFilter, JSONIdentifierAnnotationId, getJSONIdentifierAnnotation, getJSONIdentifier, Declaration, createASTGuard, Literal$1, isLiteral, UniqueSymbol, UndefinedKeyword, undefinedKeyword, NeverKeyword, neverKeyword, UnknownKeyword, unknownKeyword, AnyKeyword, anyKeyword, StringKeyword, stringKeyword, isStringKeyword, NumberKeyword, numberKeyword, isNumberKeyword, BooleanKeyword, booleanKeyword, isBooleanKeyword, BigIntKeyword, bigIntKeyword, SymbolKeyword, symbolKeyword, isSymbolKeyword, Type$1, OptionalType, getRestASTs, TupleType, formatTuple, PropertySignature, isParameter, IndexSignature, TypeLiteral, formatIndexSignatures, formatTypeLiteral, isTypeLiteral, sortCandidates, literalMap, flatten$2, unify, Union$1, mapMembers, isMembers, isUnion, toJSONMemoMap, Suspend, Refinement$1, isRefinement$1, defaultParseOption, Transformation$1, isTransformation$1, FinalTransformation, createTransformationGuard, ComposeTransformation, composeTransformation, PropertySignatureTransformation$1, TypeLiteralTransformation, isTypeLiteralTransformation, annotations, STRING_KEYWORD_PATTERN, NUMBER_KEYWORD_PATTERN, getTemplateLiteralSpanTypePattern, handleTemplateLiteralSpanTypeParens, getTemplateLiteralPattern, getTemplateLiteralRegExp, record$2, pickAnnotations, omitAnnotations, preserveTransformationAnnotations, typeAST, getTransformationFrom, encodedAST_, encodedAST, toJSONAnnotations, getEncodedParameter, formatKeyword, getOrElseExpected, getExpected;
14221
+ var TypeConstructorAnnotationId, BrandAnnotationId, SchemaIdAnnotationId, MessageAnnotationId, MissingMessageAnnotationId, IdentifierAnnotationId, TitleAnnotationId, AutoTitleAnnotationId, DescriptionAnnotationId, ExamplesAnnotationId, DefaultAnnotationId, JSONSchemaAnnotationId, ArbitraryAnnotationId, PrettyAnnotationId, EquivalenceAnnotationId, DocumentationAnnotationId, ConcurrencyAnnotationId, BatchingAnnotationId, ParseIssueTitleAnnotationId, ParseOptionsAnnotationId, DecodingFallbackAnnotationId, SurrogateAnnotationId, StableFilterAnnotationId, getAnnotation, getBrandAnnotation, getMessageAnnotation, getMissingMessageAnnotation, getTitleAnnotation, getAutoTitleAnnotation, getIdentifierAnnotation, getDescriptionAnnotation, getConcurrencyAnnotation, getBatchingAnnotation, getParseIssueTitleAnnotation$1, getParseOptionsAnnotation, getDecodingFallbackAnnotation, getSurrogateAnnotation, getStableFilterAnnotation, hasStableFilter, JSONIdentifierAnnotationId, getJSONIdentifierAnnotation, getJSONIdentifier, Declaration, createASTGuard, Literal$1, isLiteral, UniqueSymbol, UndefinedKeyword, undefinedKeyword, NeverKeyword, neverKeyword, UnknownKeyword, unknownKeyword, AnyKeyword, anyKeyword, StringKeyword, stringKeyword, isStringKeyword, NumberKeyword, numberKeyword, isNumberKeyword, BooleanKeyword, booleanKeyword, isBooleanKeyword, BigIntKeyword, bigIntKeyword, SymbolKeyword, symbolKeyword, isSymbolKeyword, Type$1, OptionalType, getRestASTs, TupleType, formatTuple, PropertySignature, isParameter, IndexSignature, TypeLiteral, formatIndexSignatures, formatTypeLiteral, isTypeLiteral, sortCandidates, literalMap, flatten$2, unify, Union$1, mapMembers, isMembers, isUnion, toJSONMemoMap, Suspend, Refinement$1, isRefinement$1, defaultParseOption, Transformation$1, isTransformation$1, FinalTransformation, createTransformationGuard, ComposeTransformation, composeTransformation, PropertySignatureTransformation$1, TypeLiteralTransformation, isTypeLiteralTransformation, annotations, STRING_KEYWORD_PATTERN, NUMBER_KEYWORD_PATTERN, getTemplateLiteralSpanTypePattern, handleTemplateLiteralSpanTypeParens, getTemplateLiteralPattern, getTemplateLiteralRegExp, record$1, pickAnnotations, omitAnnotations, preserveTransformationAnnotations, typeAST, getTransformationFrom, encodedAST_, encodedAST, toJSONAnnotations, getEncodedParameter, formatKeyword, getOrElseExpected, getExpected;
14230
14222
  var init_SchemaAST = __esmMin((() => {
14231
14223
  init_Array();
14232
14224
  init_Function();
@@ -15189,7 +15181,7 @@ var init_SchemaAST = __esmMin((() => {
15189
15181
  return pattern;
15190
15182
  };
15191
15183
  getTemplateLiteralRegExp = (ast) => new RegExp(`^${getTemplateLiteralPattern(ast, false, true)}$`);
15192
- record$2 = (key, value) => {
15184
+ record$1 = (key, value) => {
15193
15185
  const propertySignatures = [];
15194
15186
  const indexSignatures = [];
15195
15187
  const go = (key) => {
@@ -16774,7 +16766,7 @@ var init_Schema = __esmMin((() => {
16774
16766
  const issFrom = [];
16775
16767
  const issTo = [];
16776
16768
  for (const r of records) {
16777
- const { indexSignatures, propertySignatures } = record$2(r.key.ast, r.value.ast);
16769
+ const { indexSignatures, propertySignatures } = record$1(r.key.ast, r.value.ast);
16778
16770
  propertySignatures.forEach((ps) => {
16779
16771
  from.push(ps);
16780
16772
  to.push(new PropertySignature(ps.name, typeAST(ps.type), ps.isOptional, ps.isReadonly, ps.annotations));
@@ -16789,7 +16781,7 @@ var init_Schema = __esmMin((() => {
16789
16781
  }
16790
16782
  const iss = [];
16791
16783
  for (const r of records) {
16792
- const { indexSignatures, propertySignatures } = record$2(r.key.ast, r.value.ast);
16784
+ const { indexSignatures, propertySignatures } = record$1(r.key.ast, r.value.ast);
16793
16785
  propertySignatures.forEach((ps) => pss.push(ps));
16794
16786
  indexSignatures.forEach((is) => iss.push(is));
16795
16787
  }
@@ -19962,7 +19954,7 @@ var init_esm = __esmMin((() => {
19962
19954
  init_mergeStrategy();
19963
19955
  init_metric();
19964
19956
  init_boundaries();
19965
- init_hook$1();
19957
+ init_hook();
19966
19958
  init_key();
19967
19959
  init_keyType();
19968
19960
  init_label();
@@ -21023,10 +21015,7 @@ while :; do
21023
21015
  model="$(gtd next --json=model)"
21024
21016
  system="$(gtd next --json=system)"
21025
21017
  validate="$(gtd next --json=validate)"
21026
- context="$(gtd next --json=context)"
21027
21018
  log="$(gtd next --json=log)"
21028
- # Absolute, since the hook runs wherever the agent last cd'ed to.
21029
- case "$log" in /*) ;; *) log="$(git rev-parse --show-toplevel)/$log" ;; esac
21030
21019
  # \`model\` is a hint, not a model name — the bundled workflow ships
21031
21020
  # \`smart\` and \`base\`, which \`claude --model\` rejects. Resolve them
21032
21021
  # from your environment; unset, the hint is dropped and the agent runs
@@ -21035,18 +21024,6 @@ while :; do
21035
21024
  smart) model="\${GTD_LOOP_MODEL_SMART:-}" ;;
21036
21025
  base) model="\${GTD_LOOP_MODEL_BASE:-}" ;;
21037
21026
  esac
21038
- # The step's context limit, as a Claude Code hook after every tool
21039
- # call and before compacting. Past \`hard\` it stops the turn and
21040
- # appends why to the stop file, so we land nothing and stop too.
21041
- soft="$(printf '%s' "$context" | sed -n 's/.*"soft":\\([0-9.]*\\).*/\\1/p')"
21042
- hard="$(printf '%s' "$context" | sed -n 's/.*"hard":\\([0-9.]*\\).*/\\1/p')"
21043
- window="\${GTD_LOOP_CONTEXT_WINDOW:-}"
21044
- case "$model" in *'[1m]'* | *'[1M]'*) window="\${window:-1000000}" ;; esac
21045
- hook="gtd context-hook\${soft:+ --soft $soft}\${hard:+ --hard $hard}\${window:+ --window $window}"
21046
- hooks="[{\\"hooks\\":[{\\"type\\":\\"command\\",\\"command\\":\\"$hook\\"}]}]"
21047
- settings="{\\"hooks\\":{\\"PostToolUse\\":$hooks,\\"PreCompact\\":$hooks}}"
21048
- stop="$log.context-stop"
21049
- rm -f "$stop"
21050
21027
  # One turn, its prompt on stdin. The prompt embeds a full diff, so it
21051
21028
  # never goes on argv — argv is capped (~1 MB on macOS, and POSIX
21052
21029
  # guarantees only 4 KB, ARG_MAX), and a diff crosses that far sooner
@@ -21054,15 +21031,15 @@ while :; do
21054
21031
  # whether \`claude --resume\` re-applies the original session's is a
21055
21032
  # harness detail gtd cannot verify from outside. Its JSON envelope
21056
21033
  # goes to the log, and its \`total_cost_usd\` adds up into $cost. An
21057
- # \`is_error\` envelope (max turns, an error mid-turn) or one another
21058
- # hook stopped fails the turn even at exit 0: landing it would land
21059
- # the failure as work. Only a turn that never ran (no envelope: the
21034
+ # \`is_error\` envelope (max turns, an error mid-turn) or one a hook
21035
+ # stopped fails the turn even at exit 0: landing it would land the
21036
+ # failure as work. Only a turn that never ran (no envelope: the
21060
21037
  # session flag was refused) is worth retrying under the other flag.
21061
21038
  turn() {
21062
- out="$(GTD_LOOP_CONTEXT_STOP_FILE="$stop" claude -p "$@" \\
21039
+ out="$(claude -p "$@" \\
21063
21040
  \${model:+--model "$model"} \\
21064
21041
  \${system:+--system-prompt "$system"} \\
21065
- --settings "$settings" --output-format json \\
21042
+ --output-format json \\
21066
21043
  --dangerously-skip-permissions 2>>"$log")" && ok=0 || ok=$?
21067
21044
  printf '%s\\n' "$out" >>"$log"
21068
21045
  case "$out" in *'"type":"result"'*) ran=true ;; *) ran=false ;; esac
@@ -21072,10 +21049,6 @@ while :; do
21072
21049
  usd="$(printf '%s\\n' "$out" | sed -n 's/.*"total_cost_usd":\\([0-9.eE+-]*\\).*/\\1/p' | tail -n 1)"
21073
21050
  [ -z "$usd" ] || cost="$(awk -v a="\${cost:-0}" -v b="$usd" 'BEGIN { printf "%.6f", a + b }')"
21074
21051
  [ "\${cost:-}" != 0.000000 ] || cost=
21075
- if [ -s "$stop" ]; then
21076
- cat "$stop" >&2
21077
- exit 1
21078
- fi
21079
21052
  return "$ok"
21080
21053
  }
21081
21054
  # Plain \`gtd next\` (not --json=content) renders the diff once.
@@ -21544,6 +21517,16 @@ way — an existing \`gtd-loop\` survives untouched beside the new
21544
21517
  \`gtd-door review [base]\`). When either is still present beside the suite,
21545
21518
  name it to the user as superseded and ask before removing it — never remove
21546
21519
  one unasked.
21520
+
21521
+ An older \`gtd-build\` passed a \`gtd context-hook\` hook to every turn, and
21522
+ an older setup guide had it registered in Claude Code's own settings
21523
+ (\`PostToolUse\`/\`PreCompact\`). That command no longer exists: a hook
21524
+ still calling it fails on every tool call. Replace such a \`gtd-build\` as a
21525
+ differing file above, and look for a \`gtd context-hook\` entry in the user's
21526
+ Claude Code settings files (\`~/.claude/settings.json\`, the repository's
21527
+ \`.claude/settings.json\` and \`.claude/settings.local.json\`): name each file
21528
+ to the user, then remove that entry alone, leaving every other key
21529
+ byte-identical.
21547
21530
  `;
21548
21531
  const EDITOR_INTEGRATION = `
21549
21532
  ## Editor integration: offer it, don't bury it
@@ -21705,20 +21688,6 @@ const scope = async (scope, fn) => {
21705
21688
  context.popScope();
21706
21689
  }
21707
21690
  };
21708
- /**
21709
- * Mark the span `fn` covers as one named slice of the process. Lands no
21710
- * commit, prefixes no step name, opens no memory scope.
21711
- */
21712
- const slice = async (name, fn) => {
21713
- const context = ctx();
21714
- if (typeof context.enterSlice !== "function") return fn();
21715
- context.enterSlice(name);
21716
- try {
21717
- return await fn();
21718
- } finally {
21719
- context.leaveSlice?.();
21720
- }
21721
- };
21722
21691
  const read = (path) => ctx().read(path);
21723
21692
  /** Every path in the tree matching `pattern` (`*` stays within a segment, `**` crosses them). */
21724
21693
  const glob = (pattern) => ctx().glob(pattern);
@@ -22299,7 +22268,6 @@ var flows_exports = /* @__PURE__ */ __exportAll({
22299
22268
  sections: () => sections,
22300
22269
  showScript: () => showScript,
22301
22270
  skillsFor: () => skillsFor,
22302
- slice: () => slice,
22303
22271
  start: () => start,
22304
22272
  threads: () => threads,
22305
22273
  vars: () => vars,
@@ -37502,470 +37470,440 @@ const accessShapeFault = (value) => {
37502
37470
  };
37503
37471
  const sameGlobs = (a, b) => a === void 0 || b === void 0 ? a === b : a.length === b.length && a.every((g, i) => g === b[i]);
37504
37472
  const sameAccess = (a, b) => sameGlobs(a?.read, b?.read) && sameGlobs(a?.write, b?.write);
37505
- //#endregion
37506
- //#region src/wire/constants.ts
37507
- var renderFormat, ACCESS_REFUSAL;
37508
- var init_constants = __esmMin((() => {
37509
- renderFormat = (fmt, ...args) => {
37510
- let i = 0;
37511
- return fmt.replace(/%s/g, () => args[i++] ?? "");
37512
- };
37513
- ACCESS_REFUSAL = "gtd land: this turn wrote outside its write access — revert these paths, then run `gtd land` again:";
37514
- }));
37515
- //#endregion
37516
- //#region src/wire/ExitCodes.ts
37517
- var init_ExitCodes = __esmMin((() => {}));
37518
- //#endregion
37519
- //#region src/wire/types.ts
37520
- var init_types = __esmMin((() => {}));
37473
+ /** The plain-text twin of `OutcomeScript.ts`'s `printfLine`. Lives here so `src/wire/`'s encoders reach it without importing out of the leaf. */
37474
+ const renderFormat = (fmt, ...args) => {
37475
+ let i = 0;
37476
+ return fmt.replace(/%s/g, () => args[i++] ?? "");
37477
+ };
37478
+ /** The first line of the landing refusal for a turn that wrote outside its `write` access — a stable marker drivers and tests grep for. */
37479
+ const ACCESS_REFUSAL = "gtd land: this turn wrote outside its write access — revert these paths, then run `gtd land` again:";
37521
37480
  //#endregion
37522
37481
  //#region src/wire/Demand.ts
37523
- var beatKindOf, stallDiagnosis, demandOf;
37524
- var init_Demand = __esmMin((() => {
37525
- beatKindOf = (input) => {
37526
- if (input.stalled) return "stalled";
37527
- if (input.contentKind === "message" && input.dirty) return "capture";
37528
- return input.contentKind;
37529
- };
37530
- stallDiagnosis = (state, actor) => `stalled at "${state}": the last gtd(${actor}): ${state} turn landed an empty attempt, the tree is clean, and another dispatch would repeat it.\n\nTwo ways out:\n - sharpen the step's prompt so the turn has something concrete to author\n - pass allowEmpty: true to the step, so an empty turn completes it and the\n flow decides what follows — finishing, or an escalation step after N\n empty turns\n`;
37531
- demandOf = (input) => {
37532
- const { rendered, kind, session, validate } = input;
37533
- const content = kind === "stalled" ? stallDiagnosis(rendered.state, rendered.actor) : rendered.content;
37534
- const skills = rendered.skills !== void 0 && rendered.skills.length > 0 ? rendered.skills : void 0;
37535
- const access = rendered.access ?? {
37536
- read: null,
37537
- write: null
37538
- };
37539
- return kind === "prompt" ? {
37540
- kind,
37541
- content,
37542
- session,
37543
- validate,
37544
- skills,
37545
- access
37546
- } : {
37547
- kind,
37548
- content
37549
- };
37482
+ /**
37483
+ * The beat kind for a rest, in precedence order:
37484
+ *
37485
+ * 1. `stalled` — `Edge.ts`'s `stalledAt` verdict, passed in as a plain
37486
+ * boolean (this module never resolves it itself). `stalledAt` already
37487
+ * requires a clean tree, so this can never collide with `capture` below.
37488
+ * 2. `capture` — a `message` rest with a DIRTY tree: the human already
37489
+ * acted; a driver lands it immediately.
37490
+ * 3. Otherwise, the content kind verbatim (`script`/`prompt`, or `message`
37491
+ * on a clean tree).
37492
+ */
37493
+ const beatKindOf = (input) => {
37494
+ if (input.stalled) return "stalled";
37495
+ if (input.contentKind === "message" && input.dirty) return "capture";
37496
+ return input.contentKind;
37497
+ };
37498
+ /**
37499
+ * The `stalled` beat's own content: a diagnosis naming the stuck step and
37500
+ * the ways out. The first line is shaped so a stderr grep for
37501
+ * `stalled at "<state>"` stays a stable substring (see
37502
+ * `tests/integration/features/driver-doc.feature`).
37503
+ */
37504
+ const stallDiagnosis = (state, actor) => `stalled at "${state}": the last gtd(${actor}): ${state} turn landed an empty attempt, the tree is clean, and another dispatch would repeat it.\n\nTwo ways out:\n - sharpen the step's prompt so the turn has something concrete to author\n - pass allowEmpty: true to the step, so an empty turn completes it and the\n flow decides what follows — finishing, or an escalation step after N\n empty turns\n`;
37505
+ /**
37506
+ * Assemble one `Demand` — the ONLY place a rest's resolved content becomes
37507
+ * `Demand.content` (`stallDiagnosis` at `kind === "stalled"`, the rendered
37508
+ * content otherwise), and the only place `session`/`validate`/`skills`/`access` are
37509
+ * attached, which the `prompt` variant's own type makes impossible to do at
37510
+ * any other kind.
37511
+ */
37512
+ const demandOf = (input) => {
37513
+ const { rendered, kind, session, validate } = input;
37514
+ const content = kind === "stalled" ? stallDiagnosis(rendered.state, rendered.actor) : rendered.content;
37515
+ const skills = rendered.skills !== void 0 && rendered.skills.length > 0 ? rendered.skills : void 0;
37516
+ const access = rendered.access ?? {
37517
+ read: null,
37518
+ write: null
37550
37519
  };
37551
- }));
37520
+ return kind === "prompt" ? {
37521
+ kind,
37522
+ content,
37523
+ session,
37524
+ validate,
37525
+ skills,
37526
+ access
37527
+ } : {
37528
+ kind,
37529
+ content
37530
+ };
37531
+ };
37552
37532
  //#endregion
37553
37533
  //#region src/wire/BeatStatus.ts
37554
- var statusOf;
37555
- var init_BeatStatus = __esmMin((() => {
37556
- statusOf = (input) => {
37557
- const { rendered, idle, initial, workflow, log, changes, next, cost, costByModel, approvals } = input;
37558
- return {
37559
- idle,
37560
- initial,
37561
- model: rendered.model,
37562
- system: rendered.system !== void 0 && rendered.system !== "" ? rendered.system : void 0,
37563
- context: rendered.context,
37564
- log,
37565
- state: rendered.state,
37566
- actor: rendered.actor,
37567
- workflow,
37568
- label: rendered.label,
37569
- memory: rendered.memory,
37570
- file: rendered.file,
37571
- mode: rendered.mode,
37572
- changes,
37573
- next,
37574
- cost,
37575
- costByModel,
37576
- judge: rendered.judge,
37577
- approvals
37578
- };
37534
+ /**
37535
+ * Assemble one `BeatStatus`. `system` is omitted (not just falsy) when its
37536
+ * rendered value is the empty string, unlike `model`: an empty
37537
+ * `--system-prompt ""` would silently delete the harness's own default
37538
+ * instead of failing loudly.
37539
+ */
37540
+ const statusOf = (input) => {
37541
+ const { rendered, idle, initial, workflow, log, changes, next, cost, costByModel, approvals } = input;
37542
+ return {
37543
+ idle,
37544
+ initial,
37545
+ model: rendered.model,
37546
+ system: rendered.system !== void 0 && rendered.system !== "" ? rendered.system : void 0,
37547
+ log,
37548
+ state: rendered.state,
37549
+ actor: rendered.actor,
37550
+ workflow,
37551
+ label: rendered.label,
37552
+ memory: rendered.memory,
37553
+ file: rendered.file,
37554
+ mode: rendered.mode,
37555
+ changes,
37556
+ next,
37557
+ cost,
37558
+ costByModel,
37559
+ judge: rendered.judge,
37560
+ approvals
37579
37561
  };
37580
- }));
37562
+ };
37581
37563
  //#endregion
37582
37564
  //#region src/wire/BeatDocument.ts
37583
- var nextField, assertNeverDemand, dispatchFieldsOf, beatDocument, renderBeatJson;
37584
- var init_BeatDocument = __esmMin((() => {
37585
- init_constants();
37586
- nextField = (next) => next === null ? null : { target: next.target };
37587
- assertNeverDemand = (demand) => {
37588
- throw new Error(`unreachable demand: ${JSON.stringify(demand)}`);
37589
- };
37590
- dispatchFieldsOf = (demand) => {
37591
- switch (demand.kind) {
37592
- case "prompt": return {
37593
- session: demand.session,
37594
- validate: demand.validate,
37595
- skills: demand.skills,
37596
- access: demand.access
37597
- };
37598
- case "capture":
37599
- case "message":
37600
- case "script":
37601
- case "stalled": return {
37602
- session: void 0,
37603
- validate: void 0,
37604
- skills: void 0,
37605
- access: void 0
37606
- };
37607
- default: return assertNeverDemand(demand);
37608
- }
37609
- };
37610
- beatDocument = (demand, status) => {
37611
- const { session, validate, skills, access } = dispatchFieldsOf(demand);
37612
- const hasCost = status.cost > 0;
37613
- return {
37614
- schema: 1,
37615
- kind: demand.kind,
37616
- content: demand.content,
37617
- idle: status.idle,
37618
- initial: status.initial,
37619
- session,
37620
- model: status.model,
37621
- system: status.system,
37622
- context: status.context,
37623
- validate,
37624
- log: status.log,
37625
- state: status.state,
37626
- actor: status.actor,
37627
- workflow: status.workflow,
37628
- label: status.label,
37629
- memory: status.memory,
37630
- file: status.file,
37631
- mode: status.mode,
37632
- changes: status.changes,
37633
- next: nextField(status.next),
37634
- cost: hasCost ? status.cost : void 0,
37635
- costByModel: hasCost ? status.costByModel : void 0,
37636
- judge: status.judge,
37637
- skills,
37638
- access,
37639
- approvals: status.approvals
37640
- };
37565
+ /** `gtd next --json`'s `next` key — `null` when landing would commit nothing. */
37566
+ const nextField = (next) => next === null ? null : { target: next.target };
37567
+ /** Unreachable in a well-typed call — used as the `default` arm of an exhaustive switch over `Demand["kind"]`, so a future `BeatKind` member that isn't handled fails `tsc`, not just review. */
37568
+ const assertNeverDemand = (demand) => {
37569
+ throw new Error(`unreachable demand: ${JSON.stringify(demand)}`);
37570
+ };
37571
+ /**
37572
+ * The ONE exhaustive switch translating a `Demand` into its wire
37573
+ * `session`/`validate`/`skills`/`access` set — only the `prompt` variant carries
37574
+ * any of them, and every other member is listed explicitly (not a
37575
+ * `default:` fallthrough) so adding a sixth `BeatKind` without adding its
37576
+ * case here fails `tsc` at the `assertNeverDemand` call, not silently
37577
+ * falling through to "no session."
37578
+ */
37579
+ const dispatchFieldsOf = (demand) => {
37580
+ switch (demand.kind) {
37581
+ case "prompt": return {
37582
+ session: demand.session,
37583
+ validate: demand.validate,
37584
+ skills: demand.skills,
37585
+ access: demand.access
37586
+ };
37587
+ case "capture":
37588
+ case "message":
37589
+ case "script":
37590
+ case "stalled": return {
37591
+ session: void 0,
37592
+ validate: void 0,
37593
+ skills: void 0,
37594
+ access: void 0
37595
+ };
37596
+ default: return assertNeverDemand(demand);
37597
+ }
37598
+ };
37599
+ /**
37600
+ * Flatten a `Demand` plus its `BeatStatus` into the single 25-key document
37601
+ * `gtd next --json` emits — the ONLY place the two are joined. `cost`/
37602
+ * `costByModel` are omitted together, exactly when no cost was recorded
37603
+ * (`cost <= 0`).
37604
+ */
37605
+ const beatDocument = (demand, status) => {
37606
+ const { session, validate, skills, access } = dispatchFieldsOf(demand);
37607
+ const hasCost = status.cost > 0;
37608
+ return {
37609
+ schema: 1,
37610
+ kind: demand.kind,
37611
+ content: demand.content,
37612
+ idle: status.idle,
37613
+ initial: status.initial,
37614
+ session,
37615
+ model: status.model,
37616
+ system: status.system,
37617
+ validate,
37618
+ log: status.log,
37619
+ state: status.state,
37620
+ actor: status.actor,
37621
+ workflow: status.workflow,
37622
+ label: status.label,
37623
+ memory: status.memory,
37624
+ file: status.file,
37625
+ mode: status.mode,
37626
+ changes: status.changes,
37627
+ next: nextField(status.next),
37628
+ cost: hasCost ? status.cost : void 0,
37629
+ costByModel: hasCost ? status.costByModel : void 0,
37630
+ judge: status.judge,
37631
+ skills,
37632
+ access,
37633
+ approvals: status.approvals
37641
37634
  };
37642
- renderBeatJson = (document) => JSON.stringify(document) + "\n";
37643
- }));
37635
+ };
37636
+ const renderBeatJson = (document) => JSON.stringify(document) + "\n";
37644
37637
  //#endregion
37645
37638
  //#region src/wire/BeatPlain.ts
37646
- var breakdownIsInformative, costStatusLines, pendingStatusLines, nextStatusLine, definedFields, beatHeaderLines, SCRIPT_INSTRUCTION, CAPTURE_INSTRUCTION, DEMAND_BRIEFING, selfValidateInstruction, renderBeatPlain;
37647
- var init_BeatPlain = __esmMin((() => {
37648
- init_constants();
37649
- breakdownIsInformative = (byModel) => byModel.length > 1 || byModel.length === 1 && byModel[0].model !== "unspecified";
37650
- costStatusLines = (cost, byModel) => {
37651
- if (cost <= 0) return [];
37652
- const lines = [`Cost: ${cost}`];
37653
- if (breakdownIsInformative(byModel)) for (const m of byModel) lines.push(` ${m.model}: ${m.cost}`);
37654
- return lines;
37655
- };
37656
- pendingStatusLines = (statusChanges) => statusChanges.length === 0 ? ["Pending: (clean)"] : ["Pending:", ...statusChanges.map((c) => ` ${c.status} ${c.path}`)];
37657
- nextStatusLine = (next) => next === null ? "Next: (nothing would land)" : `Next: → ${next.target}`;
37658
- definedFields = (entries) => {
37659
- const result = {};
37660
- for (const [key, value] of entries) if (value !== void 0) result[key] = value;
37661
- return result;
37662
- };
37663
- beatHeaderLines = (document) => {
37664
- const optional = definedFields([
37665
- ["Label", document.label],
37666
- ["Model", document.model],
37667
- ["Memory", document.memory],
37668
- ["File", document.file],
37669
- ["Mode", document.mode]
37670
- ]);
37671
- return [
37672
- `State: ${document.state}`,
37673
- `Awaits: ${document.actor}`,
37674
- `Workflow: ${document.workflow}`,
37675
- ...Object.entries(optional).map(([key, value]) => `${key}: ${value}`),
37676
- ...costStatusLines(document.cost ?? 0, document.costByModel ?? []),
37677
- ...pendingStatusLines(document.changes),
37678
- nextStatusLine(document.next)
37679
- ];
37680
- };
37681
- SCRIPT_INSTRUCTION = "Run this script:";
37682
- CAPTURE_INSTRUCTION = "The edit is already made — run `gtd land` to land it.";
37683
- DEMAND_BRIEFING = {
37684
- capture: {
37685
- instructionLine: CAPTURE_INSTRUCTION,
37686
- suppressHeader: false
37687
- },
37688
- message: {
37689
- instructionLine: void 0,
37690
- suppressHeader: false
37691
- },
37692
- script: {
37693
- instructionLine: SCRIPT_INSTRUCTION,
37694
- suppressHeader: false
37695
- },
37696
- stalled: {
37697
- instructionLine: void 0,
37698
- suppressHeader: false
37699
- },
37700
- prompt: {
37701
- instructionLine: void 0,
37702
- suppressHeader: true
37703
- }
37704
- };
37705
- selfValidateInstruction = (command, file) => `\nBefore finishing your turn, run \`${command}\` — it checks ${file} — and fix every violation it reports until it exits cleanly. Do not finish while it still reports violations.\n`;
37706
- renderBeatPlain = (document, selfValidateCommand) => {
37707
- const content = document.content.endsWith("\n") ? document.content : document.content + "\n";
37708
- const briefing = DEMAND_BRIEFING[document.kind];
37709
- if (briefing.suppressHeader) return selfValidateCommand !== void 0 && document.file !== void 0 ? content + selfValidateInstruction(selfValidateCommand, document.file) : content;
37710
- const body = beatHeaderLines(document).join("\n") + "\n\n" + content;
37711
- return briefing.instructionLine !== void 0 ? `${briefing.instructionLine}\n` + body : body;
37712
- };
37713
- }));
37639
+ /**
37640
+ * True when the per-model breakdown adds information beyond the `Cost:`
37641
+ * total: more than one model, or a single model that carries an actual
37642
+ * `--model` tag (a lone `unspecified` bucket just restates the total, so
37643
+ * it's suppressed).
37644
+ */
37645
+ const breakdownIsInformative = (byModel) => byModel.length > 1 || byModel.length === 1 && byModel[0].model !== "unspecified";
37646
+ const costStatusLines = (cost, byModel) => {
37647
+ if (cost <= 0) return [];
37648
+ const lines = [`Cost: ${cost}`];
37649
+ if (breakdownIsInformative(byModel)) for (const m of byModel) lines.push(` ${m.model}: ${m.cost}`);
37650
+ return lines;
37651
+ };
37652
+ const pendingStatusLines = (statusChanges) => statusChanges.length === 0 ? ["Pending: (clean)"] : ["Pending:", ...statusChanges.map((c) => ` ${c.status} ${c.path}`)];
37653
+ const nextStatusLine = (next) => next === null ? "Next: (nothing would land)" : `Next: → ${next.target}`;
37654
+ /** Builds `{[key]: value}` for each entry whose value isn't `undefined` — the shared "omit absent optional fields" shape the header uses. */
37655
+ const definedFields = (entries) => {
37656
+ const result = {};
37657
+ for (const [key, value] of entries) if (value !== void 0) result[key] = value;
37658
+ return result;
37659
+ };
37660
+ /**
37661
+ * The header block's own lines — `State:`/`Awaits:`/`Workflow:`/optional `Label:`/
37662
+ * `Model:`/`Memory:`/`File:`/`Mode:`/`Cost:`(+breakdown)/`Pending:`/`Next:`,
37663
+ * in that order. Used by `renderBeatPlain`'s header-showing kinds below.
37664
+ */
37665
+ const beatHeaderLines = (document) => {
37666
+ const optional = definedFields([
37667
+ ["Label", document.label],
37668
+ ["Model", document.model],
37669
+ ["Memory", document.memory],
37670
+ ["File", document.file],
37671
+ ["Mode", document.mode]
37672
+ ]);
37673
+ return [
37674
+ `State: ${document.state}`,
37675
+ `Awaits: ${document.actor}`,
37676
+ `Workflow: ${document.workflow}`,
37677
+ ...Object.entries(optional).map(([key, value]) => `${key}: ${value}`),
37678
+ ...costStatusLines(document.cost ?? 0, document.costByModel ?? []),
37679
+ ...pendingStatusLines(document.changes),
37680
+ nextStatusLine(document.next)
37681
+ ];
37682
+ };
37683
+ /**
37684
+ * One entry per `BeatKind`, typed as `Record<BeatKind, DemandBriefing>` so
37685
+ * the object literal itself is exhaustive: adding or removing a `BeatKind`
37686
+ * member without updating this map fails `tsc`, the same guarantee
37687
+ * `dispatchFieldsOf`'s switch (`BeatDocument.ts`) gives the JSON side. Exported
37688
+ * so the one cross-cutting exhaustiveness test (`BeatDocument.test.ts`) can
37689
+ * assert against it directly, rather than re-deriving it from
37690
+ * `renderBeatPlain`'s output. Replaces the old `renderBeatPlain`'s hand-written
37691
+ * `if (kind === "script") … if (kind === "capture") …` pair.
37692
+ */
37693
+ const DEMAND_BRIEFING = {
37694
+ capture: {
37695
+ instructionLine: "The edit is already made — run `gtd land` to land it.",
37696
+ suppressHeader: false
37697
+ },
37698
+ message: {
37699
+ instructionLine: void 0,
37700
+ suppressHeader: false
37701
+ },
37702
+ script: {
37703
+ instructionLine: "Run this script:",
37704
+ suppressHeader: false
37705
+ },
37706
+ stalled: {
37707
+ instructionLine: void 0,
37708
+ suppressHeader: false
37709
+ },
37710
+ prompt: {
37711
+ instructionLine: void 0,
37712
+ suppressHeader: true
37713
+ }
37714
+ };
37715
+ /**
37716
+ * The self-validation instruction appended to a `prompt` beat's plain output
37717
+ * only — a structured (`--json`) driver runs the `validate` field
37718
+ * itself instead. Advisory either way: `gtd land` refuses a turn whose
37719
+ * steering file is invalid regardless of whether this was followed.
37720
+ */
37721
+ const selfValidateInstruction = (command, file) => `\nBefore finishing your turn, run \`${command}\` — it checks ${file} — and fix every violation it reports until it exits cleanly. Do not finish while it still reports violations.\n`;
37722
+ /**
37723
+ * `gtd next`'s plain-text encoding, driven by `DEMAND_BRIEFING`. At
37724
+ * `kind === "prompt"` the status header is suppressed and only `content`
37725
+ * plus the self-validation instruction is emitted — those bytes ARE the
37726
+ * agent's input, so gtd's own bookkeeping (state name, edges, pending
37727
+ * changes) must not be prefixed onto it. Every other kind gets the header
37728
+ * plus its instruction line, since its plain output is read by a human or
37729
+ * driver, never an agent. `selfValidateCommand` is already resolved; this
37730
+ * function never resolves it itself.
37731
+ */
37732
+ const renderBeatPlain = (document, selfValidateCommand) => {
37733
+ const content = document.content.endsWith("\n") ? document.content : document.content + "\n";
37734
+ const briefing = DEMAND_BRIEFING[document.kind];
37735
+ if (briefing.suppressHeader) return selfValidateCommand !== void 0 && document.file !== void 0 ? content + selfValidateInstruction(selfValidateCommand, document.file) : content;
37736
+ const body = beatHeaderLines(document).join("\n") + "\n\n" + content;
37737
+ return briefing.instructionLine !== void 0 ? `${briefing.instructionLine}\n` + body : body;
37738
+ };
37714
37739
  //#endregion
37715
37740
  //#region src/wire/Land.ts
37716
- var landFields, renderLandJson, FMT_NOOP, FMT_LAND_PROSE, noopText, landProseText, renderLandPlain;
37717
- var init_Land = __esmMin((() => {
37718
- init_constants();
37719
- landFields = (input) => ({
37720
- schema: 1,
37721
- script: input.script,
37722
- settled: input.settled,
37723
- idle: input.idle,
37724
- state: input.state,
37725
- subject: input.subject,
37726
- cost: input.cost,
37727
- model: input.model
37728
- });
37729
- renderLandJson = (fields) => JSON.stringify(fields) + "\n";
37730
- FMT_NOOP = "nothing to do at \"%s\"\n";
37731
- FMT_LAND_PROSE = "commit everything with this message: %s\n";
37732
- noopText = (state) => renderFormat(FMT_NOOP, state);
37733
- landProseText = (subject) => renderFormat(FMT_LAND_PROSE, subject);
37734
- renderLandPlain = (fields, command = "land") => fields.subject !== null ? `${landProseText(fields.subject).trimEnd()}\n(run \`gtd ${command} --json=script | sh\` to get the landing script)\n` : noopText(fields.state);
37735
- }));
37741
+ /** Assembles one `gtd land` result's fields in `LandFields`' declared order — the ONLY place `gtd land --json`'s wire shape is built. */
37742
+ const landFields = (input) => ({
37743
+ schema: 1,
37744
+ script: input.script,
37745
+ settled: input.settled,
37746
+ idle: input.idle,
37747
+ state: input.state,
37748
+ subject: input.subject,
37749
+ cost: input.cost,
37750
+ model: input.model
37751
+ });
37752
+ const renderLandJson = (fields) => JSON.stringify(fields) + "\n";
37753
+ const FMT_NOOP = "nothing to do at \"%s\"\n";
37754
+ const FMT_LAND_PROSE = "commit everything with this message: %s\n";
37755
+ /** `nothing to do at "<state>"` — a no-op step's plain-text line, and the text a print-only script's own `printf` carries. */
37756
+ const noopText = (state) => renderFormat(FMT_NOOP, state);
37757
+ /** `commit everything with this message: <subject>` — plain `gtd land`'s own stdout at a pending diff (no script); `--json` keeps emitting the script itself, unaffected. */
37758
+ const landProseText = (subject) => renderFormat(FMT_LAND_PROSE, subject);
37759
+ /**
37760
+ * `gtd land`'s plain-text encoding — names the commit subject at a real
37761
+ * landing and points at `--json=script`, since the script itself is
37762
+ * unreachable from plain output; prints the existing no-op note otherwise.
37763
+ * `command` names the invocation to point at — `"land"` by default, but
37764
+ * `gtd judge answer` (the only other caller of `planLanding`/`landFields`)
37765
+ * passes its own two-word name so the hint stays runnable verbatim.
37766
+ */
37767
+ const renderLandPlain = (fields, command = "land") => fields.subject !== null ? `${landProseText(fields.subject).trimEnd()}\n(run \`gtd ${command} --json=script | sh\` to get the landing script)\n` : noopText(fields.state);
37736
37768
  //#endregion
37737
37769
  //#region src/wire/Judge.ts
37738
- var judgeJson;
37739
- var init_Judge = __esmMin((() => {
37740
- init_constants();
37741
- judgeJson = (state, questions) => JSON.stringify({
37742
- schema: 1,
37743
- state,
37744
- questions
37745
- });
37746
- }));
37770
+ const judgeJson = (state, questions) => JSON.stringify({
37771
+ schema: 1,
37772
+ state,
37773
+ questions
37774
+ });
37747
37775
  //#endregion
37748
37776
  //#region src/wire/Select.ts
37749
- var toSelection, NOT_FOUND, ARRAY_INDEX, resolveSegment, selectPath;
37750
- var init_Select = __esmMin((() => {
37751
- toSelection = (value) => {
37752
- if (Array.isArray(value)) return {
37753
- kind: "value",
37754
- text: value.map((entry) => JSON.stringify(entry)).join("\n")
37755
- };
37756
- if (typeof value === "object") return {
37757
- kind: "value",
37758
- text: JSON.stringify(value)
37759
- };
37760
- return {
37761
- kind: "value",
37762
- text: String(value)
37763
- };
37777
+ /**
37778
+ * Renders a fully-walked leaf value: scalars/booleans stringify directly,
37779
+ * arrays become one `JSON.stringify` per entry, newline-joined.
37780
+ * `selectPath`'s loop already returns `absent` for `undefined`/`null` after
37781
+ * every segment (including the last), so neither ever reaches here.
37782
+ */
37783
+ const toSelection = (value) => {
37784
+ if (Array.isArray(value)) return {
37785
+ kind: "value",
37786
+ text: value.map((entry) => JSON.stringify(entry)).join("\n")
37764
37787
  };
37765
- NOT_FOUND = Symbol("not-found");
37766
- ARRAY_INDEX = /^\d+$/;
37767
- resolveSegment = (current, segment) => {
37768
- if (Array.isArray(current)) return ARRAY_INDEX.test(segment) && Number(segment) < current.length ? current[Number(segment)] : NOT_FOUND;
37769
- if (typeof current === "object" && current !== null) {
37770
- const record = current;
37771
- return Object.prototype.hasOwnProperty.call(record, segment) ? record[segment] : NOT_FOUND;
37772
- }
37773
- return NOT_FOUND;
37788
+ if (typeof value === "object") return {
37789
+ kind: "value",
37790
+ text: JSON.stringify(value)
37774
37791
  };
37775
- selectPath = (fields, path) => {
37776
- try {
37777
- let current = fields;
37778
- for (const segment of path.split(".")) {
37779
- current = resolveSegment(current, segment);
37780
- if (current === NOT_FOUND) return {
37781
- kind: "unknown",
37782
- path
37783
- };
37784
- if (current === void 0 || current === null) return { kind: "absent" };
37785
- }
37786
- return toSelection(current);
37787
- } catch {
37788
- return {
37792
+ return {
37793
+ kind: "value",
37794
+ text: String(value)
37795
+ };
37796
+ };
37797
+ /** A segment that resolves to no key at all (missing from an object, out-of-range or non-numeric against an array) — distinct from a present key holding `undefined`. */
37798
+ const NOT_FOUND = Symbol("not-found");
37799
+ /** An all-digits segment (`"0"`, `"12"`) — the only shape an array segment resolves against. */
37800
+ const ARRAY_INDEX = /^\d+$/;
37801
+ /**
37802
+ * One step of the walk: looks `segment` up on `current`, or reports
37803
+ * `NOT_FOUND` for a key that was never there (a primitive has no keys).
37804
+ * Against an array, only an all-digits segment resolves — an in-range index
37805
+ * (`skills.0` reads the first declared skill), never a non-index own key
37806
+ * (`length`/`map`/every other inherited array member stays `NOT_FOUND`).
37807
+ * Against an object, presence is an OWN-property test
37808
+ * (`Object.prototype.hasOwnProperty`), never the `in` operator — `in` walks
37809
+ * the prototype chain, so it would resolve inherited members
37810
+ * (`constructor`, `toString`, `hasOwnProperty`, `valueOf`, ...) as real
37811
+ * document fields.
37812
+ */
37813
+ const resolveSegment = (current, segment) => {
37814
+ if (Array.isArray(current)) return ARRAY_INDEX.test(segment) && Number(segment) < current.length ? current[Number(segment)] : NOT_FOUND;
37815
+ if (typeof current === "object" && current !== null) {
37816
+ const record = current;
37817
+ return Object.prototype.hasOwnProperty.call(record, segment) ? record[segment] : NOT_FOUND;
37818
+ }
37819
+ return NOT_FOUND;
37820
+ };
37821
+ /**
37822
+ * Walks `fields` by dotted key path. Never throws — any unwalkable shape
37823
+ * (a primitive mid-path, an out-of-range or non-numeric array segment)
37824
+ * degrades to `unknown`, and a present-but-`undefined` value anywhere along
37825
+ * the path short-circuits the whole remaining path to `absent` rather than
37826
+ * reporting a key that was never actually missing.
37827
+ */
37828
+ const selectPath = (fields, path) => {
37829
+ try {
37830
+ let current = fields;
37831
+ for (const segment of path.split(".")) {
37832
+ current = resolveSegment(current, segment);
37833
+ if (current === NOT_FOUND) return {
37789
37834
  kind: "unknown",
37790
37835
  path
37791
37836
  };
37837
+ if (current === void 0 || current === null) return { kind: "absent" };
37792
37838
  }
37793
- };
37794
- }));
37839
+ return toSelection(current);
37840
+ } catch {
37841
+ return {
37842
+ kind: "unknown",
37843
+ path
37844
+ };
37845
+ }
37846
+ };
37795
37847
  //#endregion
37796
37848
  //#region src/wire/Sessions.ts
37797
- var GTD_SESSION_NAMESPACE, toBytes, toUuidString, uuidv5, resolveSession;
37798
- var init_Sessions = __esmMin((() => {
37799
- GTD_SESSION_NAMESPACE = "ca4be249-805e-41c1-8b6a-75a11c011e25";
37800
- toBytes = (uuid) => Buffer.from(uuid.replace(/-/g, ""), "hex");
37801
- toUuidString = (bytes) => {
37802
- const hex = bytes.toString("hex");
37803
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
37804
- };
37805
- uuidv5 = (namespace, name) => {
37806
- const bytes = createHash("sha1").update(toBytes(namespace)).update(name, "utf8").digest().subarray(0, 16);
37807
- bytes[6] = bytes[6] & 15 | 80;
37808
- bytes[8] = bytes[8] & 63 | 128;
37809
- return toUuidString(bytes);
37810
- };
37811
- resolveSession = (memoryKey, resume, mint = randomUUID) => memoryKey === void 0 ? {
37812
- sessionId: mint(),
37813
- resume: false
37814
- } : {
37815
- sessionId: uuidv5(GTD_SESSION_NAMESPACE, memoryKey),
37816
- resume
37817
- };
37818
- }));
37849
+ /** Fixed forever — every derived session id descends from this one constant. */
37850
+ const GTD_SESSION_NAMESPACE = "ca4be249-805e-41c1-8b6a-75a11c011e25";
37851
+ const toBytes = (uuid) => Buffer.from(uuid.replace(/-/g, ""), "hex");
37852
+ const toUuidString = (bytes) => {
37853
+ const hex = bytes.toString("hex");
37854
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
37855
+ };
37856
+ /**
37857
+ * UUIDv5 (RFC 4122 §4.3), hand-rolled over `node:crypto`'s SHA-1 rather than
37858
+ * a dependency — correctness is checked against the RFC's own DNS-namespace
37859
+ * test vector in `Sessions.test.ts`.
37860
+ */
37861
+ const uuidv5 = (namespace, name) => {
37862
+ const bytes = createHash("sha1").update(toBytes(namespace)).update(name, "utf8").digest().subarray(0, 16);
37863
+ bytes[6] = bytes[6] & 15 | 80;
37864
+ bytes[8] = bytes[8] & 63 | 128;
37865
+ return toUuidString(bytes);
37866
+ };
37867
+ /**
37868
+ * Resolves a `prompt` rest's session id: with no `memoryKey` there's no
37869
+ * history to derive a stable id from, so it mints an ephemeral one and forces
37870
+ * `resume: false`; otherwise the id is deterministic (`uuidv5`) and `resume`
37871
+ * is passed through as given.
37872
+ */
37873
+ const resolveSession = (memoryKey, resume, mint = randomUUID) => memoryKey === void 0 ? {
37874
+ sessionId: mint(),
37875
+ resume: false
37876
+ } : {
37877
+ sessionId: uuidv5(GTD_SESSION_NAMESPACE, memoryKey),
37878
+ resume
37879
+ };
37819
37880
  //#endregion
37820
37881
  //#region src/wire/Glob.ts
37821
- var ESCAPE_RE, compile, globMatches;
37822
- var init_Glob = __esmMin((() => {
37823
- ESCAPE_RE = /[.+^${}()|[\]\\?]/g;
37824
- compile = (glob) => {
37825
- let pattern = "^";
37826
- let i = 0;
37827
- while (i < glob.length) {
37828
- const char = glob[i];
37829
- if (char !== "*") {
37830
- pattern += char.replace(ESCAPE_RE, "\\$&");
37831
- i += 1;
37832
- } else if (glob[i + 1] !== "*") {
37833
- pattern += "[^/]*";
37834
- i += 1;
37835
- } else if (glob[i + 2] === "/") {
37836
- pattern += "(?:.*/)?";
37837
- i += 3;
37838
- } else {
37839
- pattern += ".*";
37840
- i += 2;
37841
- }
37842
- }
37843
- return new RegExp(`${pattern}$`, "s");
37844
- };
37845
- globMatches = (path, glob) => compile(glob).test(path);
37846
- }));
37847
- //#endregion
37848
- //#region src/wire/Slices.ts
37849
- var slicesDocument, renderSlicesJson;
37850
- var init_Slices = __esmMin((() => {
37851
- init_constants();
37852
- slicesDocument = (slices) => ({
37853
- schema: 1,
37854
- slices: slices.map(({ name, base, tip, closed }) => ({
37855
- name,
37856
- base,
37857
- tip,
37858
- closed
37859
- }))
37860
- });
37861
- renderSlicesJson = (document) => JSON.stringify(document) + "\n";
37862
- }));
37863
- //#endregion
37864
- //#region src/wire/ContextLimit.ts
37865
- var DEFAULT_CONTEXT_LIMIT, DEFAULT_WINDOW, LONG_WINDOW, contextWindowFor, parseContextWindow, CONTEXT_STOP_FILE_ENV, contextShapeFault, contextLimitFault, record$1, count, usageTokens, transcriptTokens, CONTEXT_LIMIT_MARKER, percent, contextHook, hookOutput;
37866
- var init_ContextLimit = __esmMin((() => {
37867
- DEFAULT_CONTEXT_LIMIT = {
37868
- soft: .6,
37869
- hard: .7
37870
- };
37871
- DEFAULT_WINDOW = 2e5;
37872
- LONG_WINDOW = 1e6;
37873
- contextWindowFor = (model) => model !== void 0 && /\[1m\]/i.test(model) ? LONG_WINDOW : DEFAULT_WINDOW;
37874
- parseContextWindow = (raw) => {
37875
- const n = Number(raw);
37876
- return raw !== void 0 && raw.trim() !== "" && Number.isInteger(n) && n > 0 ? n : void 0;
37877
- };
37878
- CONTEXT_STOP_FILE_ENV = "GTD_LOOP_CONTEXT_STOP_FILE";
37879
- contextShapeFault = (limit) => {
37880
- if (typeof limit !== "object" || limit === null || Array.isArray(limit)) return `context must be an object { soft, hard }, got ${JSON.stringify(limit)}`;
37881
- const unknown = Object.keys(limit).filter((key) => key !== "soft" && key !== "hard");
37882
- if (unknown.length > 0) return `unknown key(s) ${unknown.join(", ")} in context`;
37883
- const shares = limit;
37884
- for (const key of ["soft", "hard"]) {
37885
- const value = shares[key];
37886
- if (value === void 0) continue;
37887
- if (typeof value !== "number" || !(value > 0 && value <= 1)) return `context.${key} must be a share of the context window in (0, 1], got ${JSON.stringify(value)}`;
37888
- }
37889
- };
37890
- contextLimitFault = (limit) => {
37891
- const shape = contextShapeFault(limit);
37892
- if (shape !== void 0) return shape;
37893
- const shares = limit;
37894
- const soft = shares.soft ?? DEFAULT_CONTEXT_LIMIT.soft;
37895
- const hard = shares.hard ?? DEFAULT_CONTEXT_LIMIT.hard;
37896
- return soft < hard ? void 0 : `context.soft (${soft}) must be below context.hard (${hard})`;
37897
- };
37898
- record$1 = (value) => typeof value === "object" && value !== null ? value : void 0;
37899
- count = (value) => typeof value === "number" && value > 0 ? value : 0;
37900
- usageTokens = (usage) => {
37901
- const u = record$1(usage);
37902
- if (u === void 0) return void 0;
37903
- return count(u["input_tokens"]) + count(u["cache_read_input_tokens"]) + count(u["cache_creation_input_tokens"]);
37904
- };
37905
- transcriptTokens = (transcript) => {
37906
- const lines = transcript.split("\n");
37907
- for (let i = lines.length - 1; i >= 0; i--) {
37908
- const line = lines[i].trim();
37909
- if (line === "") continue;
37910
- let entry;
37911
- try {
37912
- entry = JSON.parse(line);
37913
- } catch {
37914
- continue;
37915
- }
37916
- if (record$1(entry)?.["type"] !== "assistant") continue;
37917
- const tokens = usageTokens(record$1(record$1(entry)?.["message"])?.["usage"]);
37918
- if (tokens !== void 0) return tokens;
37882
+ const ESCAPE_RE = /[.+^${}()|[\]\\?]/g;
37883
+ const compile = (glob) => {
37884
+ let pattern = "^";
37885
+ let i = 0;
37886
+ while (i < glob.length) {
37887
+ const char = glob[i];
37888
+ if (char !== "*") {
37889
+ pattern += char.replace(ESCAPE_RE, "\\$&");
37890
+ i += 1;
37891
+ } else if (glob[i + 1] !== "*") {
37892
+ pattern += "[^/]*";
37893
+ i += 1;
37894
+ } else if (glob[i + 2] === "/") {
37895
+ pattern += "(?:.*/)?";
37896
+ i += 3;
37897
+ } else {
37898
+ pattern += ".*";
37899
+ i += 2;
37919
37900
  }
37920
- };
37921
- CONTEXT_LIMIT_MARKER = "gtd: context limit";
37922
- percent = (share) => `${Math.round(share * 100)}%`;
37923
- contextHook = (input) => {
37924
- const { event, tokens, window, limit } = input;
37925
- if (event === "PreCompact") return {
37926
- kind: "hard",
37927
- stopReason: `${CONTEXT_LIMIT_MARKER}: the context is about to be compacted — the run is too big for one turn`
37928
- };
37929
- if (tokens === void 0) return void 0;
37930
- const used = `${tokens} of ${window} tokens`;
37931
- if (tokens >= limit.hard * window) return {
37932
- kind: "hard",
37933
- stopReason: `${CONTEXT_LIMIT_MARKER}: ${used}, past the ${percent(limit.hard)} hard limit`
37934
- };
37935
- if (tokens >= limit.soft * window) return {
37936
- kind: "soft",
37937
- 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.`
37938
- };
37939
- };
37940
- hookOutput = (decision) => decision.kind === "soft" ? {
37941
- decision: "block",
37942
- reason: decision.reason
37943
- } : {
37944
- continue: false,
37945
- stopReason: decision.stopReason
37946
- };
37947
- }));
37948
- //#endregion
37949
- //#region src/wire/index.ts
37950
- var init_wire = __esmMin((() => {
37951
- init_constants();
37952
- init_ExitCodes();
37953
- init_types();
37954
- init_Demand();
37955
- init_BeatStatus();
37956
- init_BeatDocument();
37957
- init_BeatPlain();
37958
- init_Land();
37959
- init_Judge();
37960
- init_Select();
37961
- init_Sessions();
37962
- init_Glob();
37963
- init_Slices();
37964
- init_ContextLimit();
37965
- }));
37901
+ }
37902
+ return new RegExp(`${pattern}$`, "s");
37903
+ };
37904
+ const globMatches = (path, glob) => compile(glob).test(path);
37966
37905
  //#endregion
37967
37906
  //#region src/replay/RefName.ts
37968
- init_wire();
37969
37907
  const FORBIDDEN = /[\u0000- \u007f~^:?*[\\]/;
37970
37908
  /** Why `name` cannot be (part of) a git ref name, or `undefined` when it can. */
37971
37909
  const refNameFault = (name) => {
@@ -37981,32 +37919,6 @@ const refNameFault = (name) => {
37981
37919
  }
37982
37920
  };
37983
37921
  //#endregion
37984
- //#region src/replay/Slices.ts
37985
- const IMPLICIT_SLICE = "main";
37986
- /** Same shape as mark()'s name rule; kept local so @pmelab/gtd/flows publishes no regex. */
37987
- const SLICE_NAME = /^[a-z][a-z0-9-]*$/;
37988
- /**
37989
- * Folds boundaries into ranges: each kept span's base is the previous kept
37990
- * span's `leftAt`, so a gap before a span belongs to it, and the last span
37991
- * runs to the episode tip. A closed span that landed nothing is dropped.
37992
- */
37993
- const sliceRanges = (spans, episode) => {
37994
- if (spans.some((s) => s.enteredAt === "" || s.leftAt === "")) throw new Error("sliceRanges: a pending turn's \"\" hash is not a commit");
37995
- const kept = spans.filter((s) => s.leftAt === void 0 || s.enteredAt !== s.leftAt);
37996
- if (kept.length === 0) return [{
37997
- name: IMPLICIT_SLICE,
37998
- base: episode.base,
37999
- tip: episode.tip,
38000
- open: true
38001
- }];
38002
- return kept.map((s, i) => ({
38003
- name: s.name,
38004
- base: i === 0 ? episode.base : kept[i - 1].leftAt ?? episode.base,
38005
- tip: i === kept.length - 1 ? episode.tip : s.leftAt ?? episode.tip,
38006
- open: i === kept.length - 1
38007
- }));
38008
- };
38009
- //#endregion
38010
37922
  //#region src/replay/Tree.ts
38011
37923
  const treeFromRecord = (files) => ({
38012
37924
  paths: () => Object.keys(files).sort(),
@@ -38313,15 +38225,6 @@ const budgeted = (evidence, budgetBytes) => {
38313
38225
  truncated
38314
38226
  };
38315
38227
  };
38316
- const scopeLabel = (chain) => {
38317
- const path = chain.flatMap((o) => o.name === void 0 ? [] : [o.name]).join(".");
38318
- if (chain.at(-1)?.name !== void 0) return `scope "${path}"`;
38319
- return path === "" ? "an unnamed scope" : `an unnamed scope inside "${path}"`;
38320
- };
38321
- const inheritedContext = (layers) => layers.reduce((merged, layer) => layer === void 0 ? merged : {
38322
- ...merged,
38323
- ...Object.fromEntries(Object.entries(layer).filter(([, value]) => value !== void 0))
38324
- }, void 0);
38325
38228
  const sameSkills = (a, b) => (a?.length ?? 0) === (b?.length ?? 0) && (a ?? []).every((skill, i) => skill === b?.[i]);
38326
38229
  const samePersona = (a, b) => a.model === b.model && a.system === b.system && sameSkills(a.skills, b.skills) && sameAccess(a.access, b.access);
38327
38230
  /**
@@ -38341,7 +38244,6 @@ const KNOWN_OPTIONS = {
38341
38244
  ...STEERING_OPTIONS,
38342
38245
  "model",
38343
38246
  "system",
38344
- "context",
38345
38247
  "allowEmpty",
38346
38248
  "keep"
38347
38249
  ]),
@@ -38386,7 +38288,6 @@ const replay = async (input) => {
38386
38288
  const keptTips = /* @__PURE__ */ new Map();
38387
38289
  let landed;
38388
38290
  const marks = [];
38389
- const slices = [];
38390
38291
  let outcome;
38391
38292
  let signal;
38392
38293
  const settled = new Promise((resolve) => {
@@ -38456,13 +38357,11 @@ const replay = async (input) => {
38456
38357
  if (s.model !== void 0) persona.model = s.model;
38457
38358
  if (s.system !== void 0) persona.system = s.system;
38458
38359
  }
38459
- const context = inheritedContext([...scopes.map((s) => s.context), request.options.context]);
38460
38360
  return {
38461
38361
  ...request,
38462
38362
  options: {
38463
38363
  ...persona,
38464
- ...request.options,
38465
- ...context === void 0 ? {} : { context }
38364
+ ...request.options
38466
38365
  }
38467
38366
  };
38468
38367
  }
@@ -38596,31 +38495,14 @@ const replay = async (input) => {
38596
38495
  rest: step,
38597
38496
  trace,
38598
38497
  landed,
38599
- marks,
38600
- slices: slices.map((s) => ({ ...s }))
38498
+ marks
38601
38499
  });
38602
38500
  return landPending(step, input.pending);
38603
38501
  };
38604
- const scopeContextFault = () => {
38605
- for (const [i, s] of scopes.entries()) {
38606
- const fault = s.context === void 0 ? void 0 : contextShapeFault(s.context);
38607
- if (fault !== void 0) return `the context of ${scopeLabel(scopes.slice(0, i + 1))}: ${fault}`;
38608
- }
38609
- };
38610
- const resolvedContextFault = (request) => {
38611
- const own = request.options.context;
38612
- const layered = scopeContextFault() ?? (own === void 0 ? void 0 : contextShapeFault(own));
38613
- if (layered !== void 0) return layered;
38614
- const resolved = resolve(request);
38615
- const context = resolved.kind === "agent" ? resolved.options.context : void 0;
38616
- return context === void 0 ? void 0 : contextLimitFault(context);
38617
- };
38618
38502
  const requestFault = (request) => {
38619
38503
  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)`;
38620
38504
  if (request.kind !== "agent") return void 0;
38621
38505
  const name = scoped(request.name);
38622
- const contextFault = resolvedContextFault(request);
38623
- if (contextFault !== void 0) return `gtd: step "${name}": ${contextFault}`;
38624
38506
  if (request.options.keep === true) {
38625
38507
  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`;
38626
38508
  const refFault = name.includes("/") ? "it contains \"/\"" : refNameFault(name);
@@ -38652,8 +38534,7 @@ const replay = async (input) => {
38652
38534
  via,
38653
38535
  trace,
38654
38536
  landed,
38655
- marks,
38656
- slices: slices.map((s) => ({ ...s }))
38537
+ marks
38657
38538
  };
38658
38539
  const changesSince = (hash) => {
38659
38540
  const tree = treeAt(hash);
@@ -38678,31 +38559,6 @@ const replay = async (input) => {
38678
38559
  scopes.pop();
38679
38560
  scopeCallIds.pop();
38680
38561
  },
38681
- enterSlice: (name) => {
38682
- if (outcome !== void 0) return;
38683
- const fail = (message) => {
38684
- finish({
38685
- kind: "failed",
38686
- message
38687
- });
38688
- throw new Stop();
38689
- };
38690
- if (typeof name !== "string" || !SLICE_NAME.test(name)) fail(`gtd: slice "${name}": name must match ${SLICE_NAME}`);
38691
- if (name === "main") fail(`gtd: slice "${IMPLICIT_SLICE}" is reserved for the implicit slice`);
38692
- const open = slices.find((s) => s.leftAt === void 0);
38693
- if (open !== void 0) fail(`gtd: slice "${name}" is entered inside slice "${open.name}" — slices do not nest`);
38694
- if (slices.some((s) => s.name === name)) fail(`gtd: slice "${name}" is entered twice in this episode — a loop must sit inside its slice`);
38695
- slices.push({
38696
- name,
38697
- enteredAt: position.hash,
38698
- leftAt: void 0
38699
- });
38700
- },
38701
- leaveSlice: () => {
38702
- if (outcome !== void 0) return;
38703
- const open = slices.find((s) => s.leftAt === void 0);
38704
- if (open !== void 0) open.leftAt = position.hash;
38705
- },
38706
38562
  read: (path) => position.tree.read(path),
38707
38563
  glob: (pattern) => position.tree.paths().filter((path) => globMatches(path, pattern)),
38708
38564
  changes: () => changesBetween(previousPosition.tree, position.tree),
@@ -39781,7 +39637,6 @@ const planStep = (snapshot, opts = {}) => {
39781
39637
  };
39782
39638
  //#endregion
39783
39639
  //#region src/step/accessRefusal.ts
39784
- init_wire();
39785
39640
  /**
39786
39641
  * The landing refusal for changes outside `write`, or `undefined` when every
39787
39642
  * path is allowed. `null` is unrestricted. The message is the fix prompt: gtd
@@ -39881,14 +39736,12 @@ const resolveBase = (commandLabel, value) => gen(function* () {
39881
39736
  });
39882
39737
  //#endregion
39883
39738
  //#region src/step/strayPaths.ts
39884
- init_wire();
39885
39739
  /** Globs use the `access.write` dialect (`globMatches`). */
39886
39740
  const strayPaths = (touched, allow) => touched.filter((path) => !allow.some((glob) => globMatches(path, glob))).sort();
39887
39741
  //#endregion
39888
39742
  //#region src/edge/Edge.ts
39889
39743
  init_esm();
39890
39744
  init_steering();
39891
- init_wire();
39892
39745
  const EMPTY_TREE$1 = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
39893
39746
  const ACTORS = /* @__PURE__ */ new Set([
39894
39747
  "agent",
@@ -40241,10 +40094,6 @@ const commonOf = (step) => {
40241
40094
  ...optional("mode", options.mode)
40242
40095
  };
40243
40096
  };
40244
- const stepContext = (context) => context === void 0 ? void 0 : {
40245
- ...optional("soft", context.soft),
40246
- ...optional("hard", context.hard)
40247
- };
40248
40097
  const promptDef = (common, request, skills, access) => ({
40249
40098
  ...common,
40250
40099
  kind: "prompt",
@@ -40253,7 +40102,6 @@ const promptDef = (common, request, skills, access) => ({
40253
40102
  ...optional("system", request.options.system),
40254
40103
  ...optional("allowEmpty", request.options.allowEmpty),
40255
40104
  ...optional("keep", request.options.keep),
40256
- ...optional("context", stepContext(request.options.context)),
40257
40105
  ...optional("skills", skills),
40258
40106
  ...optional("access", access)
40259
40107
  });
@@ -40317,8 +40165,7 @@ const hintsOf = (def) => ({
40317
40165
  ...optional("system", def.system),
40318
40166
  ...optional("mode", def.mode),
40319
40167
  ...optional("skills", def.skills),
40320
- ...optional("access", def.access),
40321
- ...optional("context", def.context)
40168
+ ...optional("access", def.access)
40322
40169
  });
40323
40170
  const normalizeStatus = (raw) => raw === "A" ? "A" : raw === "D" ? "D" : "M";
40324
40171
  /** Per-model token totals, highest-cost first (ties broken by model name). */
@@ -40395,10 +40242,6 @@ const pendingChanges = (git, withIndex) => gen(function* () {
40395
40242
  const seen = new Set(worktree.map((e) => e.path));
40396
40243
  return [...worktree, ...(yield* git.stagedPaths()).filter((e) => !seen.has(e.path))];
40397
40244
  });
40398
- const sliceRangesOf = (slices, run) => sliceRanges(slices, {
40399
- base: run.startParentHash,
40400
- tip: run.trace.at(-1)?.hash ?? run.startParentHash
40401
- });
40402
40245
  /** Resolve the rest at `ref`, or at HEAD when `ref` is `undefined`. */
40403
40246
  const restAt = (ref, startOverrides = {}) => gen(function* () {
40404
40247
  const git = yield* GitService;
@@ -40442,64 +40285,6 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
40442
40285
  };
40443
40286
  });
40444
40287
  const currentRest = restAt(void 0);
40445
- /** Every slice refusal reads `<command>: refused — <reason>`; replay's own `gtd: ` lead is dropped. */
40446
- const slicesDivergence = (command, run, outcome) => {
40447
- 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";
40448
- return /* @__PURE__ */ new Error(`${command}: refused — ${reason}`);
40449
- };
40450
- /**
40451
- * The replay `gtd slices` and `gtd summary --slice` share: the slice ranges of
40452
- * `run`, plus whether HEAD closes it. Reads like `historyTrail`, never
40453
- * `restAt`: it narrates and writes nothing.
40454
- */
40455
- const replayedSlices = (config, run, how) => gen(function* () {
40456
- const workspace = yield* Workspace;
40457
- const host = yield* Host;
40458
- if (run.trace.length === 0) return yield* fail$7(/* @__PURE__ */ new Error(`${how.command}: refused — HEAD neither closes nor sits inside a process`));
40459
- const closing = run.closingHash;
40460
- let outcome;
40461
- let versionWarning;
40462
- if (closing !== void 0) {
40463
- const replayed = yield* replayFinishedProcess(config, run, closing, {
40464
- command: how.command,
40465
- configReason: how.configReason,
40466
- notEnded: (o) => slicesDivergence(how.command, run, o)
40467
- });
40468
- outcome = replayed.outcome;
40469
- versionWarning = replayed.versionWarning;
40470
- } else {
40471
- const { setup } = yield* setupFor(config, run, workspace, host);
40472
- outcome = yield* promise(() => replayFor(setup));
40473
- if (outcome.kind !== "rest") return yield* fail$7(slicesDivergence(how.command, run, outcome));
40474
- }
40475
- return {
40476
- ranges: sliceRangesOf(outcome.slices, run),
40477
- closed: closing !== void 0,
40478
- ...versionWarning !== void 0 ? { versionWarning } : {}
40479
- };
40480
- });
40481
- /**
40482
- * The slices of the process HEAD closes or sits inside, current episode only,
40483
- * stack order. Returns a version warning for a closed run.
40484
- */
40485
- const processSlices = gen(function* () {
40486
- const git = yield* GitService;
40487
- const config = yield* (yield* ConfigService).load;
40488
- const run = yield* runAtHead(git, config.workflow);
40489
- const { ranges, closed, versionWarning } = yield* replayedSlices(config, run, {
40490
- command: "gtd slices",
40491
- configReason: "HEAD's process replays under today's config; revert or stash the edit to list its slices"
40492
- });
40493
- return {
40494
- slices: ranges.map(({ name, base, tip, open }) => ({
40495
- name,
40496
- base,
40497
- tip,
40498
- closed: closed || !open
40499
- })),
40500
- ...versionWarning !== void 0 ? { versionWarning } : {}
40501
- };
40502
- });
40503
40288
  /**
40504
40289
  * Whether `gtd --workflow <name>` can start a process here: the workflow must
40505
40290
  * reach a step from the tree the opening commit would capture. That step is
@@ -40706,85 +40491,31 @@ const snapshotFromRest = (rest, verdicts) => gen(function* () {
40706
40491
  landing
40707
40492
  };
40708
40493
  });
40709
- const narrowToSlice = (run, slice) => {
40710
- if (slice === void 0) return {
40711
- trace: run.trace,
40712
- costEntries: run.costEntries
40713
- };
40714
- const from = slice.base === run.startParentHash ? -1 : run.trace.findIndex((e) => e.hash === slice.base);
40715
- const to = run.trace.findIndex((e) => e.hash === slice.tip);
40716
- const trace = run.trace.slice(from + 1, to + 1);
40717
- const hashes = new Set(trace.map((e) => e.hash));
40718
- return {
40719
- trace,
40720
- costEntries: run.costEntries.filter((c) => hashes.has(c.hash))
40721
- };
40722
- };
40723
40494
  const humanCommitsOf = (trace, entryCommit) => trace.filter((entry) => entry.actor === "human" && entry.hash !== entryCommit).map((entry) => ({
40724
40495
  hash: entry.hash,
40725
40496
  state: entry.state
40726
40497
  }));
40727
- const summaryInput = (run, slice, settings) => {
40498
+ const summaryInput = (run, settings) => {
40728
40499
  const entryCommit = run.trace[0].hash;
40729
- const { trace, costEntries } = narrowToSlice(run, slice);
40730
- const { total, byModel } = costTotals(costEntries);
40500
+ const { total, byModel } = costTotals(run.costEntries);
40731
40501
  return {
40732
40502
  entryCommit,
40733
- processBase: slice?.base ?? run.startParentHash,
40734
- processTip: slice?.tip ?? run.trace[run.trace.length - 1].hash,
40735
- humanCommits: humanCommitsOf(trace, entryCommit),
40503
+ processBase: run.startParentHash,
40504
+ processTip: run.trace[run.trace.length - 1].hash,
40505
+ humanCommits: humanCommitsOf(run.trace, entryCommit),
40736
40506
  processCost: total,
40737
40507
  processCostByModel: byModel,
40738
40508
  marks: run.marks,
40739
- ...settings,
40740
- ...slice !== void 0 ? { slice } : {}
40509
+ ...settings
40741
40510
  };
40742
40511
  };
40743
- const locateSlice = (config, run, sliceName) => gen(function* () {
40744
- if (sliceName === void 0) return {};
40745
- const { ranges, versionWarning } = yield* replayedSlices(config, run, {
40746
- command: "gtd summary",
40747
- configReason: "HEAD's process replays under today's config; revert or stash the edit to summarize its slices"
40748
- });
40749
- const warn = versionWarning !== void 0 ? { versionWarning } : {};
40750
- const at = ranges.findIndex((r) => r.name === sliceName);
40751
- if (at < 0) return {
40752
- unknownSlice: `gtd summary: refused — no slice named "${sliceName}"; the slices, in stack order: ${ranges.map((r) => r.name).join(", ")}`,
40753
- ...warn
40754
- };
40755
- const { base, tip } = ranges[at];
40756
- return {
40757
- slice: {
40758
- name: sliceName,
40759
- base,
40760
- tip,
40761
- index: at + 1,
40762
- count: ranges.length
40763
- },
40764
- ...warn
40765
- };
40766
- });
40767
- /**
40768
- * A plain call never replays; under a slice, `entryCommit` and `marks` stay
40769
- * whole-process.
40770
- */
40771
- const summaryFor = (run, sliceName) => gen(function* () {
40512
+ const summaryFor = (run) => gen(function* () {
40772
40513
  const config = yield* (yield* ConfigService).load;
40773
40514
  const host = yield* Host;
40774
40515
  const resolved = yield* workflowOf(config, run);
40775
40516
  const summary = resolved.def.summary;
40776
- if (summary === void 0) return { prompt: void 0 };
40777
- if (sliceName === void 0 && run.trace.length === 0) return { prompt: void 0 };
40778
- const located = yield* locateSlice(config, run, sliceName);
40779
- if ("unknownSlice" in located) return {
40780
- prompt: void 0,
40781
- ...located
40782
- };
40783
- const { slice, versionWarning } = located;
40784
- return {
40785
- prompt: summary(summaryInput(run, slice, settingsFor(resolved, run.pinnedVars, host.env, {}, {}))),
40786
- ...versionWarning !== void 0 ? { versionWarning } : {}
40787
- };
40517
+ if (summary === void 0 || run.trace.length === 0) return { prompt: void 0 };
40518
+ return { prompt: summary(summaryInput(run, settingsFor(resolved, run.pinnedVars, host.env, {}, {}))) };
40788
40519
  });
40789
40520
  /** 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. */
40790
40521
  const inRepoConfigPaths = (root, files, recorded) => {
@@ -41088,7 +40819,6 @@ const combinedScript = (required, optional) => {
41088
40819
  };
41089
40820
  //#endregion
41090
40821
  //#region src/emit/OutcomeScript.ts
41091
- init_wire();
41092
40822
  /**
41093
40823
  * A `printf '<fmt>' <args...>` bash statement — `args` are already-valid bash
41094
40824
  * tokens (a `shellQuote`d literal, a `"$(git …)"` substitution), never
@@ -41343,7 +41073,6 @@ var ToolCommands = class extends Tag("ToolCommands")() {};
41343
41073
  //#endregion
41344
41074
  //#region src/cli/Output.ts
41345
41075
  init_esm();
41346
- init_wire();
41347
41076
  /**
41348
41077
  * A command-level failure that should exit like a CLI usage error (2), not a
41349
41078
  * generic runtime failure (1): an unknown `--json=<path>` selector, or a
@@ -41369,7 +41098,6 @@ const writeJson = (json, out, document) => gen(function* () {
41369
41098
  //#endregion
41370
41099
  //#region src/cli/Strays.ts
41371
41100
  init_esm();
41372
- init_wire();
41373
41101
  const resolveSide = (git, side) => git.resolveRef(`${side}^{commit}`).pipe(mapError$1(() => new GtdError(`gtd strays: unknown revision '${side}'`)));
41374
41102
  /** stdout carries the array alone; the summary is ungated stderr so a quiet run still explains its exit code, the per-path lines are narration. */
41375
41103
  const runStraysCommand = (command, json, out) => gen(function* () {
@@ -41469,10 +41197,8 @@ const runHistoryFetchCommand = (remote, out) => sync$1(() => {
41469
41197
  //#endregion
41470
41198
  //#region src/cli/Metrics.ts
41471
41199
  init_esm();
41472
- init_wire();
41473
41200
  const QUALITY$1 = ".gtd/QUALITY.md";
41474
41201
  const LENS = /^((?:.*\.)?quality\.[^.]+)\.reviewing$/;
41475
- const CONTINUE = /(^|\.)continue\./;
41476
41202
  const inSeam = (step) => step.startsWith("seam.");
41477
41203
  const phaseOf = (step) => {
41478
41204
  const scope = step.split(".")[0];
@@ -41491,13 +41217,12 @@ const add = (into, key, n) => {
41491
41217
  };
41492
41218
  const roundAll = (record) => Object.fromEntries(Object.entries(record).map(([k, v]) => [k, round(v)]));
41493
41219
  const emptyTally = () => ({
41494
- slices: {
41220
+ builds: {
41495
41221
  decided: 0,
41496
41222
  landed: 0,
41497
41223
  rebuilt: 0,
41498
41224
  refactored: 0
41499
41225
  },
41500
- continuations: record(),
41501
41226
  byModel: record(),
41502
41227
  byPhase: {
41503
41228
  naive: 0,
@@ -41514,9 +41239,8 @@ const emptyTally = () => ({
41514
41239
  });
41515
41240
  const sum = (record) => Object.values(record).reduce((a, b) => a + b, 0);
41516
41241
  const finish = (t) => ({
41517
- slices: t.slices,
41518
- refactorFirstRate: t.slices.decided === 0 ? null : round(t.slices.rebuilt / t.slices.decided, 3),
41519
- continuations: t.continuations,
41242
+ builds: t.builds,
41243
+ refactorFirstRate: t.builds.decided === 0 ? null : round(t.builds.rebuilt / t.builds.decided, 3),
41520
41244
  cost: {
41521
41245
  total: round(sum(t.byModel)),
41522
41246
  byModel: roundAll(t.byModel),
@@ -41544,33 +41268,33 @@ const tallyCost = (t, open, costs, phase) => {
41544
41268
  if (phase === "implement" && open.rebuilding) t.doubleWork.rebuild += cost;
41545
41269
  }
41546
41270
  };
41547
- /** 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. */
41271
+ /** 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. */
41548
41272
  const tallyDecision = (t, open, at, to) => {
41549
41273
  if (at === "implement.closing") open.rebuilding = false;
41550
41274
  if (open.applying !== void 0 && at === "implement.applying") {
41551
41275
  const naive = open.applying;
41552
41276
  open.applying = void 0;
41553
41277
  if (to !== "implement.building") return;
41554
- t.slices.landed--;
41278
+ t.builds.landed--;
41555
41279
  rebuild(t, open, naive);
41556
41280
  return;
41557
41281
  }
41558
41282
  if (!inSeam(at) || inSeam(to)) return;
41559
- t.slices.decided++;
41283
+ t.builds.decided++;
41560
41284
  const naive = open.naive;
41561
41285
  open.naive = 0;
41562
41286
  if (to === "implement.applying") {
41563
41287
  open.applying = naive;
41564
41288
  open.rebuilding = false;
41565
- t.slices.landed++;
41289
+ t.builds.landed++;
41566
41290
  return;
41567
41291
  }
41568
41292
  rebuild(t, open, naive);
41569
- if (at === "seam.note") t.slices.refactored++;
41293
+ if (at === "seam.note") t.builds.refactored++;
41570
41294
  };
41571
41295
  const rebuild = (t, open, naive) => {
41572
41296
  open.rebuilding = true;
41573
- t.slices.rebuilt++;
41297
+ t.builds.rebuilt++;
41574
41298
  t.doubleWork.naive += naive;
41575
41299
  };
41576
41300
  /** Oldest first; a commit gtd did not write is skipped. */
@@ -41587,7 +41311,6 @@ const processMetrics = (commits) => {
41587
41311
  if (subject === void 0) continue;
41588
41312
  const at = subject.from ?? subject.to;
41589
41313
  tallyCost(t, open, message.cost, phaseOf(at));
41590
- if (subject.actor === "agent" && CONTINUE.test(at)) add(t.continuations, at.replace(CONTINUE, "$1"), 1);
41591
41314
  const lens = LENS.exec(at)?.[1];
41592
41315
  if (lens !== void 0 && commit.findings > 0) add(t.byLens, lens, commit.findings);
41593
41316
  if (message.marks.some((m) => m.name === "flaky")) add(t.flaky, at, 1);
@@ -41598,8 +41321,7 @@ const processMetrics = (commits) => {
41598
41321
  const totalMetrics = (all) => {
41599
41322
  const t = emptyTally();
41600
41323
  for (const m of all) {
41601
- for (const key of Object.keys(t.slices)) t.slices[key] += m.slices[key];
41602
- for (const [k, v] of Object.entries(m.continuations)) add(t.continuations, k, v);
41324
+ for (const key of Object.keys(t.builds)) t.builds[key] += m.builds[key];
41603
41325
  for (const [k, v] of Object.entries(m.cost.byModel)) add(t.byModel, k, v);
41604
41326
  for (const key of Object.keys(t.byPhase)) t.byPhase[key] += m.cost.byPhase[key];
41605
41327
  t.doubleWork.naive += m.cost.doubleWork.naive;
@@ -41636,12 +41358,10 @@ const readProcessMetrics = (entry) => gen(function* () {
41636
41358
  const printable = (s) => s.replace(/[\x00-\x1f\x7f-\x9f\u2028\u2029]/g, "?");
41637
41359
  const listed = (record) => Object.entries(record).map(([k, v]) => `${printable(k)} ${v}`).join(", ");
41638
41360
  const line = (label, m) => {
41639
- const { slices, cost } = m;
41361
+ const { builds, cost } = m;
41640
41362
  const percent = m.refactorFirstRate === null ? "-" : `${Math.round(m.refactorFirstRate * 100)}%`;
41641
- const continued = sum(m.continuations);
41642
41363
  return [
41643
- `${label}: ${slices.decided} slices, ${slices.rebuilt} rebuilt (refactor-first ${percent}), ${slices.refactored} refactored`,
41644
- `${continued} continuations${continued > 0 ? ` (${listed(m.continuations)})` : ""}`,
41364
+ `${label}: ${builds.decided} builds, ${builds.rebuilt} rebuilt (refactor-first ${percent}), ${builds.refactored} refactored`,
41645
41365
  `cost ${cost.total} (${listed(cost.byPhase)}; by model ${listed(cost.byModel) || "-"}), double work ${cost.doubleWork.total}`,
41646
41366
  `${m.findings.total} findings${m.findings.total > 0 ? ` (${listed(m.findings.byLens)})` : ""}`,
41647
41367
  `${m.flaky.total} flaky${m.flaky.total > 0 ? ` (${listed(m.flaky.byStep)})` : ""}`
@@ -41838,7 +41558,7 @@ const processJudgments = (commits) => {
41838
41558
  });
41839
41559
  };
41840
41560
  const NOT_COMPARABLE = { tailsNotComparable: "true" };
41841
- const field$1 = (key, value) => value === void 0 ? {} : { [key]: value };
41561
+ const field = (key, value) => value === void 0 ? {} : { [key]: value };
41842
41562
  const JUDGES = {
41843
41563
  retry: ({ reads, i, scope }, v) => {
41844
41564
  const reports = reportsBefore(reads, i, scope);
@@ -41848,8 +41568,8 @@ const JUDGES = {
41848
41568
  const known = previous !== void 0 && current !== void 0;
41849
41569
  return {
41850
41570
  state: comparable ? {
41851
- ...field$1("previous", previous),
41852
- ...field$1("current", current)
41571
+ ...field("previous", previous),
41572
+ ...field("current", current)
41853
41573
  } : NOT_COMPARABLE,
41854
41574
  outcome: known && comparable && answerOf(v.answer) === "identical" ? retryOutcome(reads, i, scope) : null
41855
41575
  };
@@ -41857,12 +41577,12 @@ const JUDGES = {
41857
41577
  triage: ({ reads, i }, v) => {
41858
41578
  const finding = reads[i].commit.findings?.find((f) => f.id === v.id);
41859
41579
  return {
41860
- state: field$1(v.id, finding && `## ${finding.title}\n\n${finding.body}`),
41580
+ state: field(v.id, finding && `## ${finding.title}\n\n${finding.body}`),
41861
41581
  outcome: triageOutcome(reads, i, answerOf(v.answer), finding)
41862
41582
  };
41863
41583
  },
41864
41584
  clear: ({ reads, i, sketch }, v) => ({
41865
- state: field$1("sketch", sketch),
41585
+ state: field("sketch", sketch),
41866
41586
  outcome: clearOutcome(reads, i, v)
41867
41587
  })
41868
41588
  };
@@ -42049,7 +41769,6 @@ const runHistoryJudgesCommand = (refs, vars, jsonl, json, out) => gen(function*
42049
41769
  //#endregion
42050
41770
  //#region src/cli/Program.ts
42051
41771
  init_esm();
42052
- init_wire();
42053
41772
  init_steering();
42054
41773
  const selfGtd = () => {
42055
41774
  const script = process.argv[1];
@@ -42131,16 +41850,10 @@ const planLanding = (opts = {}, current) => gen(function* () {
42131
41850
  * closes or sits inside its own closing message. Writes nothing — no git, no
42132
41851
  * state transition, no file. Refuses (throws, mapped to the runtime-error
42133
41852
  * exit code) when the workflow declares no `summary` prompt, or the resolved
42134
- * run has no commits to name. `--slice` also refuses an unknown slice name
42135
- * (listing the valid slices), a config edited since the process closed, and a
42136
- * replay that diverged. The edge never narrates: the CLI prints the
42137
- * version warning, before the unknown-slice refusal; a replay refusal carries
42138
- * it in its message.
42139
- */
42140
- const runSummaryCommand = (out, sliceName) => gen(function* () {
42141
- const { prompt, versionWarning, unknownSlice } = yield* summaryFor(yield* summaryRun, sliceName);
42142
- if (versionWarning !== void 0) yield* (yield* Narrator).warn(versionWarning);
42143
- if (unknownSlice !== void 0) return yield* fail$7(new Error(unknownSlice));
41853
+ * run has no commits to name.
41854
+ */
41855
+ const runSummaryCommand = (out) => gen(function* () {
41856
+ const { prompt } = yield* summaryFor(yield* summaryRun);
42144
41857
  if (prompt === void 0) return yield* fail$7(/* @__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"));
42145
41858
  out.write(prompt.endsWith("\n") ? prompt : prompt + "\n");
42146
41859
  });
@@ -42185,17 +41898,6 @@ const readStdin = () => tryPromise({
42185
41898
  },
42186
41899
  catch: (e) => e instanceof Error ? e : new Error(String(e))
42187
41900
  });
42188
- /** Stdout is written once: a hook reads one JSON document, or none. */
42189
- const runContextHookCommand = (command, out) => gen(function* () {
42190
- const tools = yield* ToolCommands;
42191
- const input = yield* readStdin();
42192
- out.write(yield* tools.contextHook({
42193
- input,
42194
- ...command.soft !== void 0 ? { soft: command.soft } : {},
42195
- ...command.hard !== void 0 ? { hard: command.hard } : {},
42196
- ...command.window !== void 0 ? { window: command.window } : {}
42197
- }));
42198
- });
42199
41901
  /** Stdout is written once, after the answerer resolves — a rejection leaves it empty. */
42200
41902
  const runJudgeRunCommand = (command, out) => gen(function* () {
42201
41903
  const host = yield* Host;
@@ -42337,17 +42039,6 @@ const runDoorsCommand = (json, out) => gen(function* () {
42337
42039
  if (json.kind === "document") out.write(`${JSON.stringify(document)}\n`);
42338
42040
  else yield* writeSelection(out, document, json.path);
42339
42041
  });
42340
- const runSlicesCommand = (json, out) => gen(function* () {
42341
- const { slices, versionWarning } = yield* processSlices;
42342
- if (versionWarning !== void 0) yield* (yield* Narrator).warn(versionWarning);
42343
- if (json.kind === "off") {
42344
- out.write(slices.map((s) => `${s.name} ${s.base}..${s.tip}\n`).join(""));
42345
- return;
42346
- }
42347
- const document = slicesDocument(slices);
42348
- if (json.kind === "document") out.write(renderSlicesJson(document));
42349
- else yield* writeSelection(out, document, json.path);
42350
- });
42351
42042
  /**
42352
42043
  * Ends the process underway without completing it. Nothing is discarded: a
42353
42044
  * `git reset --mixed` to the process's start commit leaves every turn
@@ -42654,7 +42345,6 @@ const needsOf = (kind) => {
42654
42345
  switch (kind) {
42655
42346
  case "lsp":
42656
42347
  case "install":
42657
- case "contextHook":
42658
42348
  case "judgeRun":
42659
42349
  case "strays": return "none";
42660
42350
  case "check":
@@ -42683,7 +42373,6 @@ const dispatchVoidCommand = (command, json, out) => {
42683
42373
  case "start": return runStartCommand(command.actor, command.workflow, command.vars, out, command.label);
42684
42374
  case "door": return runDoorCommand(command.name, command.args, out);
42685
42375
  case "doors": return runDoorsCommand(json, out);
42686
- case "slices": return runSlicesCommand(json, out);
42687
42376
  case "abandon": return runAbandonCommand(out);
42688
42377
  case "restore": return runRestoreCommand(out);
42689
42378
  case "next": return runNextCommand(json, out);
@@ -42693,8 +42382,7 @@ const dispatchVoidCommand = (command, json, out) => {
42693
42382
  return runCheckCommand(command.mode, command.file, command.openQuestions ?? false, command.openThreads ?? false);
42694
42383
  case "uncheck": return runUncheckCommand(command.file);
42695
42384
  case "install": return runInstallCommand(out);
42696
- case "contextHook": return runContextHookCommand(command, out);
42697
- case "summary": return runSummaryCommand(out, command.slice);
42385
+ case "summary": return runSummaryCommand(out);
42698
42386
  case "base": return runBaseCommand(out);
42699
42387
  case "judge": return runJudgeCommand(json, out);
42700
42388
  case "judgeAnswer": return runJudgeAnswerCommand(json, out);
@@ -42733,23 +42421,17 @@ const runCommand = (command, json, out) => {
42733
42421
  //#endregion
42734
42422
  //#region src/cli/Cli.ts
42735
42423
  init_esm();
42736
- init_wire();
42737
42424
  const nonNegativeNumber = (raw, flag) => {
42738
42425
  const n = Number(raw);
42739
42426
  if (raw.trim() === "" || !Number.isFinite(n) || n < 0) return left(`gtd: ${flag} must be a non-negative number — got "${raw}"`);
42740
42427
  return right(n);
42741
42428
  };
42742
- const share = (raw, flag) => {
42743
- const n = Number(raw);
42744
- 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}"`);
42745
- };
42746
42429
  const JSON_KINDS = /* @__PURE__ */ new Set([
42747
42430
  "next",
42748
42431
  "land",
42749
42432
  "judge",
42750
42433
  "judgeAnswer",
42751
42434
  "doors",
42752
- "slices",
42753
42435
  "historyList",
42754
42436
  "historyShow",
42755
42437
  "historyMetrics",
@@ -42763,10 +42445,10 @@ const FLAGS = [
42763
42445
  repeatable: false,
42764
42446
  scope: (kind) => JSON_KINDS.has(kind),
42765
42447
  decode: ([raw]) => right(raw ?? ""),
42766
- 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",
42448
+ 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",
42767
42449
  valueHint: "<path>",
42768
42450
  help: [
42769
- "(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd slices/gtd history",
42451
+ "(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd history",
42770
42452
  "list/gtd history show/gtd history metrics/gtd history judges/gtd strays only) output structured JSON. Bare --json prints the whole document;",
42771
42453
  "--json=<path> (a dotted key path into that document, e.g.",
42772
42454
  "kind, content, session.id) prints just that value: a",
@@ -42883,54 +42565,6 @@ const FLAGS = [
42883
42565
  "GTD_JUDGE_MODEL, then judge.json, then sonnet"
42884
42566
  ]
42885
42567
  },
42886
- {
42887
- name: "--soft",
42888
- arity: 1,
42889
- repeatable: false,
42890
- scope: (kind) => kind === "contextHook",
42891
- decode: ([raw]) => share(raw ?? "", "--soft"),
42892
- scopeError: "gtd: --soft is only valid for `gtd context-hook`",
42893
- valueHint: "<share>",
42894
- help: ["(gtd context-hook only) the soft limit; default 0.6"]
42895
- },
42896
- {
42897
- name: "--hard",
42898
- arity: 1,
42899
- repeatable: false,
42900
- scope: (kind) => kind === "contextHook",
42901
- decode: ([raw]) => share(raw ?? "", "--hard"),
42902
- scopeError: "gtd: --hard is only valid for `gtd context-hook`",
42903
- valueHint: "<share>",
42904
- help: ["(gtd context-hook only) the hard limit; default 0.7"]
42905
- },
42906
- {
42907
- name: "--window",
42908
- arity: 1,
42909
- repeatable: false,
42910
- scope: (kind) => kind === "contextHook",
42911
- decode: ([raw]) => {
42912
- const window = parseContextWindow(raw);
42913
- return window !== void 0 ? right(window) : left(`gtd: --window must be a positive integer — got "${raw ?? ""}"`);
42914
- },
42915
- scopeError: "gtd: --window is only valid for `gtd context-hook`",
42916
- valueHint: "<tokens>",
42917
- help: ["(gtd context-hook only) the model's context window in", "tokens; default 200000"]
42918
- },
42919
- {
42920
- name: "--slice",
42921
- arity: 1,
42922
- repeatable: false,
42923
- scope: (kind) => kind === "summary",
42924
- decode: ([raw]) => right(raw),
42925
- scopeError: "gtd: --slice is only valid for `gtd summary`",
42926
- valueHint: "<name>",
42927
- help: [
42928
- "(gtd summary only) the closing-message prompt for one",
42929
- "slice, named as `gtd slices` lists it: its own range, human",
42930
- "commits and cost. An unknown name is refused (exit 1) with",
42931
- "every valid slice listed"
42932
- ]
42933
- },
42934
42568
  {
42935
42569
  name: "--workflow",
42936
42570
  arity: 1,
@@ -43221,22 +42855,6 @@ const COMMAND_ROWS = [
43221
42855
  "else it writes nothing."
43222
42856
  ]
43223
42857
  },
43224
- {
43225
- token: "context-hook",
43226
- kind: "contextHook",
43227
- arity: "none",
43228
- details: [
43229
- "A Claude Code PostToolUse and PreCompact hook that stops an",
43230
- "agent turn at a share of its context window. Reads the",
43231
- "hook's input on stdin and the context the transcript's",
43232
- "latest assistant message used. Past --soft it blocks with a",
43233
- "request to end the turn with a handoff note; past --hard,",
43234
- "or at PreCompact, it stops the turn with a stopReason",
43235
- "starting \"gtd: context limit\". Prints nothing otherwise,",
43236
- "and for a subagent's tool call. Always exits 0 on valid",
43237
- "input; see docs/driver.md"
43238
- ]
43239
- },
43240
42858
  {
43241
42859
  token: "summary",
43242
42860
  kind: "summary",
@@ -43250,15 +42868,10 @@ const COMMAND_ROWS = [
43250
42868
  "no state transition, no file, no session identity — the",
43251
42869
  "driver pipes the output to a cold agent and does what it",
43252
42870
  "wants with the result (a squash, an amend, a PR body).",
43253
- "--slice <name> prints it for one slice instead: its own",
43254
- "range, human commits and cost (see `gtd slices`); it also",
43255
- "refuses an unknown slice name, and a HEAD-closing process",
43256
- "whose config files differ from the working tree or whose",
43257
- "replay diverged; an open last slice is provisional. Refuses",
43258
- "(exit 1) when the workflow declares no summary: template,",
43259
- "or when the resolved run has no commits to name — plain",
43260
- "gtd summary is runnable any time before the next thing",
43261
- "lands on the branch"
42871
+ "Refuses (exit 1) when the workflow declares no summary:",
42872
+ "template, or when the resolved run has no commits to name —",
42873
+ "it is runnable any time before the next thing lands on the",
42874
+ "branch"
43262
42875
  ]
43263
42876
  },
43264
42877
  {
@@ -43304,25 +42917,6 @@ const COMMAND_ROWS = [
43304
42917
  "value off it. Writes nothing"
43305
42918
  ]
43306
42919
  },
43307
- {
43308
- token: "slices",
43309
- kind: "slices",
43310
- arity: "none",
43311
- details: [
43312
- "List the slices of the process HEAD closes or sits inside,",
43313
- "first slice first: one `name base..tip` line each (full",
43314
- "hashes; plain output carries no promise). --json prints",
43315
- "{ schema: 1, slices: [{ name, base, tip, closed }] };",
43316
- "--json=<path> reads one value off it. Writes nothing: no",
43317
- "git, no state transition, no session identity. When HEAD",
43318
- "closes a process, every slice is listed as closed; a gtd",
43319
- "version mismatch only warns on stderr. Refuses (exit 1)",
43320
- "when HEAD neither closes nor sits inside a process, or when",
43321
- "today's config disagrees with history about the process:",
43322
- "HEAD closes it and its config files differ from the working",
43323
- "tree, or the replay diverged"
43324
- ]
43325
- },
43326
42920
  {
43327
42921
  token: "judge",
43328
42922
  kind: "judge",
@@ -43424,11 +43018,11 @@ const COMMAND_ROWS = [
43424
43018
  many: true
43425
43019
  },
43426
43020
  details: [
43427
- "Report how the kept processes went: slices whose naive pass",
43428
- "landed or was rebuilt (the refactor-first rate), continuations",
43429
- "per phase, Gtd-Cost by model and phase with the double work of",
43430
- "a rebuild, quality-lap findings per lens, and red checks that",
43431
- "passed on their re-check — per process and in total. Each <ref>",
43021
+ "Report how the kept processes went: builds whose naive pass",
43022
+ "landed or was rebuilt (the refactor-first rate), Gtd-Cost by",
43023
+ "model and phase with the double work of a rebuild, quality-lap",
43024
+ "findings per lens, and red checks that passed on their",
43025
+ "re-check — per process and in total. Each <ref>",
43432
43026
  "is read as by history show, but a branch name means every",
43433
43027
  "process kept from it; none means every ref.",
43434
43028
  "Reads commit subjects and trailers, never replays, so a",
@@ -43757,23 +43351,9 @@ const parseArgv = (argv) => {
43757
43351
  verbose
43758
43352
  };
43759
43353
  }
43760
- if (kind === "contextHook") return {
43761
- kind: "command",
43762
- command: {
43763
- kind: "contextHook",
43764
- ...bag["--soft"] !== void 0 ? { soft: bag["--soft"] } : {},
43765
- ...bag["--hard"] !== void 0 ? { hard: bag["--hard"] } : {},
43766
- ...bag["--window"] !== void 0 ? { window: bag["--window"] } : {}
43767
- },
43768
- json,
43769
- verbose
43770
- };
43771
43354
  if (kind === "summary") return {
43772
43355
  kind: "command",
43773
- command: {
43774
- kind: "summary",
43775
- ...bag["--slice"] !== void 0 ? { slice: bag["--slice"] } : {}
43776
- },
43356
+ command: { kind: "summary" },
43777
43357
  json,
43778
43358
  verbose
43779
43359
  };
@@ -59472,88 +59052,6 @@ var init_lsp = __esmMin((() => {
59472
59052
  init_Lsp();
59473
59053
  }));
59474
59054
  //#endregion
59475
- //#region src/hook/HookHost.ts
59476
- var HookHost, liveHookHost;
59477
- var init_HookHost = __esmMin((() => {
59478
- init_esm();
59479
- HookHost = class extends Tag("HookHost")() {};
59480
- liveHookHost = succeed$2(HookHost, {
59481
- env: process.env,
59482
- readFile: (path) => try_({
59483
- try: () => {
59484
- try {
59485
- return readFileSync(path, "utf-8");
59486
- } catch (error) {
59487
- if (error.code === "ENOENT") return void 0;
59488
- throw error;
59489
- }
59490
- },
59491
- catch: (error) => /* @__PURE__ */ new Error(`could not read ${path}: ${String(error)}`)
59492
- }),
59493
- appendFile: (path, text) => sync$1(() => appendFileSync(path, text))
59494
- });
59495
- }));
59496
- //#endregion
59497
- //#region src/hook/ContextHook.ts
59498
- var field, limitOf, parseInput, tokensOf, recordStop, runContextHook;
59499
- var init_ContextHook = __esmMin((() => {
59500
- init_esm();
59501
- init_wire();
59502
- init_HookHost();
59503
- field = (value, key) => typeof value === "object" && value !== null ? value[key] : void 0;
59504
- limitOf = (request) => {
59505
- const limit = {
59506
- soft: request.soft ?? DEFAULT_CONTEXT_LIMIT.soft,
59507
- hard: request.hard ?? DEFAULT_CONTEXT_LIMIT.hard
59508
- };
59509
- const fault = contextLimitFault(limit);
59510
- return fault === void 0 ? succeed$3(limit) : fail$7(/* @__PURE__ */ new Error(`gtd context-hook: ${fault}`));
59511
- };
59512
- parseInput = (text) => try_({
59513
- try: () => JSON.parse(text),
59514
- catch: () => /* @__PURE__ */ new Error("gtd context-hook: stdin is not a hook's JSON input")
59515
- }).pipe(map$4((input) => {
59516
- const event = field(input, "hook_event_name");
59517
- if (event !== "PostToolUse" && event !== "PreCompact") return void 0;
59518
- if (field(input, "agent_id") !== void 0) return void 0;
59519
- const path = field(input, "transcript_path");
59520
- return {
59521
- event,
59522
- transcript: typeof path === "string" && path !== "" ? path : void 0
59523
- };
59524
- }));
59525
- tokensOf = (input) => input.event === "PostToolUse" && input.transcript !== void 0 ? flatMap$4(HookHost, (host) => host.readFile(input.transcript)).pipe(map$4((text) => text === void 0 ? void 0 : transcriptTokens(text))) : succeed$3(void 0);
59526
- recordStop = (stopReason) => flatMap$4(HookHost, (host) => {
59527
- const file = host.env[CONTEXT_STOP_FILE_ENV];
59528
- return file === void 0 || file === "" ? _void : host.appendFile(file, `${stopReason}\n`).pipe(catchAllDefect(() => _void));
59529
- });
59530
- runContextHook = (request) => gen(function* () {
59531
- const limit = yield* limitOf(request);
59532
- const input = yield* parseInput(request.input);
59533
- if (input === void 0) return "";
59534
- const decision = contextHook({
59535
- event: input.event,
59536
- tokens: yield* tokensOf(input),
59537
- window: request.window ?? contextWindowFor(void 0),
59538
- limit
59539
- });
59540
- if (decision === void 0) return "";
59541
- if (decision.kind === "hard") yield* recordStop(decision.stopReason);
59542
- return `${JSON.stringify(hookOutput(decision))}\n`;
59543
- });
59544
- }));
59545
- //#endregion
59546
- //#region src/hook/index.ts
59547
- var hook_exports = /* @__PURE__ */ __exportAll({
59548
- HookHost: () => HookHost,
59549
- liveHookHost: () => liveHookHost,
59550
- runContextHook: () => runContextHook
59551
- });
59552
- var init_hook = __esmMin((() => {
59553
- init_HookHost();
59554
- init_ContextHook();
59555
- }));
59556
- //#endregion
59557
59055
  //#region src/judges/providers/io.ts
59558
59056
  var liveJudgeIo;
59559
59057
  var init_io = __esmMin((() => {
@@ -60036,10 +59534,6 @@ const liveToolCommands = effect$1(ToolCommands, gen(function* () {
60036
59534
  const { startLspServer } = await Promise.resolve().then(() => (init_lsp(), lsp_exports));
60037
59535
  return () => startLspServer({ gtd });
60038
59536
  }),
60039
- contextHook: (request) => lazy("context-hook", async () => {
60040
- const { runContextHook, liveHookHost } = await Promise.resolve().then(() => (init_hook(), hook_exports));
60041
- return () => runContextHook(request).pipe(provide$1(liveHookHost));
60042
- }),
60043
59537
  judge: async (request) => {
60044
59538
  const { runJudge } = await Promise.resolve().then(() => (init_judges(), judges_exports)).catch((error) => {
60045
59539
  throw loadFailure("judge")(error);