@pmelab/gtd 29.3.0 → 30.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 (51) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +65 -71
  3. package/claude/hooks/ship.ts +36 -25
  4. package/dist/chunks/{reserved-Dmpg6dAp.mjs → reserved-iG86IR3I.mjs} +19 -233
  5. package/dist/chunks/{tail-CLyE5VFR.mjs → tail-DA2UuJNP.mjs} +1 -1
  6. package/dist/gtd.bundle.mjs +0 -5
  7. package/dist/testkit.mjs +0 -5
  8. package/dist/workflows/cleanup.mjs +2 -2
  9. package/dist/workflows/feature.mjs +14 -180
  10. package/dist/workflows/fix.mjs +2 -2
  11. package/dist/workflows/review.mjs +2 -2
  12. package/dist/workflows/rollout.mjs +1 -1
  13. package/dist/workflows.mjs +29 -409
  14. package/package.json +1 -1
  15. package/skills/authoring/SKILL.md +15 -27
  16. package/src/cli/Cli.test.ts +6 -6
  17. package/src/edge/Edge.test.ts +7 -7
  18. package/src/flags/index.test.ts +0 -4
  19. package/src/flags/index.ts +0 -1
  20. package/src/workflow/load.test.ts +1 -1
  21. package/src/workflows/feature/fast-path.feature +1 -2
  22. package/src/workflows/feature/feature.test.ts +74 -63
  23. package/src/workflows/feature/feature.ts +13 -35
  24. package/src/workflows/feature/index.ts +0 -1
  25. package/src/workflows/feature/one-step-land.feature +4 -6
  26. package/src/workflows/feature/planning.ts +2 -27
  27. package/src/workflows/feature/refactor-absorbs.feature +1 -2
  28. package/src/workflows/feature/refactor-first.feature +1 -3
  29. package/src/workflows/feature/refactor-review.feature +0 -1
  30. package/src/workflows/feature/risk-tier.feature +2 -3
  31. package/src/workflows/feature/seam-rejection.feature +1 -3
  32. package/src/workflows/feature/seam.feature +4 -6
  33. package/src/workflows/feature/steps.test.ts +0 -20
  34. package/src/workflows/feature/steps.ts +1 -49
  35. package/src/workflows/feature/{seam-pin.feature → tier-pin.feature} +17 -11
  36. package/src/workflows/feature/tier.test.ts +3 -5
  37. package/src/workflows/feature/tier.ts +2 -2
  38. package/src/workflows/index.ts +0 -1
  39. package/src/workflows/shared/access.test.ts +0 -2
  40. package/src/workflows/shared/access.ts +0 -2
  41. package/src/workflows/shared/options.test.ts +2 -3
  42. package/src/workflows/shared/prose.ts +4 -11
  43. package/src/workflows/shared/reserved.ts +2 -2
  44. package/src/workflows/shared/skills.test.ts +0 -11
  45. package/src/workflows/shared/skills.ts +2 -9
  46. package/src/workflows/shared/steps.ts +0 -1
  47. package/src/workflows/shared/text.test.ts +4 -104
  48. package/src/workflows/shared/text.ts +15 -223
  49. package/src/workflows/shared/vars.ts +1 -3
  50. package/src/workflows/feature/packages.test.ts +0 -368
  51. package/src/workflows/feature/packages.ts +0 -173
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "29.3.0",
3
+ "version": "30.0.0",
4
4
  "description": "Drive the gtd loop inside Claude Code: agent turns run as subagents, gtd decides every beat",
