@pmelab/gtd 7.6.3 → 7.7.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.
package/README.md CHANGED
@@ -108,7 +108,9 @@ handful of commands drive it:
108
108
  elsewhere shows up without a manual refresh.
109
109
  - **`gtd edit [path]`** — open `<path>` (or, with no argument, the current
110
110
  resting state's steering file, or the repo dir) in `${VISUAL:-$EDITOR}`,
111
- blocking until it exits.
111
+ blocking until it exits. Low-level plumbing — for "force an edit at the
112
+ current human gate, then keep driving", pass `--edit` to the loop instead (see
113
+ below).
112
114
 
113
115
  `gtd version` (or `gtd --version`/`-v`) prints the installed version and exits;
114
116
  `gtd help` (or `gtd --help`/`-h`) prints the command list. Both short-circuit
@@ -181,8 +183,12 @@ code), waits for you to save and exit, then captures your edit itself and keeps
181
183
  driving — or halts if you left nothing changed — so you never run
182
184
  `gtd step human` by hand. Pass `--no-edit` (or set `GTD_NO_EDIT`) to fall back
183
185
  to halting and printing the gate instead, if you'd rather edit and re-launch it
184
- yourself. Bare `gtd` prints one line per event — colored and emoji on a real
185
- terminal, plain ASCII under `NO_COLOR` or when piped — and redirects the noisier
186
+ yourself; pass `--edit`/`-e` to force the editor open at the gate right now,
187
+ overriding an ambient `GTD_NO_EDIT`/`--no-edit`. Pass `--once` to restrict a run
188
+ to exactly one beat — one human gate, one check, or one agent turn — instead of
189
+ driving all the way to idle; it combines freely with `--edit`/`--no-edit`. Bare
190
+ `gtd` prints one line per event — colored and emoji on a real terminal, plain
191
+ ASCII under `NO_COLOR` or when piped — and redirects the noisier
186
192
  agent/check/step subprocess output to a per-repo/per-worktree log file.
187
193
  `gtd log` opens that logfile in your editor. See
188
194
  [Driving the loop](docs/loop.md).
package/bin/gtd CHANGED
@@ -3,21 +3,29 @@ set -euo pipefail
3
3
 
4
4
  # This file is the single published `bin` entry: `bin/gtd`. Before anything
5
5
  # else, it dispatches on $1:
6
- # - `--no-edit` (bare, or immediately after `loop`) -> stripped here and
7
- # disables the loop's automatic editor launching for this run (see
8
- # `edit_disabled`/`GTD_NO_EDIT` below); dispatch then continues on the
9
- # remaining arguments as if it had never been given.
6
+ # - `--no-edit` / `--edit` (`-e`) / `--once` (bare, or immediately after
7
+ # `loop`, in any order/combination — see the flag-parsing block below) ->
8
+ # stripped here; `--no-edit` disables the loop's automatic editor
9
+ # launching for this run, `--edit` forces it on for this run (overriding
10
+ # an ambient `GTD_NO_EDIT`/`--no-edit` — the two are mutually exclusive),
11
+ # and `--once` restricts the loop to exactly one beat (one human gate, one
12
+ # script check+step, or one agent prompt+step) before exiting. Dispatch
13
+ # then continues on the remaining arguments as if none of them had ever
14
+ # been given.
10
15
  # - `edit [path]` -> handled entirely here in bash, never forwarded to the
11
16
  # bundle: opens `${VISUAL:-$EDITOR}` on `path`, or (no `path` given) on the
12
- # resolved rest's `.file` (or `.` when absent).
17
+ # resolved rest's `.file` (or `.` when absent). Low-level plumbing now that
18
+ # `--edit` covers the common "force an edit, then keep driving" case; this
19
+ # subcommand remains for opening a specific path, or the current rest's
20
+ # file, WITHOUT driving the loop afterward.
13
21
  # - `log` -> handled entirely here in bash, never forwarded to the bundle:
14
22
  # opens `${VISUAL:-$EDITOR}` on the current repo/worktree's loop logfile
15
23
  # (see `resolve_log_path`), refusing rather than opening an empty buffer
16
24
  # when no loop has produced one yet.
