@pmelab/gtd 8.4.0 → 8.5.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 (4) hide show
  1. package/README.md +79 -55
  2. package/bin/gtd +1 -1
  3. package/dist/gtd.bundle.mjs +3701 -2823
  4. package/package.json +11 -9
package/README.md CHANGED
@@ -6,10 +6,10 @@
6
6
  **Chat is a terrible source of truth. Git isn't.**
7
7
 
8
8
  **gtd** is a git-aware CLI that derives the entire agentic workflow — capture,
9
- plan, build, test, review — from your repository state, and prints the next
10
- prompt for whatever agent you point at it. Every step is a commit. Tests are run
11
- by the tool and branched on by exit code, so the agent never grades its own
12
- homework.
9
+ plan, build, check, review — from your repository state, and prints the next
10
+ prompt for whatever agent you point at it. Every turn is a commit. The test
11
+ command is printed for your driver to run, and the branch is decided by what the
12
+ run leaves in the tree — so the agent never grades its own homework.
13
13
 
14
14
  No chat scrollback. No lost sessions. No infinite fix loops. Just git.
15
15
 
@@ -120,31 +120,31 @@ for). The two steering-file entries are chosen by which file you create:
120
120
  Both flows converge on the same tail: an agent hands you a `.gtd/REVIEW.md`
121
121
  checkbox review of the diff — the prompt never inlines the diff itself; it names
122
122
  the commit the changes are based at and the agent runs `git diff` to read the
123
- range before writing the review. While the cycle rests at that gate, gtd opens a
124
- **review checkout window**: HEAD is rewound to the review base with the working
125
- tree untouched, so the whole reviewable change shows up as ordinary uncommitted
126
- changes in your editor's normal git integration (and files added during the
127
- cycle show up as ordinary untracked files, so discarding one deletes it). The
128
- window closes on the next gtd invocation. Tick a box as you review each hunk
129
- (ticking just records "I read this"), and leave a **comment** to request
130
- changes: a note on a line, an inline `// TODO`-style comment in the code, or a
131
- direct code edit. Any comment sends a build + re-review round — an agent first
132
- turns your comments into an explicit instruction list, then a build turn
133
- implements it (a re-review then covers only the follow-through, and a hand-edit
134
- is treated as your own fix the agent completes without reverting your lines; a
135
- comment can't be silently dropped — a build turn that addresses nothing is
136
- refused). For the simple flow, the build turn that follows through on feedback
137
- and the turn that drafts the final squash message both resume the same session
138
- that built the feature in the first place, since the review tail is nested
139
- inside that build identity rather than sitting beside it. Ticking every box with
140
- no comment is the sign-off, which collapses the whole cycle into one commit (a
141
- **squash finale** whose message an agent drafts). Stepping with a box still
142
- unticked and no comment is refused (finish reviewing first), as is deleting
143
- `.gtd/REVIEW.md`.
123
+ range before writing the review. While the process rests at that gate, gtd opens
124
+ a **review checkout window**: HEAD is rewound to the review base with the
125
+ working tree untouched, so the whole reviewable change shows up as ordinary
126
+ uncommitted changes in your editor's normal git integration (and files added
127
+ during the process show up as ordinary untracked files, so discarding one
128
+ deletes it). The window closes on the next gtd invocation. Tick a box as you
129
+ review each hunk (ticking just records "I read this"), and leave a **comment**
130
+ to request changes: a note on a line, an inline `// TODO`-style comment in the
131
+ code, or a direct code edit. Any comment sends a build + re-review round — an
132
+ agent first turns your comments into an explicit instruction list, then a build
133
+ turn implements it (a re-review then covers only the follow-through, and a
134
+ hand-edit is treated as your own fix the agent completes without reverting your
135
+ lines; a comment can't be silently dropped — a build turn that addresses nothing
136
+ is refused). For the simple flow, the build turn that follows through on
137
+ feedback and the turn that drafts the final squash message both resume the same
138
+ session that built the feature in the first place, since the review tail is
139
+ nested inside that build identity rather than sitting beside it. Ticking every
140
+ box with no comment is the sign-off, which collapses the whole process into one
141
+ commit (a **squash finale** whose message an agent drafts). Stepping with a box
142
+ still unticked and no comment is refused (finish reviewing first), as is
143
+ deleting `.gtd/REVIEW.md`.
144
144
 
145
145
  The same review tail also has a direct entry point —
146
146
  `gtd --entry review-gate.check --var reviewBase=<commitish>` starts a brand new
147
- process reviewing `<commitish>..HEAD` with no cycle of its own, e.g. a
147
+ process reviewing `<commitish>..HEAD` with no build of its own, e.g. a
148
148
  colleague's PR branch (`review-gate.check`'s `reviewBase:` is a template bound
149
149
  to the `reviewBase` var, so supplying it via `--var` fixes the whole process's
150
150
  diff base to that commitish). Its squash keeps and describes only the fixes made
@@ -207,14 +207,12 @@ Commands:
207
207
  just-finished invocation's token cost and model on the
208
208
  turn commit (summed into it.processCost/processCostByModel).
209
209
  Pass --entry <state> to start a brand NEW process at
210
- <state> instead — any declared, non-commit state (e.g.
211
- review-gate.check or fix-precheck on the bundled unified
212
- template) — with repeatable --var <name>=<value> supplying
213
- that new process's fixed it.vars overrides
210
+ <state> instead — any declared, non-commit state — with
211
+ repeatable --var <name>=<value> supplying that new
212
+ process's fixed it.vars overrides
214
213
  (no command) --entry <state>
215
214
  Short form of 'step human --entry <state>' — starts a new
216
- process authenticated as human, e.g.
217
- 'gtd --entry review-gate.check'
215
+ process authenticated as human, e.g. 'gtd --entry <state>'
218
216
  abandon End the process currently underway without completing it:
219
217
  close any open review checkout window, then rewind HEAD to
220
218
  the commit the process started from, keeping everything it
@@ -226,8 +224,7 @@ Commands:
226
224
  Refuses on a dirty working tree, when there is no retained
227
225
  history, or when HEAD has advanced past the squash with
228
226
  commits that would be lost
229
- next Print the resolved rest's rendered script/prompt/message
230
- (no mutation)
227
+ next Print the resolved rest's rendered script/prompt/message (no mutation)
231
228
  status Print the resolved rest's state/actor and which declared
232
229
  pattern (if any) each pending change matches (no mutation)
233
230
  validate Format and validate the steering file the resolved rest
@@ -255,9 +252,6 @@ Options:
255
252
  (with --entry; repeatable) supply a fixed it.vars
256
253
  override for the new process; the name must already be
257
254
  declared by the workflow's own vars: or the .gtdrc vars:
258
- --once (bare gtd or gtd loop only) run exactly one loop beat (one
259
- human-gate capture, one script check+step, or one agent
260
- prompt+step), then exit
261
255
  --version, -v Print version and exit
262
256
  --help, -h Print this help and exit
263
257
  ```
@@ -312,9 +306,16 @@ envelope on **stdout**, and still exits 1:
312
306
  { "state": "error", "prompt": "<message>" }
313
307
  ```
314
308
 
309
+ This covers every failure mode, not just a command's own refusal: a **usage
310
+ error** (an unknown flag, a missing argument, `gtd --entry version`'s "not an
311
+ enterable state") and a **defect** (a layer throwing outside the ordinary error
312
+ channel) both get the same envelope — there is no failure path that reaches
313
+ `--json` without one.
314
+
315
315
  A human-readable `gtd: <message>` line is still written to **stderr** regardless
316
316
  of `--json` — the envelope adds a structured stdout channel, it does not replace
317
- the plain-text one.
317
+ the plain-text one. Stderr always carries exactly one `gtd: ` prefix: a message
318
+ already authored with its own `gtd:`/`gtd <cmd>:` prefix is never doubled.
318
319
 
319
320
  ## Driving the loop
320
321
 
@@ -453,7 +454,7 @@ A few things to know before relying on it:
453
454
  owns a `herdr:claude` session until the process exits; while it does, the
454
455
  wrapper's reports are silently ignored and the sidebar stops tracking `gtd`.
455
456
  - **`blocked` covers the resting `idle` state too** — gtd's own `idle` is a
456
- human gate (it waits for you to write a steering file), so a finished cycle
457
+ human gate (it waits for you to write a steering file), so a finished process
457
458
  reads as "your turn", which is what it is.
458
459
  - The status **persists after the wrapper exits** — that's the point: the
459
460
  sidebar keeps showing which worktree is waiting on you. Hand the pane back to
@@ -527,7 +528,7 @@ workflow:
527
528
  anyKey: anyScalarValue
528
529
  modes: # optional — steering-file modes a state's `mode:` may name
529
530
  <name>:
530
- format: <shell command> # at least one of format/validate
531
+ format: <shell command> # both optional — {} is the format-only tier
531
532
  validate: <shell command>
532
533
  entry:
533
534
  default: <machine name> # which machine is the ROOT instance
@@ -553,11 +554,14 @@ workflow:
553
554
  retry:
554
555
  max: <number>
555
556
  otherwise: <targetState>
557
+ label: <string> # optional, opaque display name passed through `gtd next --json`/`gtd status --json`
556
558
  file: <string> # optional, an Eta template naming the state's steering file
557
- mode: <modeName> # optional, requires "file" — a built-in (qa/review/prose) or a `modes:` entry
559
+ mode: <modeName> # optional, requires "file" — must be declared in `modes:` (qa/review are seeded for you; everything else, including prose, you declare)
558
560
  reviewWindow: true # optional — open the review checkout window at rest here
559
561
  reviewBase: true # optional — anchor the review window's diff base to this state's most-recent commit
560
562
  # reviewBase: <Eta template> # OR a template — rendered (only meaningful entering via --entry) to a commitish that fixes the WHOLE PROCESS's diff base
563
+ requireProgress: true # optional, requires "file" — refuse a turn whose only change deletes this state's own `file:`
564
+ answerGate: true # optional, requires "file" — refuse a turn until every open question in the (qa-mode) `file:` is answered
561
565
  entry: true # optional — an EXTRA reachability root (`entries.manual`), enterable via `gtd --entry <this state's qualified name>` — NOT a precondition for `--entry` (any declared, non-commit state is a valid target)
562
566
  <local>: { machine: <name>, with: { <param>: <value> } } # a REFERENCE — instantiates <name> as a child, qualified as `<local>.<childLocal>`
563
567
  ```
@@ -587,7 +591,7 @@ conversation across both call sites.
587
591
 
588
592
  Besides `it.vars` (below), a `script`/`prompt`/`message`/`commit` template sees:
589
593
 
590
- - **`it.startCommit`** — the process's diff base (the commit the current cycle
594
+ - **`it.startCommit`** — the process's diff base (the commit the current process
591
595
  started from, or the base a `--var reviewBase=<commitish>` entry resolved to).
592
596
  - **`it.reviewBase`** — the previous review round's boundary, falling back to
593
597
  `it.startCommit` on a first review.
@@ -607,9 +611,9 @@ model, pattern grammar, load-time rules, and how to verify a change compiles.
607
611
 
608
612
  > **Upgrading from a pre-8.2 `workflow:`?** The old flat `states:` shape (with a
609
613
  > per-state `initial: true`/`reviewEntry: true`/`fixEntry: true` flag) is no
610
- > longer accepted — finish or `gtd abandon` any in-flight cycle before
611
- > upgrading, since the old and new shapes aren't compatible mid-cycle. Wrap your
612
- > states under a single
614
+ > longer accepted — finish or `gtd abandon` any in-flight process before
615
+ > upgrading, since the old and new shapes aren't compatible mid-process. Wrap
616
+ > your states under a single
613
617
  > `machines: { <name>: { entry: <initial state>, states: {...} } }` and declare
614
618
  > `entry: { default: <name> }` at the top level (moving any
615
619
  > `reviewEntry`/`fixEntry` state to a plain per-state `entry: true` flag,
@@ -785,13 +789,18 @@ own `- [ ]` line — diagnostics for both (live as you edit), and a
785
789
 
786
790
  It is config-driven via each state's `file:`/`mode:`, and falls back to basename
787
791
  dispatch (`REVIEW.md` → `review`) with no config in sight. `qa` and `review` are
788
- gtd's built-in steering-file MODES with a VALIDATOR gtd itself implements; a
789
- mode's `format:` and `validate:` are shell commands a workflow (or a project's
790
- `.gtdrc`) declares for itself, so you bring your own formatter and your own
791
- checkers. A third built-in, `prose` — format-only, no validator, used by the
792
- simple flow's plan file — has no live editor support: `gtd lsp` publishes no
793
- symbols or diagnostics for it, though `gtd validate` and the `gtd step` gate
794
- still format and validate it like any other mode.
792
+ gtd's built-in steering-file FORMATS, each with its own outline/actions/
793
+ go-to-definition and a VALIDATOR gtd itself implements; a mode's `format:` and
794
+ `validate:` are shell commands a workflow (or a project's `.gtdrc`) declares for
795
+ itself, so you bring your own formatter and your own checkers. Overriding one of
796
+ `qa`/`review`'s `validate:` with a shell command does NOT lose its outline/
797
+ actions — those come from the FORMAT (the name), independent of who validates —
798
+ but `gtd lsp` never runs a shell command per keystroke, so live diagnostics
799
+ become one `Information` notice pointing at `gtd validate` instead of the
800
+ built-in findings. Any OTHER mode name — `prose` (used by the simple flow's plan
801
+ file), or a project's own declared name — has no built-in format, so it gets no
802
+ live editor support at all: `gtd validate` and the `gtd step` gate still format
803
+ and validate it like any other mode.
795
804
 
796
805
  ## Development
797
806
 
@@ -800,8 +809,10 @@ npm install
800
809
  npm run dev # run from source, no build (node dev/run.mjs)
801
810
  npm run build # tsdown → dist/gtd.bundle.mjs
802
811
  npm test # format:check, typecheck, lint, unit + e2e tests, fallow
803
- npm run test:unit # vitest unit tests (the pure resolver) — --project unit
804
- npm run test:e2e # gherkin e2e via vitest + quickpickle — --project e2e
812
+ npm run test:unit # vitest unit tests (the pure resolver) — --project unit
813
+ npm run test:e2e # gherkin e2e, both tiers — test:e2e:inmem + test:e2e:live
814
+ npm run test:e2e:inmem # @inmem scenarios, in-process, no build — --project e2e-inmem
815
+ npm run test:e2e:live # @live scenarios against the built bundle — --project e2e-live
805
816
  npm run test:mutation # StrykerJS mutation testing (manual only, ~10 min)
806
817
  npm run typecheck
807
818
  npm run lint
@@ -817,7 +828,20 @@ The decision core is pure and IO-free: the pattern machine's shape (states,
817
828
  patterns, retry) is a plain-data `WorkflowDefinition` (`src/PatternMachine.ts`),
818
829
  and the same module's `resolveState`/`step` are the pure resolver/interpreter
819
830
  over it — so the whole engine is trivially unit-testable in isolation. All
820
- git/filesystem/template IO is confined to the edge (`src/Edge.ts`).
831
+ git/filesystem/template IO is confined to the edge (`src/Edge.ts`), whose
832
+ `currentRest` resolves everything derivable from where the process rests right
833
+ now into a single snapshot, and whose `planStep`/`planEntry` pair separates
834
+ deciding a transition from performing it — so a command's own capture guards run
835
+ between the two. The edge reaches the outside world through two content ports
836
+ (`src/RepoFiles.ts` for working-tree and committed reads, `src/CommandRunner.ts`
837
+ for the one place gtd spawns a subprocess itself), with the `gtd step` capture
838
+ guards (`src/StepGuards.ts`) built on top of them.
839
+
840
+ `src/testing/` holds the in-memory git/config/filesystem test double every
841
+ `@inmem` scenario and unit test runs against instead of a real repo — it never
842
+ ships (a lint rule and a build-time check both enforce that) and is trustworthy
843
+ only because the same `GitOperations` contract suite runs against a real git
844
+ repo too.
821
845
 
822
846
  Releases are automatic: push releasable Conventional Commits (`fix:`, `feat:`,
823
847
  or breaking changes) to `main` and semantic-release computes the next version,
package/bin/gtd CHANGED
@@ -513,7 +513,7 @@ reported_head="$(git rev-parse HEAD 2>/dev/null || echo none)"
513
513
  # We peek with the pure, mutation-free `gtd next --json` and step ONLY when it
514
514
  # reports a human rest (`kind == "message"`): at an agent/check rest
515
515
  # `gtd step human` would refuse out-of-turn (non-zero, aborting under `set -e`),
516
- # and a mid-cycle restart must simply resume driving, not fail. A clean gate is
516
+ # and a mid-process restart must simply resume driving, not fail. A clean gate is
517
517
  # a harmless no-op; a genuine refusal there (a malformed steering file, or an
518
518
  # edit no `on` pattern accepts) is surfaced and stops the loop rather than being
519
519
  # driven past. --once treats a captured commit here as the run's one beat and