@pmelab/gtd 24.0.0 → 24.1.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 (196) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +31 -5
  3. package/claude/hooks/access.ts +1 -1
  4. package/dist/chunks/Schema-Dh-mkwkq.mjs +25353 -0
  5. package/dist/chunks/reserved-ejn-LVYO.mjs +1388 -0
  6. package/dist/chunks/rolldown-runtime-8H4AJuhK.mjs +14 -0
  7. package/dist/chunks/steering-D-PvRC5W.mjs +16296 -0
  8. package/dist/chunks/wire-DA7q2ikx.mjs +473 -0
  9. package/dist/flows.mjs +525 -0
  10. package/dist/gtd.bundle.mjs +43339 -55575
  11. package/dist/steering.mjs +2 -0
  12. package/dist/testkit.mjs +13006 -0
  13. package/dist/wire.mjs +2 -0
  14. package/dist/workflow-kit.mjs +352 -0
  15. package/dist/workflows/feature.mjs +315 -0
  16. package/dist/workflows/fix.mjs +40 -0
  17. package/dist/workflows/review.mjs +25 -0
  18. package/dist/workflows.mjs +8 -6
  19. package/package.json +69 -8
  20. package/src/cli/Cli.test.ts +1329 -0
  21. package/src/cli/Cli.ts +1348 -0
  22. package/src/cli/Program.test.ts +2262 -0
  23. package/src/cli/Program.ts +1331 -0
  24. package/src/cli/ToolCommands.ts +43 -0
  25. package/src/cli/index.ts +14 -0
  26. package/src/edge/Edge.test.ts +1016 -0
  27. package/src/edge/Edge.ts +1225 -0
  28. package/src/edge/index.ts +16 -0
  29. package/src/emit/Emit.test.ts +425 -0
  30. package/src/emit/Emit.ts +139 -0
  31. package/src/emit/GitScript.test.ts +260 -0
  32. package/src/emit/GitScript.ts +131 -0
  33. package/src/emit/ModeContradiction.test.ts +120 -0
  34. package/src/emit/ModeContradiction.ts +66 -0
  35. package/src/emit/OutcomeScript.test.ts +238 -0
  36. package/src/emit/OutcomeScript.ts +73 -0
  37. package/src/emit/SteeringMode.test.ts +274 -0
  38. package/src/emit/SteeringMode.ts +256 -0
  39. package/src/emit/index.ts +40 -0
  40. package/src/install/ConfigScaffold.ts +19 -0
  41. package/src/install/Install.test.ts +426 -0
  42. package/src/install/Install.ts +634 -0
  43. package/src/install/Version.ts +27 -0
  44. package/src/install/index.ts +3 -0
  45. package/src/platform/CommandRunner.ts +74 -0
  46. package/src/platform/Commentary.test.ts +126 -0
  47. package/src/platform/Commentary.ts +92 -0
  48. package/src/platform/Git.test.ts +50 -0
  49. package/src/platform/Git.ts +552 -0
  50. package/src/platform/Host.ts +42 -0
  51. package/src/platform/RetainedHistory.test.ts +65 -0
  52. package/src/platform/RetainedHistory.ts +34 -0
  53. package/src/platform/Workspace.test.ts +669 -0
  54. package/src/platform/Workspace.ts +631 -0
  55. package/src/platform/WorktreeState.test.ts +162 -0
  56. package/src/platform/WorktreeState.ts +50 -0
  57. package/src/platform/index.ts +22 -0
  58. package/src/replay/Access.test.ts +53 -0
  59. package/src/replay/Access.ts +46 -0
  60. package/src/replay/Format.test.ts +20 -0
  61. package/src/replay/Format.ts +14 -0
  62. package/src/replay/Replay.test.ts +887 -0
  63. package/src/replay/Replay.ts +669 -0
  64. package/src/replay/Trailers.test.ts +117 -0
  65. package/src/replay/Trailers.ts +213 -0
  66. package/src/replay/Tree.test.ts +25 -0
  67. package/src/replay/Tree.ts +45 -0
  68. package/src/replay/index.ts +11 -0
  69. package/src/steering/Blocks.fixture.ts +6 -0
  70. package/src/steering/Blocks.test.ts +167 -0
  71. package/src/steering/Blocks.ts +321 -0
  72. package/src/steering/CodeThreads.test.ts +127 -0
  73. package/src/steering/CodeThreads.ts +197 -0
  74. package/src/steering/Edits.test.ts +19 -0
  75. package/src/steering/Edits.ts +20 -0
  76. package/src/steering/Eol.test.ts +16 -0
  77. package/src/steering/Eol.ts +2 -0
  78. package/src/steering/FORMATS.md +58 -0
  79. package/src/steering/Footnotes.test.ts +977 -0
  80. package/src/steering/Footnotes.ts +661 -0
  81. package/src/steering/Inline.fixture.ts +7 -0
  82. package/src/steering/Inline.test.ts +227 -0
  83. package/src/steering/Inline.ts +195 -0
  84. package/src/steering/MarkdownTree.test.ts +247 -0
  85. package/src/steering/MarkdownTree.ts +260 -0
  86. package/src/steering/Outline.ts +19 -0
  87. package/src/steering/SteeringFormat.test.ts +91 -0
  88. package/src/steering/SteeringFormat.ts +267 -0
  89. package/src/steering/SteeringFormats.test.ts +163 -0
  90. package/src/steering/Threads.ts +163 -0
  91. package/src/steering/contract.test.ts +264 -0
  92. package/src/steering/fixtures/code-threads/cache.stripped.ts.txt +6 -0
  93. package/src/steering/fixtures/code-threads/cache.ts.md +15 -0
  94. package/src/steering/fixtures/free-form/notes.md +12 -0
  95. package/src/steering/fixtures/free-form/notes.read.json +42 -0
  96. package/src/steering/fixtures/qa/answered.md +23 -0
  97. package/src/steering/fixtures/qa/answered.read.json +119 -0
  98. package/src/steering/fixtures/qa/requirements.md +160 -0
  99. package/src/steering/fixtures/qa/requirements.read.json +475 -0
  100. package/src/steering/fixtures/review/review.md +76 -0
  101. package/src/steering/fixtures/review/review.read.json +237 -0
  102. package/src/steering/fixtures/review/ticked.md +23 -0
  103. package/src/steering/fixtures/review/ticked.read.json +99 -0
  104. package/src/steering/fixtures/review-notes/baseline.md +14 -0
  105. package/src/steering/fixtures/review-notes/noted.md +19 -0
  106. package/src/steering/freeform.test.ts +311 -0
  107. package/src/steering/freeform.ts +148 -0
  108. package/src/steering/index.test.ts +62 -0
  109. package/src/steering/index.ts +140 -0
  110. package/src/steering/qa.test.ts +2160 -0
  111. package/src/steering/qa.ts +1104 -0
  112. package/src/steering/review.test.ts +1829 -0
  113. package/src/steering/review.ts +1180 -0
  114. package/src/step/LandStep.ts +31 -0
  115. package/src/step/RepoSnapshot.ts +29 -0
  116. package/src/step/accessRefusal.test.ts +58 -0
  117. package/src/step/accessRefusal.ts +26 -0
  118. package/src/step/index.ts +5 -0
  119. package/src/step/planStart.test.ts +202 -0
  120. package/src/step/planStart.ts +167 -0
  121. package/src/step/planStep.test.ts +104 -0
  122. package/src/step/planStep.ts +89 -0
  123. package/src/step/snapshot.fixture.ts +28 -0
  124. package/src/testkit/EmittedScriptRecognizer.test.ts +639 -0
  125. package/src/testkit/EmittedScriptRecognizer.ts +642 -0
  126. package/src/testkit/FakeGitOperations.ts +95 -0
  127. package/src/testkit/GitTiers.test.ts +11 -0
  128. package/src/testkit/GitTiers.ts +1052 -0
  129. package/src/testkit/InMemRepo.ts +467 -0
  130. package/src/testkit/Layers.test.ts +58 -0
  131. package/src/testkit/Layers.ts +241 -0
  132. package/src/testkit/StrictGitOperations.ts +31 -0
  133. package/src/testkit/TestRepo.test.ts +119 -0
  134. package/src/testkit/TestRepo.ts +274 -0
  135. package/src/testkit/cliIo.ts +36 -0
  136. package/src/testkit/index.ts +9 -0
  137. package/src/testkit/steps.ts +177 -0
  138. package/src/testkit/testkit.feature +65 -0
  139. package/src/wire/BeatDocument.test.ts +419 -0
  140. package/src/wire/BeatDocument.ts +114 -0
  141. package/src/wire/BeatPlain.test.ts +113 -0
  142. package/src/wire/BeatPlain.ts +127 -0
  143. package/src/wire/BeatStatus.test.ts +59 -0
  144. package/src/wire/BeatStatus.ts +81 -0
  145. package/src/wire/Demand.test.ts +142 -0
  146. package/src/wire/Demand.ts +108 -0
  147. package/src/wire/ExitCodes.test.ts +21 -0
  148. package/src/wire/ExitCodes.ts +16 -0
  149. package/src/wire/Glob.ts +29 -0
  150. package/src/wire/Judge.ts +22 -0
  151. package/src/wire/Land.test.ts +120 -0
  152. package/src/wire/Land.ts +75 -0
  153. package/src/wire/Select.test.ts +148 -0
  154. package/src/wire/Select.ts +78 -0
  155. package/src/wire/Sessions.test.ts +59 -0
  156. package/src/wire/Sessions.ts +39 -0
  157. package/src/wire/WireSchema.test.ts +162 -0
  158. package/src/wire/constants.ts +22 -0
  159. package/src/wire/golden/1/beat-capture.json +34 -0
  160. package/src/wire/golden/1/beat-message.json +34 -0
  161. package/src/wire/golden/1/beat-prompt.json +44 -0
  162. package/src/wire/golden/1/beat-script.json +34 -0
  163. package/src/wire/golden/1/beat-stalled.json +34 -0
  164. package/src/wire/golden/1/judge.json +14 -0
  165. package/src/wire/golden/1/land-noop.json +10 -0
  166. package/src/wire/golden/1/land.json +10 -0
  167. package/src/wire/index.ts +12 -0
  168. package/src/wire/types.ts +46 -0
  169. package/src/workflow/Diagnostic.test.ts +93 -0
  170. package/src/workflow/Diagnostic.ts +54 -0
  171. package/src/workflow/SteeringFormats.test.ts +27 -0
  172. package/src/workflow/SteeringFormats.ts +6 -0
  173. package/src/workflow/Workflow.ts +77 -0
  174. package/src/workflow/compile.test.ts +53 -0
  175. package/src/workflow/compile.ts +56 -0
  176. package/src/workflow/discovery.ts +122 -0
  177. package/src/workflow/fingerprint.test.ts +52 -0
  178. package/src/workflow/fingerprint.ts +53 -0
  179. package/src/workflow/index.ts +33 -0
  180. package/src/workflow/load.test.ts +1017 -0
  181. package/src/workflow/load.ts +836 -0
  182. package/src/workflow/skills.test.ts +109 -0
  183. package/src/workflow/skills.ts +90 -0
  184. package/src/workflow/vars.ts +33 -0
  185. package/src/workflows/feature/feature.test.ts +27 -33
  186. package/src/workflows/feature/index.ts +10 -0
  187. package/src/workflows/fix/fix.feature +80 -0
  188. package/src/workflows/fix/fix.test.ts +58 -78
  189. package/src/workflows/fix/index.ts +6 -0
  190. package/src/workflows/index.ts +2 -10
  191. package/src/workflows/review/index.ts +6 -0
  192. package/src/workflows/review/review.test.ts +85 -29
  193. package/src/workflows/review/review.ts +3 -1
  194. package/src/workflows/shared/config.fixture.ts +12 -0
  195. package/src/workflows/shared/reserved.ts +11 -0
  196. package/src/workflows/shared/workflows.steps.ts +7 -0