17
- # - no arguments, or exactly `loop` with no further arguments -> fall through
18
- # into the loop body below (the loop protocol comment further down).
19
- # - `loop` with additional arguments (other than a lone `--no-edit`) ->
20
- # usage error, exit 2.
25
+ # - no arguments, or exactly `loop` with no further arguments (other than the
26
+ # flags above) -> fall through into the loop body below (the loop protocol
27
+ # comment further down).
28
+ # - `loop` with an unrecognised extra argument -> usage error, exit 2.
21
29
  # - anything else -> hand off to the bundle via `run_gtd`, forwarding all
22
30
  # arguments unchanged (e.g. `bin/gtd status`, `bin/gtd step human`).
23
31
  # `run_gtd` is the ONE place that knows how to invoke "the bundle": by default
@@ -206,19 +214,59 @@ resolve_log_path() {
206
214
  fi
207
215
  }
208
216
 
209
- # --no-edit / GTD_NO_EDIT govern only the loop's automatic editor launching at
210
- # human gates (see handle_human_gate further down) — `gtd edit` itself never
211
- # reads either. `--no-edit` is recognised in exactly the two sanctioned
212
- # positions (bare, or right after `loop`) and stripped before anything below
213
- # looks at "$@", so the bundle's own unknown-option guard never sees it.
217
+ # --no-edit / --edit (-e) / --once govern only the loop driver, recognised in
218
+ # exactly two positions — bare (e.g. `gtd --edit --once`) or immediately after
219
+ # `loop` (e.g. `gtd loop --once`) — in any order, each at most once. `--edit`
220
+ # and `--no-edit` are mutually exclusive (forcing and suppressing the editor
221
+ # in the same run is not a coherent request). A recognised, valid combination
222
+ # is stripped here before anything below looks at "$@", so the bundle's own
223
+ # unknown-option guard never sees it; anything else (an unknown flag, a
224
+ # duplicate/conflicting one, or a flag mixed with a real subcommand) is left
225
+ # untouched and falls through to the existing dispatch below, which forwards a
226
+ # non-flag first argument to the bundle (whose own unknown-option guard
227
+ # rejects it) or rejects a `loop` invocation carrying an unrecognised extra
228
+ # argument.
214
229
  edit_disabled=0
230
+ edit_forced=0
231
+ once_mode=0
215
232
  [[ -n "${GTD_NO_EDIT:-}" ]] && edit_disabled=1
216
- if [[ "${1:-}" == "--no-edit" && $# -eq 1 ]]; then
217
- edit_disabled=1
218
- shift
219
- elif [[ "${1:-}" == "loop" && "${2:-}" == "--no-edit" && $# -eq 2 ]]; then
220
- edit_disabled=1
221
- set -- loop
233
+
234
+ if [[ "${1:-}" == "loop" ]]; then
235
+ loop_form=1
236
+ loop_flag_args=("${@:2}")
237
+ else
238
+ loop_form=0
239
+ loop_flag_args=("$@")
240
+ fi
241
+
242
+ valid_combo=1
243
+ n_no_edit=0
244
+ n_edit=0
245
+ n_once=0
246
+ for a in ${loop_flag_args[@]+"${loop_flag_args[@]}"}; do
247
+ case "$a" in
248
+ --no-edit) n_no_edit=$((n_no_edit + 1)) ;;
249
+ --edit | -e) n_edit=$((n_edit + 1)) ;;
250
+ --once) n_once=$((n_once + 1)) ;;
251
+ *) valid_combo=0 ;;
252
+ esac
253
+ done
254
+ if ((n_no_edit > 1 || n_edit > 1 || n_once > 1 || (n_no_edit >= 1 && n_edit >= 1))); then
255
+ valid_combo=0
256
+ fi
257
+
258
+ if [[ "$valid_combo" == 1 ]]; then
259
+ ((n_no_edit >= 1)) && edit_disabled=1
260
+ ((n_edit >= 1)) && edit_forced=1
261
+ ((n_once >= 1)) && once_mode=1
262
+ # --edit overrides an ambient GTD_NO_EDIT/--no-edit for THIS run — the
263
+ # explicit ask to force the editor wins over the suppressing default.
264
+ ((edit_forced)) && edit_disabled=0
265
+ if [[ "$loop_form" == 1 ]]; then
266
+ set -- loop
267
+ else
268
+ set --
269
+ fi
222
270
  fi