5
5
  "author": {
6
6
  "name": "Philipp Melab"
package/README.md CHANGED
@@ -74,8 +74,8 @@ autonomously run the process for you. Ask your agent if you have any questions.
74
74
  misreading it — see
75
75
  [History format](https://github.com/pmelab/gtd/blob/main/docs/configuration.md#history-format).
76
76
  - **Files, not chat.** Conversation lives in files like `REQUIREMENTS.md` or
77
- `ARCHITECTURE.md`. Request changes by editing them, approve by leaving the
78
- tree clean — all in your own editor. There is no chat UI that could flicker.
77
+ `REVIEW.md`. Request changes by editing them, approve by leaving the tree
78
+ clean — all in your own editor. There is no chat UI that could flicker.
79
79
  - **Harness agnostic.** gtd emits prompts to stdout (or JSON). Claude Code, a
80
80
  bash loop, a CI job, or you reading it out loud to your sentient cat — the
81
81
  workflow doesn't care who executes it.
@@ -341,24 +341,26 @@ wait for the next rest. When the process finishes, Claude offers to squash it
341
341
  and open its pull request(s) (or run `/gtd ship`; `GTD_SHIP_MODE` picks one
342
342
  stacked pull request per slice or one commit per slice, see the
343
343
  [driver doc](docs/driver.md)); a process started for an issue ships with
344
- `Closes #N` on its top pull request; a closed pull request whose branch was
345
- deleted from origin is left behind and a new one opens; re-running `/gtd ship`
346
- after a lower stacked pull request merged restacks the rest (it refuses, with
347
- nothing changed, when that pull request merged anywhere but the default branch
348
- (unless into a lower slice branch before that one merged into it), a slice
349
- branch is checked out in another worktree, an unrelated local branch sits inside
350
- the rebased commits, or a slice's change is already upstream; a failed rebase is
351
- rolled back; to tear a stack down from its worktree, delete the top branch with
344
+ `Closes #N` on its top pull request; a later process on an already-stacked
345
+ branch, such as review fixes, lands one commit per slice on the top pull request
346
+ and keeps the stack; a closed pull request whose branch was deleted from origin
347
+ is left behind and a new one opens; re-running `/gtd ship` after a lower stacked
348
+ pull request merged restacks the rest (it refuses, with nothing changed, when
349
+ that pull request merged anywhere but the default branch (unless into a lower
350
+ slice branch before that one merged into it), a slice branch is checked out in
351
+ another worktree, an unrelated local branch sits inside the rebased commits, or
352
+ a slice's change is already upstream; a failed rebase is rolled back; to tear a
353
+ stack down from its worktree, delete the top branch with
352
354
  `git push origin --delete`, since `gh pr close --delete-branch` cannot). A
353
355
  process can change hands at any gate: ask Claude to hand it off (or run
354
356
  `/gtd throw @dev`) and it opens a draft pull request assigned to them, and
355
357
  `/gtd catch <pr>` picks it up exactly where it waits, so whoever wrote the
356
- requirements can hand the architecture to someone else. Ship, hand-off and throw
357
- answer at once and run in the background, with progress on the status line; they
358
- report by waking Claude, a toast, a transcript line and a push notification, and
359
- a failed push or `gh` call reports as a failure. While a ship or hand-off runs
360
- or is queued, `/gtd catch`, the doors, `/gtd <requirements>` and bare `/gtd`
361
- wait for its report. See
358
+ requirements can hand the build to someone else. Ship, hand-off and throw answer
359
+ at once and run in the background, with progress on the status line; they report
360
+ by waking Claude, a toast, a transcript line and a push notification, and a
361
+ failed push or `gh` call reports as a failure. While a ship or hand-off runs or
362
+ is queued, `/gtd catch`, the doors, `/gtd <requirements>` and bare `/gtd` wait
363
+ for its report. See
362
364
  [Inside Claude Code](https://github.com/pmelab/gtd/blob/main/docs/driver.md#inside-claude-code-the-gtd-mod).
363
365
 
364
366
  ### Then let an agent build your own
@@ -409,36 +411,26 @@ noted in steps 3 and 4 below.
409
411
  1. **You sketch.** Change anything, or write the idea into `.gtd/TODO.md`. Rough
410
412
  is fine; it is treated as a sketch, not as work.
411
413
  2. **You answer questions and discuss.** Planning works out what the thing
412
- should do first, then how it should be built, and hands you a file at either
413
- point — every process stops there, even with no open question. Answer open
414
- questions in your editor, in the file, and start the loop again; a round that
415
- only leaves notes needs no tick. Start a footnote with `- H: <question>` to
416
- ask something: the agent replies with `- A: <reply>` and the process stops at
417
- the same gate again. Close a thread by replying with a conclusion or deleting
418
- it. While a thread's last entry is the agent's, moving on is refused. Leave
419
- the file untouched and start the loop to accept the plan as-is, unanswered
420
- questions and all. Every plan gets the technical pass: its document has four
421
- sections, in order — `## Interfaces`, `## Call Stacks`, `## E2E Scenarios`,
422
- `## Unit Tests` — behind a leading `## Open Questions` when there are any.
423
- `## E2E Scenarios` is never empty: it names each user-visible behaviour in
424
- plain words (the builder writes the scenario itself), or, when nothing
425
- user-visible changes, the line `No e2e change.` with a one-line reason.
426
-
427
- 3. **You wait.** The work is split into vertical slices and built one at a time,
428
- each one starting from the unit and e2e tests it declares (a build turn
429
- missing one is refused; a test the package only relies on is marked
430
- `(unchanged)`; it must exist before the turn, and the build turn must not
431
- create, edit or delete it — a wrong marker is fixed upstream, in the plan,
432
- and a process stuck on one is ended with `gtd abandon`; and a package whose
433
- work lies outside the repository, such as filing issues, closes on recorded
434
- evidence), checked against the full suite (`testCommand`) and fixed until it
435
- passes before moving on — so keep that suite fast; gtd has no lighter one.
436
- Scenario wording is not frozen: you sign the scenarios off once, in the
437
- review's `## Scenarios` chunk, where any assertion an existing scenario lost
438
- is marked `Weakened:`. After the last package the quality lap runs. Two
439
- points along the process are judged rather than always asking you outright —
440
- each stops and hands you a verdict to make (`gtd judge answer`, or land with
441
- a clean tree to accept the conservative default, which never skips work;
414
+ should do and hands you `.gtd/REQUIREMENTS.md` — every process stops there,
415
+ even with no open question. Answer open questions in your editor, in the
416
+ file, and start the loop again; a round that only leaves notes needs no tick.
417
+ Start a footnote with `- H: <question>` to ask something: the agent replies
418
+ with `- A: <reply>` and the process stops at the same gate again. Close a
419
+ thread by replying with a conclusion or deleting it. While a thread's last
420
+ entry is the agent's, moving on is refused. Leave the file untouched and
421
+ start the loop to accept the plan as-is, unanswered questions and all.
422
+
423
+ 3. **You wait.** The agent first implements the requirements the quickest way
424
+ that works — the naive pass — and a judge reads where it changed code (see
425
+ [the naive pass](#the-naive-pass) below). Then the change is built
426
+ test-first, checked against the full suite (`testCommand`) and fixed until it
427
+ passes — so keep that suite fast; gtd has no lighter one. Scenario wording is
428
+ not frozen: you sign the scenarios off once, in the review's `## Scenarios`
429
+ chunk, where any assertion an existing scenario lost is marked `Weakened:`.
430
+ After the build the quality lap runs. Three points along the process are
431
+ judged rather than always asking you outright — each stops and hands you a
432
+ verdict to make (`gtd judge answer`, or land with a clean tree to accept the
433
+ conservative default, which never skips work;
442
434
  `gtd judge run --provider fixed --answers <path>` — or the
443
435
  `GTD_JUDGE_ANSWERS` env var, inline JSON — answers one from a file, piped
444
436
  between `gtd judge --json` and `gtd judge answer`;
@@ -453,6 +445,8 @@ noted in steps 3 and 4 below.
453
445
  choose the judge without editing the driver (flags win over both, in that
454
446
  order); `--model` with `--provider fixed`/`jev` is refused. jev and llm exit
455
447
  1 with nothing on stdout when they cannot answer every question):
448
+ - After the naive pass: does it land as is, need its seam refactored first,
449
+ or get rejected and retried?
456
450
  - Every red round after the first: was the failure identical, new, or
457
451
  progress?
458
452
  - After you review: is each of your notes an edit, a question, a nit or
@@ -467,8 +461,8 @@ noted in steps 3 and 4 below.
467
461
  measured probability, so a confidently wrong verdict can clear the 0.7 floor
468
462
  and skip a gate unattended.**
469
463
 
470
- Once the last package is built, the whole change goes through a qualitative
471
- review lap before you see anything: six lenses, one turn each, in order —
464
+ Once the change is built, the whole change goes through a qualitative review
465
+ lap before you see anything: six lenses, one turn each, in order —
472
466
  `correctness`, `owasp-security`, `ponytail-review`, `test-audit`,
473
467
  `conventions`, `spec-challenge`. Each traces the change from its own angle
474
468
  and records every finding, blocking or not; one fix turn then fixes ALL of
@@ -495,14 +489,14 @@ noted in steps 3 and 4 below.
495
489
  you unfixed; risk: a fix lands with no check that the risk was real. Tick the
496
490
  boxes to approve, or write what is wrong. Approving ends the process;
497
491
  feedback is judged note by note, each as `edit`, `question`, `nit` or
498
- `praise`. An `edit` sends the process back to step 2 for a fresh plan — it
499
- never patches over a design you rejected. A `question` is answered inline
500
- under your note and the process stops at the review again — no new plan. A
501
- `nit` is fixed in one batch, the suite must go green (a red one gets fix
502
- turns first), then a fresh review of the change stops at the review again.
503
- `praise` is dropped; a round of only praise signs off. When a round mixes
504
- them, questions are answered and nits fixed first, then the edits are planned
505
- — risk: the planning lap may redo nit fixes it touches.
492
+ `praise`. An `edit` sends the process back to step 2 for re-derived
493
+ requirements, and the implementation is amended to meet them. A `question` is
494
+ answered inline under your note and the process stops at the review again —
495
+ no new plan. A `nit` is fixed in one batch, the suite must go green (a red
496
+ one gets fix turns first), then a fresh review of the change stops at the
497
+ review again. `praise` is dropped; a round of only praise signs off. When a
498
+ round mixes them, questions are answered and nits fixed first, then the edits
499
+ are planned — risk: the planning lap may redo nit fixes it touches.
506
500
 
507
501
  Each `edit` note also gets a severity tier from four judge questions
508
502
  (security, reversibility, blast radius, new concept). `critical` and `should`
@@ -521,8 +515,8 @@ noted in steps 3 and 4 below.
521
515
 
522
516
  You never talk to it. Every exchange is a file in `.gtd/` that you edit in your
523
517
  own editor, and every answer you give is a commit. Your test suite is the gate
524
- throughout: a red baseline stops the process before it starts, and a red package
525
- is fixed before the next one begins.
518
+ throughout: a red baseline stops the process before it starts, and a red build
519
+ is fixed before review.
526
520
 
527
521
  Prefer a phone or another device over your editor? `gtd ui` opens the file whose
528
522
  turn it is as a small web app, over Tailscale — including a step that names no
@@ -582,11 +576,12 @@ unknown key. `gtd.config.ts` is the only repository config: gtd 23 removed the
582
576
  home directory — fails every command until you delete it. See
583
577
  [Migrating from `.gtdrc`](https://github.com/pmelab/gtd/blob/main/docs/configuration.md#migrating-from-gtdrc).
584
578
 
585
- **Naive pass (flag `GTD_FLAG_NAIVE_PASS=1`, off by default).** Instead of an
586
- architecture and package files, the agent first implements the requirements
587
- directly. A judge checks whether it changed anything outside the expected set
588
- (tests, steering files; see `seamAllow`). Incidental changes land; a needed seam
589
- change is refactored first, in its own slice with its own quality lap
579
+ #### The naive pass
580
+
581
+ After design the agent first implements the requirements directly. A judge
582
+ checks whether it changed anything outside the expected set (tests, steering
583
+ files; see `seamAllow`). Incidental changes land; a needed seam change is
584
+ refactored first, in its own slice with its own quality lap
590
585
  (`refactorQualityReviews`) and review, before the feature is implemented and
591
586
  reviewed; a wrong approach is retried twice, then rests for you to steer or
592
587
  overrule it. `gtd slices` lists the `refactor` and `feature` slices. A refactor
@@ -600,14 +595,13 @@ out is `gtd abandon`. A process from 27.5.0 or later whose judge ruled
600
595
  that slice. Details in
601
596
  [the naive-pass path](docs/configuration.md#the-naive-pass-path).
602
597
 
603
- **Risk tier (flag `GTD_FLAG_RISK_TIER=1`, off by default, needs
604
- `GTD_FLAG_NAIVE_PASS=1`).** Triage proposes a risk tier, `low` or `high`, and
605
- the production signals to watch after merge. Edit the first word under
606
- `## Risk Tier` at the design gate to change it; a missing or unreadable tier
607
- means `high`. Each lap lands a `Gtd-Mark: risk-tier=<low|high>` trailer and a
608
- `Gtd-Mark: production-signal=…` trailer per signal; `gtd summary` reads the last
609
- lap's only. Its "needs a human reviewer" line for `high` is advisory, not a
610
- gate.
598
+ **Risk tier (flag `GTD_FLAG_RISK_TIER=1`, off by default).** Triage proposes a
599
+ risk tier, `low` or `high`, and the production signals to watch after merge.
600
+ Edit the first word under `## Risk Tier` at the design gate to change it; a
601
+ missing or unreadable tier means `high`. Each lap lands a
602
+ `Gtd-Mark: risk-tier=<low|high>` trailer and a `Gtd-Mark: production-signal=…`
603
+ trailer per signal; `gtd summary` reads the last lap's only. Its "needs a human
604
+ reviewer" line for `high` is advisory, not a gate.
611
605
 
612
606
  A `gtd.config.ts` call to `buildTail` or `tail` that fits neither call form is
613
607
  rejected; `tail`'s positional form is deprecated for `tail(finish, options?)`.
@@ -495,8 +495,7 @@ async function squash(
495
495
  if (!msgs.ok) return msgs
496
496
  const { fulls, history } = msgs
497
497
  const warn = () => (mode === "commits" && many ? rebaseWarning(io) : undefined)
498
- if (resume?.links)
499
- return squashOntoStack(io, git, ok, groups[0]!, fulls[0]!, history, resume, isDry, tip)
498
+ if (resume?.links) return squashOntoStack(io, git, ok, groups, fulls, history, resume, isDry, tip)
500
499
  if (isDry)
501
500
  return dryRun(
502
501
  groups,
@@ -552,18 +551,21 @@ async function stackReads(
552
551
  mode: ShipMode,
553
552
  ): Promise<Resumed | string | undefined> {
554
553
  if (mode !== "stack") return undefined
555
- if (groups.length > 1) return await stackPrecheck(io, git, ok, branch, baseBranch, groups)
554
+ if (groups.length > 1)
555
+ return await stackPrecheck(io, git, ok, branch, baseBranch, groups, base, isDry)
556
556
  return resumable(io, git, ok, branch, baseBranch, isDry, base)
557
557
  }
558
558
 
559
- // One slice on a recorded stack: its commit becomes the new top of the stack, and
560
- // the record, including the recorded top the missing-branch fallbacks read, stays.
559
+ // A process on a recorded stack — a re-ship, or a follow-up such as review fixes —
560
+ // lands its slice commits on the top branch, so they join the top pull request.
561
+ // The record, including the recorded top the missing-branch fallbacks and the
562
+ // restack read, stays.
561
563
  async function squashOntoStack(
562
564
  io: ShipIo,
563
565
  git: Git,
564
566
  ok: Ok,
565
- group: Group,
566
- full: string,
567
+ groups: Group[],
568
+ fulls: string[],
567
569
  history: string | undefined,
568
570
  resume: Resumed,
569
571
  isDry: boolean,
@@ -572,16 +574,16 @@ async function squashOntoStack(
572
574
  const links = resume.links!
573
575
  const top = links.length - 1
574
576
  if (isDry) {
575
- const messages = links.map((_, i) => (i === top ? full : ""))
576
- return {
577
- ...dryRun([group], [full], history, undefined, links),
578
- messages,
579
- restack: resume.restack,
580
- }
577
+ // the follow-up's slice names are not stack slices: no [name] headers
578
+ const messages = links.map((_, i) => (i === top ? fulls.join("\n\n") : ""))
579
+ return { ok: true as const, text: "", history, stack: links, messages, restack: resume.restack }
581
580
  }
582
581
  const pushed = await pushHistory(io, history)
583
582
  if (pushed) return fail(pushed)
584
- const one = await commitSquash(io, git, ok, group.base, full)
583
+ const one =
584
+ groups.length === 1
585
+ ? await commitSquash(io, git, ok, groups[0]!.base, fulls[0]!)
586
+ : await commitChain(io, git, ok, groups, fulls)
585
587
  if (!one.ok) return one
586
588
  const failed = await resume.revive?.()
587
589
  if (failed) return fail(failed)
@@ -709,24 +711,30 @@ async function takenSlice(git: Git, ok: Ok, branch: string, groups: Group[], dea
709
711
  return undefined
710
712
  }
711
713
 
712
- async function recordedRefusal(
714
+ // A live record takes the process onto its top branch; a dead one (no links)
715
+ // leaves the way open for a fresh stack.
716
+ async function liveRecord(
713
717
  io: ShipIo,
714
718
  git: Git,
715
719
  ok: Ok,
716
720
  branch: string,
717
721
  baseBranch: string,
718
722
  recorded: string[],
719
- ): Promise<string | undefined> {
720
- // The staleness test a resume uses: a dead record is not a live stack.
723
+ base: string,
724
+ isDry: boolean,
725
+ ): Promise<Resumed | string> {
726
+ // The staleness test a resume uses, without the restack's origin and worktree
727
+ // reads: a dead record is not a live stack, and its dropped-record line is
728
+ // silenced since a fresh stack replaces it.
721
729
  const quiet = { ...io, log: () => {} }
722
730
  const found = await resumable(quiet, git, ok, branch, baseBranch, true, "HEAD", false)
723
731
  // resumable refuses (a missing branch, a closed slice PR) before it tests the
724
732
  // chain; only a record that still chains to HEAD may refuse a fresh stack.
725
- if (typeof found === "string")
726
- return (await chainsToHead(git, ok, branch, recorded)) ? found : undefined
727
- if (!found.forget)
728
- return `${branch} already has a recorded stack (${recorded.join(", ")}); nothing changed. Ship with GTD_SHIP_MODE=commits, or run git config --local --unset branch.${branch}.gtdStack and git config --local --unset branch.${branch}.gtdStackTop, delete or rename the ${branch}-<slice> branches, then ship again.`
729
- return undefined
733
+ if (typeof found === "string") return (await chainsToHead(git, ok, branch, recorded)) ? found : {}
734
+ // `forget` with links is a live stack a restack ends, not a dead record.
735
+ if (!found.links) return {}
736
+ // Live: any refusal of the full resume (origin, worktrees) stands.
737
+ return resumable(io, git, ok, branch, baseBranch, isDry, base)
730
738
  }
731
739
 
732
740
  async function chainsToHead(git: Git, ok: Ok, branch: string, recorded: string[]) {
@@ -760,11 +768,14 @@ async function stackPrecheck(
760
768
  branch: string,
761
769
  baseBranch: string,
762
770
  groups: Group[],
763
- ): Promise<string | undefined> {
771
+ base: string,
772
+ isDry: boolean,
773
+ ): Promise<Resumed | string | undefined> {
764
774
  const recorded = names(await git("config", "--local", "--get", STACK_KEY(branch)))
765
775
  if (recorded.length) {
766
- const refused = await recordedRefusal(io, git, ok, branch, baseBranch, recorded)
767
- if (refused) return refused
776
+ const live = await liveRecord(io, git, ok, branch, baseBranch, recorded, base, isDry)
777
+ // Without links the squash builds a fresh stack, so the collision checks still run.
778
+ if (typeof live === "string" || live?.links) return live
768
779
  }
769
780
  const retired = names(await git("config", "--local", "--get", RETIRED_KEY(branch)))
770
781
  const taken = await takenSlice(git, ok, branch, groups, [...recorded, ...retired])