@@ -0,0 +1,634 @@
1
+ // `MINIMAL_DRIVER` is pinned equal to https://github.com/pmelab/gtd/blob/main/docs/driver.md's "A complete minimal driver" fenced bash block by `Install.test.ts`, so the two can never drift.
2
+ import { GTD_VERSION } from "./Version.js"
3
+
4
+ export const MINIMAL_DRIVER = `#!/usr/bin/env sh
5
+ set -eu
6
+
7
+ # A turn ends when \`claude -p\` returns, so work it backgrounds dies unread
8
+ # and stalls the process. Keep every command in the foreground, with room to
9
+ # finish a long suite (Claude Code's defaults: 2 min, capped at 10).
10
+ export CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1
11
+ export BASH_DEFAULT_TIMEOUT_MS=1800000
12
+ export BASH_MAX_TIMEOUT_MS=7200000
13
+
14
+ beat=1
15
+ while :; do
16
+ # One value per invocation: \`gtd next --json=<path>\` is a pure read (peek
17
+ # or dispatch, indistinguishable, always safe to call again), so nothing is
18
+ # lost by reading \`kind\`/\`idle\` as two separate calls rather than one
19
+ # combined document.
20
+ kind="$(gtd next --json=kind)"
21
+ idle="$(gtd next --json=idle)"
22
+
23
+ # \`idle\` (true iff the initial state, clean tree) is the one shape that
24
+ # means the process is genuinely done — EXCEPT on the run's opening beat:
25
+ # land it anyway, so a workflow whose first step accepts a clean landing
26
+ # (acceptClean) still gets a chance to fire.
27
+ if [ "$beat" -gt 1 ] && [ "\${idle:-}" = true ]; then
28
+ gtd next
29
+ exit 0
30
+ fi
31
+
32
+ case "$kind" in
33
+ stalled)
34
+ gtd next --json=content >&2
35
+ exit 1 ;;
36
+ # you re-ran us resting here: you either edited something or accepted by
37
+ # editing nothing, so land the opening beat either way. Later beats are
38
+ # gates we just produced and you have not read yet — hand off. A judge
39
+ # gate's own \`--json=judge\` is non-empty here: pipe it through
40
+ # \`gtd judge run\` and land the verdict via \`gtd judge answer\`. POSIX sh has
41
+ # no pipefail, so each step is captured first and only piped on when it
42
+ # succeeded; if \`gtd judge run\` fails or \`gtd judge answer\` rejects the
43
+ # verdict, its stderr reaches you and we fall through to the human hand-off.
44
+ message)
45
+ if [ -n "$(gtd next --json=judge)" ] &&
46
+ verdict="$(gtd judge --json | gtd judge run)" &&
47
+ script="$(printf '%s\\n' "$verdict" | gtd judge answer --json=script)"; then
48
+ printf '%s\\n' "$script" | sh
49
+ beat=$((beat + 1))
50
+ continue
51
+ fi
52
+ [ "$beat" = 1 ] || {
53
+ gtd next
54
+ exit 0
55
+ } ;;
56
+ capture) ;; # the human already acted — just land it
57
+ script)
58
+ # A \`script\` rest's plain output is prose, not pipeable into \`sh\` — the
59
+ # raw script comes from \`--json=content\` instead.
60
+ content="$(gtd next --json=content)"
61
+ log="$(gtd next --json=log)"
62
+ sh -c "$content" >>"$log" 2>&1 || true ;;
63
+ prompt)
64
+ session_id="$(gtd next --json=session.id)"
65
+ resume="$(gtd next --json=session.resume)"
66
+ model="$(gtd next --json=model)"
67
+ system="$(gtd next --json=system)"
68
+ validate="$(gtd next --json=validate)"
69
+ log="$(gtd next --json=log)"
70
+ # \`model\` is a hint, not a model name — the bundled workflow ships
71
+ # \`smart\` and \`base\`, which \`claude --model\` rejects. Resolve them
72
+ # with the variables \`gtd loop\` reads; unset, the hint is dropped and
73
+ # the agent runs its own default. Any other value is a real model name.
74
+ case "$model" in
75
+ smart) model="\${GTD_LOOP_MODEL_SMART:-}" ;;
76
+ base) model="\${GTD_LOOP_MODEL_BASE:-}" ;;
77
+ esac
78
+ # The prompt embeds a full diff, so it goes to the agent over stdin,
79
+ # never as an argv positional — argv is capped (~1 MB on macOS, and
80
+ # POSIX guarantees only 4 KB, ARG_MAX), and a diff crosses that far
81
+ # sooner than you'd expect. Piping plain \`gtd next\` (not
82
+ # --json=content) means the diff is rendered once, for this input,
83
+ # rather than twice.
84
+ agent_turn() { gtd next | claude -p "$1" "$session_id" \\
85
+ \${model:+--model "$model"} \\
86
+ \${system:+--system-prompt "$system"} \\
87
+ --dangerously-skip-permissions >>"$log" 2>&1; }
88
+ if [ "\${resume:-}" = true ]
89
+ then agent_turn --resume || agent_turn --session-id
90
+ else agent_turn --session-id || agent_turn --resume
91
+ fi
92
+ n=0
93
+ while [ -n "\${validate:-}" ] && ! fix="$(sh -c "$validate" 2>&1)"; do
94
+ n=$((n + 1)) && [ "$n" -gt 3 ] && { printf '%s\\n' "$fix" >&2; exit 1; }
95
+ # $fix IS the fix prompt, verbatim — piped for the same reason the
96
+ # agent's own turn above is. Whether \`claude --resume\` re-applies the
97
+ # original session's model/system prompt is a harness detail gtd
98
+ # cannot verify from outside, so this passes the identical
99
+ # $model/$system on both calls rather than assume it does —
100
+ # otherwise this fix turn might silently fall back to Claude Code's
101
+ # own defaults while the turn that produced the file ran under the
102
+ # workflow's own model and persona.
103
+ printf '%s' "$fix" | claude -p --resume "$session_id" \\
104
+ \${model:+--model "$model"} \\
105
+ \${system:+--system-prompt "$system"} \\
106
+ --dangerously-skip-permissions >>"$log" 2>&1
107
+ done ;;
108
+ esac
109
+
110
+ # Read every value the landing needs BEFORE piping the script to \`sh\`:
111
+ # \`gtd land\` itself never mutates — it only plans and prints — so
112
+ # \`settled\`/\`idle\`/\`script\` planned against the same untouched tree all
113
+ # agree, whatever order they're read in, as long as all three are read
114
+ # before the script runs and changes the tree out from under them.
115
+ settled="$(gtd land --json=settled)"
116
+ idle="$(gtd land --json=idle)"
117
+ gtd land --json=script | sh
118
+ if [ "\${settled:-}" = true ]; then
119
+ [ "\${idle:-}" = true ] && gtd next
120
+ exit 0
121
+ fi
122
+ beat=$((beat + 1))
123
+ done`
124
+
125
+ export const EDIT_COMMAND = `#!/usr/bin/env sh
126
+ set -eu
127
+ cd "$(git rev-parse --show-toplevel)"
128
+ f="$(gtd next --json=file)"
129
+ f="\${f:-.gtd/TODO.md}"
130
+ mkdir -p "$(dirname "$f")"
131
+ exec "\${EDITOR:-vi}" "$f"`
132
+
133
+ export const DOOR_COMMAND = `#!/usr/bin/env sh
134
+ set -eu
135
+ GTD_BUILD=~/.local/bin/gtd-build
136
+ if [ -z "\${1:-}" ]; then
137
+ echo "usage: gtd-door <name> [args...]" >&2
138
+ exit 2
139
+ fi
140
+ cd "$(git rev-parse --show-toplevel)"
141
+ script="$(gtd door "$@")"
142
+ sh -c "$script"
143
+ exec "$GTD_BUILD"`
144
+
145
+ const HEADER = (): string =>
146
+ `gtd ${GTD_VERSION} — driver protocol\n` +
147
+ `\n` +
148
+ `gtd decides and prints; the driver executes. gtd never runs git, never runs\n` +
149
+ `your agent, never runs a check script, and never runs a steering mode's own\n` +
150
+ `format:/validate: command either — that pair is text in the script gtd next\n` +
151
+ `--json's validate field (or gtd validate) prints, for the driver to run\n` +
152
+ `itself ahead of gtd land.\n`
153
+
154
+ const BEAT_PROTOCOL = `
155
+ ## The beat protocol
156
+
157
+ Two reads and one write, repeated: ask -> act -> land.
158
+
159
+ gtd next --json=<path> which kind is next, and any other field, one pure
160
+ scalar read per value (kind, content, idle,
161
+ session.id, model, validate, log, ...)
162
+ gtd next what to run/show, in plain text — the SAME rest
163
+ the --json=<path> reads above are peeking at
164
+ gtd land --json=<path> how to record what happened, and whether anything
165
+ is still owed (script to run, settled to check)
166
+
167
+ Every beat that acts, lands. Every beat that rests, exits.
168
+
169
+ Every \`gtd next --json=<path>\`/\`gtd land --json=<path>\` read is a pure PEEK —
170
+ every field it reports (\`session.id\`/\`session.resume\` included) is DERIVED
171
+ from history, never stored, so looking is free: nothing distinguishes a peek
172
+ from a dispatch, and calling either one twice in a row is always safe. There
173
+ is no separate claiming form.
174
+
175
+ \`gtd next\` has one structured encoding, \`--json\`: bare \`--json\` prints the
176
+ whole document, \`--json=<path>\` (a dotted key path, e.g. \`kind\`, \`content\`,
177
+ \`session.id\`) prints just that one value — a scalar raw and unquoted, a
178
+ boolean as \`true\`/\`false\`, a list one JSON entry per line. An absent
179
+ optional field prints nothing and exits 0; an unknown path is a usage error
180
+ (exit 2). An AI driver reads \`kind\`/\`session.id\` and the rest straight off
181
+ \`--json=<path>\`, one call per value, without parsing anything — no JSON
182
+ library, no \`jq\`; this briefing's own reference driver reads every field this
183
+ way. Plain \`gtd next\` (no flag) is NOT a parsing surface: \`content\` is the
184
+ bare step in every encoding; plain adds the self-validation instruction at a
185
+ validatable \`prompt\` rest, and adds a status-summary header at every kind
186
+ EXCEPT \`prompt\` (those bytes are the agent's own input, so no header is
187
+ prefixed there). A driver that wants \`content\` programmatically reads it off
188
+ \`--json=content\`, never by scraping plain text.
189
+
190
+ The \`kind\` field (\`--json=kind\`) selects what to do:
191
+
192
+ - \`stalled\` — print \`--json=content\` (the diagnosis, already in hand) to
193
+ stderr and exit non-zero. Terminal: another dispatch would just repeat the
194
+ same fruitless turn.
195
+ - \`message\` — print plain \`gtd next\`'s output (the richer status header,
196
+ not bare \`content\`) and exit 0. This is a human gate. EXCEPT on the run's
197
+ opening beat: the human invoked you while resting there, so land it
198
+ instead of printing. Some gates route a \`"C"\` (clean-tree) pattern
199
+ onward, making "change nothing" a real decision that looks identical to a
200
+ gate nobody has read yet; landing an opening beat at a gate without one is
201
+ a benign no-op, and it prints on the next beat.
202
+ - \`capture\` — a human gate the human already acted on (the tree is dirty).
203
+ Land it immediately, no display needed.
204
+ - \`script\` — run \`--json=content\` with \`sh -c\`, appending its output to
205
+ \`--json=log\`, and ignore its exit code — the outcome lives in the tree,
206
+ not in the exit code.
207
+ - \`prompt\` — send plain \`gtd next\`'s own output to your agent CLI over
208
+ stdin (it's the bare content at this kind, so no separate \`--json=content\`
209
+ read is needed), using the accompanying \`session.id\`/\`session.resume\`/
210
+ \`model\`/\`validate\` fields, each its own \`--json=<path>\` read (see
211
+ below).
212
+ `
213
+
214
+ const JSON_FIELD_REFERENCE = `
215
+ ## Field reference
216
+
217
+ \`gtd next\` and \`gtd land\` each carry one field set, read through \`--json\`
218
+ (bare for the whole document, \`--json=<path>\` for one value) — this driver's
219
+ own beats read fields one at a time via \`--json=<path>\` throughout (see the
220
+ beat protocol above). Each document carries \`schema\`, an integer bumped only
221
+ when a field is removed or renamed — never for an added one, and never tied to
222
+ gtd's package version. A driver that can meet a gtd other than the one it was
223
+ written against checks it and stops on a value it does not read. Every field below
224
+ is always present unless marked "when set" — omitted under bare \`--json\`
225
+ (never \`null\`); under \`--json=<path>\` an absent field prints nothing and
226
+ exits 0, so read it in a shell variable and guard on emptiness
227
+ (\`\${x:-}\`/\`\${x:+...}\`) rather than assuming it is always populated.
228
+
229
+ \`gtd next\` fields — Always: \`schema\`, \`kind\` (\`capture\`|\`message\`|\`script\`|\`prompt\`|
230
+ \`stalled\`), \`content\` (the bare step in every encoding — plain \`gtd next\`
231
+ adds the self-validation instruction at a validatable \`prompt\` rest, and a
232
+ status-summary header at every OTHER kind; \`content\` itself never carries
233
+ either), \`idle\` (\`true\` iff the resolved rest is the workflow's initial
234
+ state with a clean tree), \`initial\` (\`true\` iff no process is underway —
235
+ the default workflow's first step, first visit — whatever the tree holds), \`log\`, \`state\`, \`actor\`, \`changes\` (which
236
+ declared \`on\` pattern, if any, each pending change matches), \`next\` (the
237
+ first declared \`on\` edge that would fire right now — \`{action?, pattern,
238
+ target}\` — or \`null\` on no match). Only at \`kind: "prompt"\` (the DISPATCH
239
+ BLOCK — absent at every other kind, a \`stalled\` beat included, by
240
+ construction): \`session.id\`/\`session.resume\` — both DERIVED from history,
241
+ never stored, so a plain peek is exactly as safe to call as a dispatch would
242
+ be — and, when the state declares a validatable steering file, \`validate\`
243
+ (the script that formats then validates it), and, when the turn's scope declares
244
+ skills, \`skills\` (an array of the skill names, already trimmed and split —
245
+ \`--json=skills.0\` reads the first), and always \`access\`
246
+ (\`{read, write}\`, each an array of globs or \`null\` for unrestricted). When set: \`model\`, \`memory\`,
247
+ \`label\`, \`file\`, \`mode\`, \`edges\`, \`judge\` (the pending judgment's rendered
248
+ document). When a cost has been recorded (a prior \`gtd land --cost=<n>\`):
249
+ \`cost\`, \`costByModel\`.
250
+
251
+ \`gtd land\` fields, in fixed order: \`schema\`, \`script\` (the POSIX sh to run), \`settled\`
252
+ (true for a no-op at a \`script\` rest — stop immediately, nothing more to
253
+ read), \`idle\` (true iff the state landing rests at is the workflow's initial
254
+ state), \`state\`, \`subject\`, \`cost\`, \`model\` (the last three \`null\`, never
255
+ omitted, for a genuine no-op).
256
+
257
+ ### The error envelope
258
+
259
+ stdout is either the complete artifact or byte-empty — never a partial write
260
+ followed by an error. On any failure — a usage error, a refusal, a defect —
261
+ stdout stays byte-empty: \`{"state":"error","prompt":"<message>"}\` rides
262
+ **stderr** instead, followed by a single \`gtd: \` line — exit 1 for a refusal
263
+ or defect, exit 2 for a usage error (nothing was even attempted). This is
264
+ true of every command, not only \`gtd next\` — an invocation that carries (or
265
+ misuses) \`--json\` still gets this envelope on its own failure. A driver
266
+ reading stdout on a failed run reads nothing; read stderr or the exit code
267
+ to learn why.
268
+ `
269
+
270
+ const DRIVER_OBLIGATIONS = `
271
+ ## Driver obligations, in order
272
+
273
+ 1. Read the resolved rest's \`kind\` off \`gtd next --json=kind\` once per
274
+ iteration. Exit code is uniformly \`0\` on success now — it never says
275
+ whose turn is next; only \`1\`/\`2\` means gtd itself failed. There is no
276
+ opening move: a human's pending edit arrives as a \`kind: "capture"\` beat,
277
+ which you land immediately without executing anything. EXCEPT: don't
278
+ trust \`idle\` on the run's very first iteration — land that beat first, so
279
+ a workflow whose first step accepts a clean landing (\`acceptClean\`)
280
+ still gets one chance to advance before you conclude nothing is owed.
281
+ 2. Read the content to run/show off \`--json=content\` — never off plain
282
+ \`gtd next\`, which is not a parsing surface (it wraps \`content\` in a
283
+ status-summary header at every kind but \`prompt\`; see the beat protocol
284
+ above). At a \`prompt\` beat, though, pipe plain \`gtd next\`'s own output to
285
+ the agent instead of reading \`--json=content\` separately — it IS the bare
286
+ content at that kind, so a second read would only render the same diff
287
+ twice. Whichever form you read, hand it to the agent CLI over STDIN, never
288
+ as a command-line argument — argv is capped (roughly 1 MB on macOS, and
289
+ POSIX guarantees only 4 KB, \`ARG_MAX\`), both reachable by an ordinary
290
+ diff, and an argv-passing driver fails on the first large one in a way
291
+ that looks like an agent error rather than a driver bug.
292
+ 3. A \`kind: "stalled"\` beat prints \`--json=content\` to stderr and halts with
293
+ a non-zero exit; a \`kind: "message"\` beat prints plain \`gtd next\`'s
294
+ output and exits 0, unless it is the run's opening beat — land that one,
295
+ since the human's re-invocation while resting there is itself their
296
+ decision.
297
+ 4. Run scripts with their output appended to \`--json=log\` — gtd never
298
+ creates or truncates that file itself; truncate it once per run.
299
+ \`$GTD_LOOP_LOG\` overrides its path.
300
+ 5. Map \`--json=session.id\`/\`--json=session.resume\` onto the agent CLI's own
301
+ session flags — try \`resume\`'s hinted flag first and fall back to the
302
+ other on failure (\`resume\` is a hint, not a contract: nothing is stored,
303
+ so a crashed prior turn or an expired agent session recovers by itself).
304
+ 6. After a \`prompt\` beat, run the same beat's own \`--json=validate\` script
305
+ (when set) and re-prompt its output verbatim on failure — the DRIVER
306
+ owns the retry cap, not gtd.
307
+ 7. Land only a beat you acted on (\`capture\`/\`script\`/\`prompt\`) — a stray
308
+ \`gtd land\` at a clean \`prompt\` rest authors an empty attempt on purpose
309
+ (that IS the stall bookkeeping), so don't land beats you didn't dispatch.
310
+ 8. Read \`--json=settled\`/\`--json=idle\` from \`gtd land\` BEFORE running the
311
+ script — \`gtd land\` never mutates, it only plans and prints, so these
312
+ reads and the \`--json=script\` read that follows all agree, planned
313
+ against the same untouched tree. Pipe \`--json=script\`'s own output into
314
+ \`sh\` — never a bare \`gtd land | sh\`, which would hand an empty script to
315
+ \`sh\` on a refusal instead of stopping first.
316
+ 9. Check the settled/idle values read in step 8 — no second read needed to
317
+ decide. \`settled = true\` means stop right there (a no-op at a \`script\`
318
+ rest settles in place; nothing more to read); otherwise keep looping, and
319
+ the run finishes when a LATER \`gtd next --json=idle\` reports \`true\`
320
+ (read once more with plain \`gtd next\` only to show that gate's message,
321
+ the decision to stop already made). Exit code carries none of this any
322
+ more — every command exits \`0\` on success uniformly.
323
+ 10. A \`message\` beat whose \`--json=judge\` is non-empty is a JUDGE GATE — an
324
+ UNAWARE driver (obligation 3, above) already handles it correctly by
325
+ doing nothing special: display the message (which itself tells a human
326
+ to run \`gtd judge answer\`) and stop. An AWARE driver reads
327
+ \`--json=judge\`, pipes that rendered \`{ schema, state, questions }\` document to a
328
+ judge model, and pipes ITS verdict (one \`{ id, answer, p }\` per pending
329
+ question, as JSON on stdin) to \`gtd judge answer\` — the FIFTH command
330
+ that changes anything, alongside \`gtd land\`/\`gtd --workflow\`/\`gtd
331
+ abandon\`/\`gtd restore\`. It follows the exact same required-half /
332
+ optional-half script contract as \`gtd land\` in obligation 8 above:
333
+ read \`--json=script\` (and, if you want them, \`--json=settled\`/
334
+ \`--json=idle\` — \`gtd judge answer --json\` emits the same pinned 8-key
335
+ document \`gtd land --json=<path>\` does) and pipe the script into \`sh\`.
336
+ A malformed verdict (stdin isn't
337
+ JSON, or it names a question id this judgment never asked) exits 2 — a
338
+ caller-input error, retry with a corrected verdict, not obligation 3's
339
+ "stop" — before anything is planned.
340
+ 11. When the chosen agent CLI can restrict which skills a turn sees (\`pi\`
341
+ can; \`claude -p\` cannot), the built driver MUST pass each prompt turn's
342
+ \`--json=skills\` (read per entry, \`skills.0\`, \`skills.1\`, …) so the
343
+ turn sees only those. An absent field means none. A CLI that cannot
344
+ restrict relies on the preamble gtd already puts in the prompt — the
345
+ reference body's \`claude -p\` does exactly that.
346
+ 12. Map each prompt turn's \`--json=access.read\` / \`--json=access.write\`
347
+ (arrays of globs, \`null\` = unrestricted) onto the chosen CLI's sandbox
348
+ (Codex \`workspace-write\` plus writable roots) or a container with
349
+ \`:ro\` / \`:rw\` bind mounts. \`access\` is a prompt-only field. A CLI
350
+ that cannot restrict relies on the preamble and gtd's landing check. When
351
+ \`gtd land\` refuses naming an access violation, re-prompt the turn with
352
+ the refusal text and land again, or stop.
353
+ `
354
+
355
+ const RECOVERY = `
356
+ ## Recovery
357
+
358
+ gtd exiting 2 means nothing was even attempted — a usage error; exiting 1
359
+ means a refusal — nothing was emitted either. An emitted script exiting
360
+ non-zero when YOU run it means something MAY have partially happened. Both
361
+ recover the same way: re-invoke gtd (\`gtd next --json=kind\`, \`gtd next\`,
362
+ then \`gtd land --json=<path>\`). It re-reads the real repository state fresh
363
+ every time — never a cached plan — and emits whatever still needs to happen
364
+ from there. This works because every emitted script asserts its own HEAD
365
+ precondition, so a script generated against a repository state that has
366
+ since moved refuses loudly instead of corrupting anything. Scripts are
367
+ re-runnable and assert their own HEAD precondition — a driver needs no retry
368
+ logic beyond "ask gtd again".
369
+ `
370
+
371
+ const commandSuite = (): string =>
372
+ `
373
+ ## Building the user's command suite: interview first, then adapt
374
+
375
+ The obligations above are the contract; the three command bodies below are one
376
+ WORKED EXAMPLE of it. Do not copy them blindly, and do not guess the user's
377
+ setup — INVESTIGATE first, then INTERVIEW, then build a suite shaped by both.
378
+ Hold ONE conversation, not three: the setup steps below come first, because a
379
+ repo that is not set up yet cannot be driven, and the suite-shape questions
380
+ follow in the same numbered list.
381
+
382
+ 1. **Investigate the repository and ask the user what they want before
383
+ driving anything.** Look at what is already there (a \`.gtd/\` directory,
384
+ existing config, the project's own README/docs) and ask the user what
385
+ they're trying to accomplish with gtd here — don't assume a fresh
386
+ install or silently start a process on their behalf.
387
+ 2. **Make sure a \`gtd.config.ts\` governs the repository.** Run at the
388
+ repository root, \`gtd install\` writes one importing gtd's shipped
389
+ workflows when none exists yet (its own message says so). Repository
390
+ settings — steering-file modes, \`gtd ui\`, per-scope skills and access —
391
+ go in its \`config\` export; workflow settings are options of the
392
+ workflow factory it exports.
393
+ 3. **Commit the config before the first drive.** \`gtd install\` leaves the
394
+ file it writes uncommitted on purpose — an uncommitted config is a
395
+ pending change, so starting a process without committing it first turns
396
+ the config into the very first thing the process plans against. Commit
397
+ it, THEN start driving.
398
+ 4. **Which agent should run the turns?** Probe \`PATH\` for the known
399
+ coding-agent CLIs — \`claude\`, \`codex\`, \`gemini\`, \`cursor-agent\`,
400
+ \`aider\`, \`opencode\`, \`amp\` — show the user which are actually
401
+ installed, and ask which one to drive with (default: \`claude\`). Accept
402
+ a name that was not on the list; the probe is a convenience, not a
403
+ restriction. The chosen CLI's own flags then replace the \`claude\` lines
404
+ in the reference body below — its session flags (obligation 5), its
405
+ skill-restriction flag when it has one (obligation 11) and its
406
+ permission model (question 5). Carry over the body's \`export\` block too:
407
+ whatever the chosen CLI's own way is to **stop backgrounding commands and
408
+ let a foreground command run for hours** — a turn ends when the CLI
409
+ returns, so anything it leaves running dies unread and stalls the
410
+ process.
411
+ 5. **Under which permission model, and should the workflow's model hints be
412
+ honored at all?** Fully autonomous turns (e.g.
413
+ \`--dangerously-skip-permissions\`), a sandbox, or the agent's default
414
+ prompting — their risk tolerance, their call. Also ask whether the
415
+ workflow's own \`model\` hints (resolved in question 6 below) should be
416
+ honored (default: yes) — answering no means dropping \`--model\` entirely
417
+ from every prompt line and writing no model exports, so every turn runs
418
+ on the chosen CLI's own default tier.
419
+ 6. **If model hints are honored, resolve them to real model identifiers.**
420
+ The bundled workflow ships \`plannerModel: smart\` and \`coderModel: base\`.
421
+ **Those are opaque hints, not model names** — gtd never interprets them,
422
+ and passing \`--model smart\` to a real CLI fails on the first prompt
423
+ beat. Derive the chosen CLI's own available models at install time — its
424
+ \`--help\`, its docs, its own config — never a hardcoded table (a vendor
425
+ renames a model and a baked-in list goes stale the day it ships). **When
426
+ the heavier/cheaper mapping is not obvious, ask**: list the CLI's models
427
+ and let the user pick one per tier — the heavier tier for
428
+ \`plannerModel\` (triage, design, review), the cheaper tier for
429
+ \`coderModel\` (build, fix). Write the resolved names as
430
+ \`GTD_PLANNERMODEL\`/\`GTD_CODERMODEL\` exports at the top of \`gtd-build\`,
431
+ wrapped in \`# gtd-install: model exports\` / \`# gtd-install: end\`
432
+ markers — NOT into \`gtd.config.ts\`. They stay per machine and are never
433
+ committed, so one person's model choice binds nobody else on the repo.
434
+ Two consequences follow, both worth stating to the user: \`gtd-door\`
435
+ inherits these exports only because it \`exec\`s
436
+ \`gtd-build\` — anything that drives beats without going through
437
+ \`gtd-build\` sees the raw \`smart\`/\`base\` hints and fails; and \`GTD_*\` is
438
+ the highest-precedence config layer, so these exports silently win over
439
+ a \`plannerModel\`/\`coderModel\` a teammate later commits to
440
+ \`gtd.config.ts\`.
441
+ 7. **What should happen at the boundaries?** Where the log goes (obligation
442
+ 4), and whether halting at a human gate should do anything richer than
443
+ print — desktop notification, terminal-multiplexer status, editor focus —
444
+ which belongs in their wrapper, never in gtd.
445
+ 8. **How do they want to invoke it?** A command on PATH (default:
446
+ \`~/.local/bin/gtd-build\`), a project task-runner entry, a CI job step —
447
+ or no artifact at all: YOU drive the beats yourself, following the
448
+ obligations directly. Pick the runtime to match: bash, their language of
449
+ choice, anything that reads \`--json=<path>\` and spawns subprocesses.
450
+ 9. **Where should the suite live, which editor should \`gtd-edit\` spawn, and
451
+ does anything need renaming?** Suggest \`~/.local/bin\` for all three —
452
+ \`gtd-build\`, \`gtd-edit\`, \`gtd-door\` — and \`$EDITOR\` (never
453
+ a hardcoded editor) for the one that opens files. The interview can
454
+ rename any of the three; whatever name is chosen for \`gtd-build\` is the
455
+ RESOLVED path baked into \`GTD_BUILD\` in the \`gtd-door\` body below —
456
+ never the literal string \`gtd-build\`.
457
+
458
+ Then build it, and verify safely before the first real drive:
459
+ \`gtd next --json=<path>\`/\`gtd next\` are both pure reads — read/parse them,
460
+ check your kind dispatch against the table above, call them as often as you
461
+ like. Nothing happens until you run an emitted script.
462
+
463
+ The reference rendering in sh (no \`jq\`, no JSON parser at all — each
464
+ \`--json=<path>\` call prints its one value directly). \`gtd-door\` takes the
465
+ \`gtd-build\` path from one convention: \`GTD_BUILD\` is set once, at the top, to
466
+ the suite's resolved \`gtd-build\` path.
467
+
468
+ ### \`gtd-build\` — the driver loop
469
+
470
+ The \`claude\` lines are what interview answers 4 and 6 replace. Default path:
471
+ \`~/.local/bin/gtd-build\`.
472
+
473
+ \`\`\`bash
474
+ ${MINIMAL_DRIVER}
475
+ \`\`\`
476
+
477
+ ### \`gtd-edit\` — open the steering file the process is resting at
478
+
479
+ \`gtd next --json=file\` already reports the file for every state that
480
+ declares one — the edit command needs no new engine surface, just a read and
481
+ an editor spawn. \`file\` is an OPTIONAL field: \`--json=file\` prints nothing
482
+ (not the string \`null\`) at a state that declares no \`file:\`, so read it into
483
+ a shell variable and fall back explicitly — \`f="$(gtd next --json=file)"\`
484
+ then \`f="\${f:-.gtd/TODO.md}"\`, never assume the read populated anything. This
485
+ is also the general rule for every optional field \`--json=<path>\` exposes:
486
+ an absent field is silence, not the string \`null\` a \`jq .field\` recipe would
487
+ print on the same input — code that treats empty output as "field genuinely
488
+ absent" is correct, code that expects a literal \`null\` string is not.
489
+
490
+ This is also how a human starts a process at all: at the initial \`idle\`
491
+ state the resting file IS \`.gtd/TODO.md\`, so on a clean repository the edit
492
+ command opens the empty scratch file, and whatever the human writes there is
493
+ the first sketch the whole process gets planned from. The same command does
494
+ the same thing at every rest, whether or not a state declares a \`file:\`.
495
+
496
+ The edit command stops when the editor exits — it never drives the loop. It
497
+ opens a file and returns; the human runs \`gtd-build\` themselves when they
498
+ are ready. Default path: \`~/.local/bin/gtd-edit\`.
499
+
500
+ \`\`\`bash
501
+ ${EDIT_COMMAND}
502
+ \`\`\`
503
+
504
+ ### \`gtd-door <name> [args...]\` — start a process through a door and drive it
505
+
506
+ Runs \`gtd door <name> [args...]\`, captured by command substitution — never a
507
+ pipe: a pipeline reports only its LAST command's exit status, so
508
+ \`gtd door ... | sh\` under \`set -e\` would sail past a refusal and \`exec\` a
509
+ loop over a process that was never started. It runs the captured script, then
510
+ \`exec\`s the suite's RESOLVED \`gtd-build\` path — never the literal string
511
+ \`gtd-build\`. This is one invocation that carries the process all the way to
512
+ its next human gate, not just a starter: \`gtd-door review\` on a RED baseline
513
+ hands off to \`gtd-build\`, which halts at the blocked gate and prints it,
514
+ rather than starting the review; \`gtd-door fix\` on a GREEN suite is a no-op
515
+ straight back to \`idle\` — the exec'd \`gtd-build\` exits immediately on it.
516
+ \`gtd doors\` lists the doors a repository offers (those of the workflows its
517
+ config exports — the bundled \`fix\` and \`review [base]\` — plus any it
518
+ declares). Refuses with
519
+ \`usage: gtd-door <name> [args...]\` on stderr and exit \`2\` when the name is
520
+ missing — nothing was even attempted. Default path: \`~/.local/bin/gtd-door\`.
521
+
522
+ \`\`\`bash
523
+ ${DOOR_COMMAND}
524
+ \`\`\`
525
+ ${REINSTALL}`
526
+
527
+ const REINSTALL = `
528
+ ## Re-installing: detect and adapt, don't blindly overwrite
529
+
530
+ A second install on a machine that already has a suite is not a fresh
531
+ install — read each of the three suite paths before writing anything
532
+ (\`gtd-build\`, \`gtd-edit\`, \`gtd-door\`), and branch per path:
533
+
534
+ - Absent — install it, as in a fresh install.
535
+ - Present and content-equal to what this gtd version would emit — say so,
536
+ change nothing, and skip that command's interview questions entirely.
537
+ - Present and different — show the difference, name the likely cause (a gtd
538
+ upgrade, or the user's own edit), and ask before overwriting. Never
539
+ silently replace a file the user may have customised.
540
+ - Unreadable, or present as a directory instead of a file — report it and
541
+ ask about it, the same as a differing file. Never overwritten.
542
+
543
+ Drift is detected by comparing CONTENT, never by parsing a version out of
544
+ the installed file — an installed command carries no version marker to
545
+ parse.
546
+
547
+ \`gtd-build\` needs one exemption before that comparison: the region between
548
+ \`# gtd-install: model exports\` and \`# gtd-install: end\` gets stripped from
549
+ the installed file first. A \`gtd-build\` differing only inside those
550
+ markers is unchanged, and re-asks nothing — the resolved model names are
551
+ per machine, so without this exemption every single re-install would
552
+ report drift on the one command everybody has.
553
+
554
+ This check scopes to exactly the three suite paths above, plus the superseded
555
+ pair below. \`gtd-loop\` is
556
+ outside it entirely — never read, never diffed, never touched by it in any
557
+ way — an existing \`gtd-loop\` survives untouched beside the new
558
+ \`gtd-build\`, and cleaning it up is the human's own call.
559
+
560
+ \`gtd-fix\` and \`gtd-review\` are superseded by \`gtd-door\` (\`gtd-door fix\`,
561
+ \`gtd-door review [base]\`). When either is still present beside the suite,
562
+ name it to the user as superseded and ask before removing it — never remove
563
+ one unasked.
564
+ `
565
+
566
+ const EDITOR_INTEGRATION = `
567
+ ## Editor integration: offer it, don't bury it
568
+
569
+ \`gtd lsp\` runs a language server, started by \`gtd lsp\` itself, speaking LSP
570
+ over \`stdio\`, applying to files under \`.gtd/\`. That is the whole integration
571
+ contract, identical for every editor — this briefing names no specific editor
572
+ and carries no per-editor config recipe.
573
+
574
+ Find the user's editor from their OWN shell configuration, never a guess:
575
+ check \`$EDITOR\`/\`$VISUAL\` first, then look for an alias or export in their
576
+ shell rc files (\`.zshrc\`, \`.bashrc\`, \`.config/fish/config.fish\`). Ask the
577
+ user outright when that turns up nothing or turns up more than one. When the
578
+ shell configuration names no editor and the user names none either, say
579
+ nothing and move on — don't pitch editor integration to somebody with no
580
+ editor to integrate.
581
+
582
+ Once an editor is identified, look ITS OWN LSP configuration format up
583
+ yourself — there is no bundled recipe for it. If it is LSP-capable, offer to
584
+ wire \`gtd lsp\` up with a brief explanation (three or four lines, not a copy
585
+ of \`docs/setup.md\`): live diagnostics on \`.gtd/\` steering files, an outline
586
+ of what is left to review, click-to-check review hunks, pick-an-option
587
+ actions on open questions, and a \`gtd.openSteeringFile\` command that jumps
588
+ to the current state's file. Two facts belong in that offer because both
589
+ bite on a fresh repo: \`gtd lsp\` never creates \`.gtd/\`, so
590
+ \`gtd.openSteeringFile\` may point at a path that does not exist yet before
591
+ the first sketch; and \`gtd lsp\` needs no repository root — it is one of the
592
+ commands that skips the root guard.
593
+
594
+ On yes, edit the editor's own config file yourself — never print a snippet
595
+ and walk away. Integration works without the human touching anything. Three
596
+ guards apply to that write, because it lands outside the repository in a
597
+ file the user shares across every project:
598
+
599
+ - **Ask first, per editor, naming the exact file** about to change. Silence
600
+ is not consent for a machine-wide config.
601
+ - **Merge, never overwrite.** Read the existing config, add only the gtd
602
+ language-server entry, and leave every other key byte-identical.
603
+ - **Skip and report when the entry is already present** — a second install
604
+ must not append a duplicate server registration. A malformed existing
605
+ config (unparseable JSON, a TOML syntax error) is a stop-and-report, not a
606
+ rewrite: name the file that could not be parsed and leave it untouched.
607
+ `
608
+
609
+ const PREREQUISITES = `
610
+ ## Prerequisites and portability
611
+
612
+ - A POSIX \`sh\` (dash, ash, bash's own POSIX mode, etc.) — gtd's own emitted
613
+ scripts (\`gtd land --json=script\`, \`gtd --workflow <name>\`, \`gtd abandon\`,
614
+ \`gtd restore\`, \`gtd judge answer --json=script\`) are POSIX sh; captured,
615
+ then piped into it (see obligation 8 above). Reading
616
+ \`gtd next --json=<path>\`/\`gtd land --json=<path>\`'s own output needs
617
+ nothing beyond the same POSIX \`sh\` — no \`eval\`, no parser.
618
+ - \`gtd\` on \`PATH\` — a seeded mode \`validate:\` command is literally the
619
+ string \`gtd check <mode> '<file>'\`, resolved by NAME at script-run time.
620
+ Keep one \`gtd\` on \`PATH\`, consistently.
621
+
622
+ Any runtime works: the contract is \`--json\`/\`--json=<path>\` in, subprocesses
623
+ out.
624
+ `
625
+
626
+ export const renderBriefing = (): string =>
627
+ HEADER() +
628
+ BEAT_PROTOCOL +
629
+ JSON_FIELD_REFERENCE +
630
+ DRIVER_OBLIGATIONS +
631
+ RECOVERY +
632
+ commandSuite() +
633
+ EDITOR_INTEGRATION +
634
+ PREREQUISITES
@@ -0,0 +1,27 @@
1
+ import { existsSync } from "node:fs"
2
+ import { createRequire } from "node:module"
3
+ import { dirname, join } from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ const _require = createRequire(import.meta.url)
7
+
8
+ // A source-relative literal (`../../package.json`) breaks the built single-file
9
+ // bundle: bundling doesn't rewrite `import.meta.url` per original module, so a
10
+ // hardcoded depth only matches whichever depth this file happens to sit at in
11
+ // dist/ vs src/ — they differ (dist/gtd.bundle.mjs is always one level below
12
+ // repo root; src/install/Version.ts is two). Walking up from wherever this module
13
+ // actually runs from resolves correctly in both the unbundled (vitest/tsc) and
14
+ // bundled (dist/gtd.bundle.mjs) case.
15
+ const findPackageJson = (fromUrl: string): string => {
16
+ let dir = dirname(fileURLToPath(fromUrl))
17
+ for (let i = 0; i < 5; i++) {
18
+ const candidate = join(dir, "package.json")
19
+ if (existsSync(candidate)) return candidate
20
+ dir = dirname(dir)
21
+ }
22
+ throw new Error(`package.json not found by walking up from ${fromUrl}`)
23
+ }
24
+
25
+ export const GTD_VERSION: string = (
26
+ _require(findPackageJson(import.meta.url)) as { version: string }
27
+ ).version
@@ -0,0 +1,3 @@
1
+ export { renderBriefing } from "./Install.js"
2
+ export { CONFIG_SCAFFOLD } from "./ConfigScaffold.js"
3
+ export { GTD_VERSION } from "./Version.js"