223
271
 
224
272
  if [[ "${1:-}" == "edit" ]]; then
@@ -382,8 +430,12 @@ Fix the reported issue and re-run gtd-loop."
382
430
  exit 0
383
431
  fi
384
432
  # A new commit was captured — render it, then return normally so the
385
- # caller keeps driving.
433
+ # caller keeps driving, unless --once says this one human gate is the whole
434
+ # run.
386
435
  report_commits "$head_before"
436
+ if [[ "$once_mode" == 1 ]]; then
437
+ exit 0
438
+ fi
387
439
  }
388
440
 
389
441
  trap 'code=$?; if [ "$code" -ne 0 ]; then
@@ -456,7 +508,10 @@ run_agent_turn() {
456
508
  # (open the editor there first, THEN step) instead of blindly stepping human
457
509
  # and moving on. Editing OFF: unchanged from before this function existed —
458
510
  # step silently, refusal surfaced and halts, a clean/committed gate just falls
459
- # into the loop below.
511
+ # into the loop below. Either way, --once treats a captured commit here as the
512
+ # run's one beat and exits (see handle_human_gate's own once-mode exit for the
513
+ # editing-ON path); a clean/no-op gate captures nothing, so it isn't a beat —
514
+ # it falls into the loop below exactly as it would without --once.
460
515
  if ! peek_json="$(run_gtd next --json 2>&1)"; then
461
516
  emit_error "could not determine the next step:
462
517
  $peek_json
@@ -464,8 +519,10 @@ Run \`gtd status\` to inspect the repo."
464
519
  emit_log_pointer
465
520
  exit 1
466
521
  fi
467
- if [[ "$(jq -r .kind <<<"$peek_json")" == "message" ]]; then
522
+ peek_kind="$(jq -r .kind <<<"$peek_json")"
523
+ if [[ "$peek_kind" == "message" ]]; then
468
524
  if [[ "$edit_disabled" == 1 ]]; then
525
+ opening_head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
469
526
  if ! step_out="$(run_gtd step human 2>&1)"; then
470
527
  emit_error "could not capture your changes at the human gate:
471
528
  $step_out
@@ -473,9 +530,21 @@ Fix the reported issue and re-run gtd-loop."
473
530
  emit_log_pointer
474
531
  exit 1
475
532
  fi
533
+ if [[ "$once_mode" == 1 && "$opening_head_before" != "$(git rev-parse HEAD 2>/dev/null || echo none)" ]]; then
534
+ exit 0
535
+ fi
476
536
  else
477
537
  handle_human_gate "$peek_json"
478
538
  fi
539
+ elif [[ "$edit_forced" == 1 ]]; then
540
+ # Honesty caveat: a forced edit only means something at a human gate — the
541
+ # machine isn't resting at one right now, so say so instead of pretending,
542
+ # and just keep driving.
543
+ if [[ "$FANCY" == 1 ]]; then
544
+ printf '%sℹ️ --edit: not at a human gate yet — continuing to drive%s\n' "$C_DIM" "$C_RESET"
545
+ else
546
+ printf '%s--edit: not at a human gate yet — continuing to drive%s\n' "$C_DIM" "$C_RESET"
547
+ fi
479
548
  fi
480
549
 
481
550
  while true; do
@@ -527,6 +596,9 @@ while true; do
527
596
  fi
528
597
  prev_state=""
529
598
  prev_content=""
599
+ if [[ "$once_mode" == 1 ]]; then
600
+ exit 0
601
+ fi
530
602
  continue
531
603
  fi
532
604
 
@@ -610,4 +682,7 @@ Stopping rather than stepping with a malformed steering file."
610
682
  # zero commits here — report_commits returns non-zero, and that's fine: the
611
683
  # NEXT iteration's stall check is what catches unchanged state/content.
612
684
  report_commits "$head_before" || true
685
+ if [[ "$once_mode" == 1 ]]; then
686
+ exit 0
687
+ fi
613
688
  done
package/docs/cli.md CHANGED
@@ -48,7 +48,9 @@ Commands:
48
48
  edit [path] Open <path> (repo-relative) in $VISUAL/$EDITOR, blocking
49
49
  until the editor exits. With no argument, opens the
50
50
  resolved rest's declared file (or the repo root if it
51
- declares none). Never reads --no-edit/GTD_NO_EDIT
51
+ declares none). Never reads --no-edit/--edit/GTD_NO_EDIT.
52
+ Low-level plumbing — --edit covers the common "force an
53
+ edit, then keep driving" case
52
54
  status Print the resolved rest's state/actor and which declared
53
55
  pattern (if any) each pending change matches (no mutation)
54
56
  validate Format and validate the steering file the resolved rest
@@ -69,6 +71,13 @@ Options:
69
71
  --model=<name> (gtd step only, with --cost) tag that cost's model
70
72
  --no-edit (bare gtd or gtd loop only) disable the loop's automatic
71
73
  editor launching at human gates
74
+ --edit, -e (bare gtd or gtd loop only) force the editor open at the
75
+ current human gate, overriding --no-edit/GTD_NO_EDIT; a
76
+ no-op note (not an error) when not at a human gate
77
+ --once (bare gtd or gtd loop only) run exactly one loop beat (one
78
+ human gate, one script check+step, or one agent
79
+ prompt+step), then exit — combines freely with
80
+ --edit/--no-edit
72
81
  --version, -v Print version and exit
73
82
  --help, -h Print this help and exit
74
83
  ```
@@ -92,25 +101,52 @@ to plain-text mode. A bare `--cost`/`--model` with no value, a non-numeric or
92
101
  negative `--cost`, an empty `--model`, `--model` without `--cost`, or either
93
102
  flag on any command other than `gtd step` are all usage errors.
94
103
 
95
- `--no-edit` is a separate, bash-level flag handled entirely by `bin/gtd` itself,
96
- stripped before anything reaches the bundle — see [`--no-edit`](#--no-edit)
104
+ `--no-edit`, `--edit`/`-e`, and `--once` are separate, bash-level flags handled
105
+ entirely by `bin/gtd` itself, stripped before anything reaches the bundle — see
97
106
  below.
98
107
 
99
- ## `--no-edit`
100
-
101
- A flag on the loop driver only — bare `gtd --no-edit` or `gtd loop --no-edit`
102
- (the two are equivalent, since bare `gtd` and `gtd loop` are themselves
103
- equivalent). Recognized in exactly those two positions; any other
104
- placement/combination (e.g. `gtd step --no-edit`, or combined with any other
105
- argument) is a usage error. The `GTD_NO_EDIT` environment variable (any
106
- non-empty value) does the same thing.
107
-
108
- Disables the loop's default behavior of launching an editor at human gates,
109
- restoring the previous halt-and-print-and-exit behavior with no editor involved.
110
- It governs only the loop's own automatic launching — `gtd edit` invoked directly
111
- never reads it (see [`gtd edit`](#gtd-edit-path) above). See
112
- [Driving the loop](loop.md) and `skills/loop/SKILL.md` for the full gate-flow
113
- description.
108
+ ## `--no-edit`, `--edit`, `--once`
109
+
110
+ Three flags on the loop driver only (`--edit` also has the short form `-e`),
111
+ each recognized bare (`gtd --no-edit`) or immediately after `loop`
112
+ (`gtd loop --no-edit`) — the two positions are equivalent, since bare `gtd` and
113
+ `gtd loop` are themselves equivalent. All three combine freely with each other,
114
+ in any order (e.g. `gtd --edit --once`, `gtd loop --once --edit`), except
115
+ `--edit`/`--no-edit` together, which conflict (forcing and suppressing the
116
+ editor in the same run is not a coherent request). Any other placement (e.g.
117
+ `gtd step --once`), a duplicated flag (`gtd --once --once`), or the
118
+ `--edit`/`--no-edit` conflict is a usage error, surfaced via whatever guard the
119
+ misplaced argument first reaches (the compiled bundle's own
120
+ unknown-option/unknown-command rejection for a misplaced flag, or
121
+ `gtd: 'loop' takes no arguments` for one misplaced after `loop`) — never
122
+ silently ignored or silently resolved one way.
123
+
124
+ **`--no-edit`** (or the `GTD_NO_EDIT` environment variable, any non-empty value)
125
+ disables the loop's default behavior of launching an editor at human gates,
126
+ restoring the halt-and-print-and-exit behavior with no editor involved.
127
+
128
+ **`--edit`** (`-e`) is `--no-edit`'s mirror: it forces the editor open at the
129
+ current human gate, overriding an ambient `GTD_NO_EDIT`/`--no-edit` default for
130
+ this one run. It is only meaningful when the machine currently rests at a human
131
+ gate — the honesty caveat: at any other rest, forcing an edit is undefined, so
132
+ `bin/gtd` prints a note that it isn't at a human gate yet and simply keeps
133
+ driving, rather than pretending to force something. Both flags govern only the
134
+ loop's own automatic launching — `gtd edit` invoked directly never reads either
135
+ (see [`gtd edit`](#gtd-edit-path) above).
136
+
137
+ **`--once`** restricts the loop to exactly one beat — one human gate, one script
138
+ check+step, or one agent prompt+step — then exits, instead of driving all the
139
+ way back to idle/settled. Precisely: at most one commit/transition is made;
140
+ whichever of the three kinds the currently-resolved rest is, that one gets
141
+ processed (including any internal `gtd validate` fix-reprompt rounds a producing
142
+ agent turn needs — those are all part of finishing the ONE prompt beat, not
143
+ separate beats) and the loop exits 0 immediately after, without re-peeking at
144
+ what comes next. A clean human gate with nothing to capture (the opening move's
145
+ silent capture attempt under `--no-edit`, or a no-op editor session) is not a
146
+ beat and falls through to the driver's ordinary halt, same as without `--once`.
147
+
148
+ See [Driving the loop](loop.md) and `skills/loop/SKILL.md` for the full
149
+ gate-flow description.
114
150
 
115
151
  ## `gtd init`
116
152
 
@@ -436,6 +472,12 @@ which are JSON-only. `--json` emits
436
472
 
437
473
  ## `gtd edit [path]`
438
474
 
475
+ Low-level plumbing: for the common case of "force an edit at the current human
476
+ gate, then keep driving the loop", use `--edit` (below) instead. `gtd edit`
477
+ itself only opens an editor on a path — it never drives anything afterward,
478
+ which makes it the right tool for opening a SPECIFIC path (or peeking at the
479
+ current rest's file) without starting a loop.
480
+
439
481
  Handled entirely in `bin/gtd`'s own bash — unlike every other subcommand, it is
440
482
  **never forwarded** to the compiled bundle (`dist/gtd.bundle.mjs`).
441
483
 
@@ -457,8 +499,8 @@ gtd edit: could not determine the next step:
457
499
  <gtd next --json's own error output>
458
500
  ```
459
501
 
460
- `gtd edit` **never** reads `--no-edit`/`GTD_NO_EDIT` — those two govern only the
461
- loop's own automatic editor launching at human gates (see
502
+ `gtd edit` **never** reads `--no-edit`/`--edit`/`GTD_NO_EDIT` — those govern
503
+ only the loop's own automatic editor launching at human gates (see
462
504
  [Driving the loop](loop.md) and `skills/loop/SKILL.md`), not this command.
463
505
  Invoked directly, `gtd edit` always launches, unconditionally.
464
506
 
package/docs/loop.md CHANGED
@@ -66,11 +66,25 @@ the state declares none), blocking until the editor exits, then runs
66
66
  exactly as a manual `gtd step human` refusal would.
67
67
 
68
68
  Set `GTD_NO_EDIT` (any non-empty value), or pass `--no-edit` — see
69
- [`cli.md`'s `--no-edit`](cli.md#--no-edit) for its exact syntax and positioning
69
+ [`cli.md`](cli.md#--no-edit---edit---once) for its exact syntax and positioning
70
70
  — to disable this and restore the original halt-and-print behavior: the loop
71
71
  prints the gate via `gtd next` and exits 0 without touching an editor, leaving
72
72
  the human to edit and re-launch the loop themselves.
73
73
 
74
+ Pass `--edit` (`-e`) to force the editor open at the current human gate right
75
+ now, overriding an ambient `GTD_NO_EDIT`/`--no-edit` for this one run — useful
76
+ when a shell profile sets `GTD_NO_EDIT` by default but you want to edit
77
+ interactively just this once. It only means something when the machine is
78
+ actually resting at a human gate: at any other rest, `bin/gtd` prints a note
79
+ that it isn't at one yet and simply keeps driving, rather than pretending to
80
+ force something undefined.
81
+
82
+ Pass `--once` to restrict the loop to exactly one beat — one human gate, one
83
+ script check+step, or one agent prompt+step — then exit, instead of driving all
84
+ the way to idle/settled. It combines freely with `--edit`/`--no-edit` (e.g.
85
+ `gtd --edit --once` forces the editor at the current gate, captures the edit,
86
+ and stops right there rather than continuing to drive).
87
+
74
88
  ```bash
75
89
  gtd next --json # ask who's up and what they should do
76
90
  ```
@@ -92,18 +106,21 @@ when the machine rests at a `"message"` gate — so `gtd` is the only command a
92
106
  human runs. `bin/gtd` is the packaged entry point: invoked bare, or with `loop`
93
107
  as its first argument, it runs this exact script; any other first argument (e.g.
94
108
  `next`, `step`, `status`) hands off to `node dist/gtd.bundle.mjs` instead. The
95
- loop body it runs adds five things on top of the reference script above: it
96
- opens an editor at every `"message"` gate instead of just printing it (see
97
- "Editing is on by default" above; `--no-edit`/`GTD_NO_EDIT` fall back to the
98
- reference script's plain print-and-exit); it stops with a diagnostic if the same
99
- `"prompt"` state/content repeat with no progress (see `skills/loop/SKILL.md`'s
100
- "Stall detection"); it lets `GTD_LOOP_AGENT_CMD` swap in any coding agent CLI in
101
- place of the default `claude -p`, receiving the prompt via `$GTD_LOOP_PROMPT`;
102
- it exports the resolved state's optional `model` hint as `$GTD_LOOP_MODEL`,
103
- appending `--model "$GTD_LOOP_MODEL"` to the default `claude -p` invocation
104
- whenever it's non-empty; and it acts on the optional `memory` scope hint (see
105
- "Agent memory scope" below) — continuing one agent session across consecutive
106
- same-scope turns and starting fresh when the scope changes.
109
+ loop body it runs adds six things on top of the reference script above: it opens
110
+ an editor at every `"message"` gate instead of just printing it (see "Editing is
111
+ on by default" above; `--no-edit`/`GTD_NO_EDIT` fall back to the reference
112
+ script's plain print-and-exit, while `--edit`/`-e` forces it on for this run
113
+ even over an ambient `GTD_NO_EDIT`); it can restrict itself to exactly one beat
114
+ via `--once` instead of driving to idle/settled; it stops with a diagnostic if
115
+ the same `"prompt"` state/content repeat with no progress (see
116
+ `skills/loop/SKILL.md`'s "Stall detection"); it lets `GTD_LOOP_AGENT_CMD` swap
117
+ in any coding agent CLI in place of the default `claude -p`, receiving the
118
+ prompt via `$GTD_LOOP_PROMPT`; it exports the resolved state's optional `model`
119
+ hint as `$GTD_LOOP_MODEL`, appending `--model "$GTD_LOOP_MODEL"` to the default
120
+ `claude -p` invocation whenever it's non-empty; and it acts on the optional
121
+ `memory` scope hint (see "Agent memory scope" below) — continuing one agent
122
+ session across consecutive same-scope turns and starting fresh when the scope
123
+ changes.
107
124
 
108
125
  `bin/gtd` resolves both its bundle hand-off and the loop's own internal `gtd`
109
126
  calls against `dist/gtd.bundle.mjs` next to its own location. Set `GTD_BIN` — a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "7.6.3",
3
+ "version": "7.7.0",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {