@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +65 -71
- package/claude/hooks/ship.ts +36 -25
- package/dist/chunks/{reserved-Dmpg6dAp.mjs → reserved-iG86IR3I.mjs} +19 -233
- package/dist/chunks/{tail-CLyE5VFR.mjs → tail-DA2UuJNP.mjs} +1 -1
- package/dist/gtd.bundle.mjs +0 -5
- package/dist/testkit.mjs +0 -5
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +14 -180
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +2 -2
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +29 -409
- package/package.json +1 -1
- package/skills/authoring/SKILL.md +15 -27
- package/src/cli/Cli.test.ts +6 -6
- package/src/edge/Edge.test.ts +7 -7
- package/src/flags/index.test.ts +0 -4
- package/src/flags/index.ts +0 -1
- package/src/workflow/load.test.ts +1 -1
- package/src/workflows/feature/fast-path.feature +1 -2
- package/src/workflows/feature/feature.test.ts +74 -63
- package/src/workflows/feature/feature.ts +13 -35
- package/src/workflows/feature/index.ts +0 -1
- package/src/workflows/feature/one-step-land.feature +4 -6
- package/src/workflows/feature/planning.ts +2 -27
- package/src/workflows/feature/refactor-absorbs.feature +1 -2
- package/src/workflows/feature/refactor-first.feature +1 -3
- package/src/workflows/feature/refactor-review.feature +0 -1
- package/src/workflows/feature/risk-tier.feature +2 -3
- package/src/workflows/feature/seam-rejection.feature +1 -3
- package/src/workflows/feature/seam.feature +4 -6
- package/src/workflows/feature/steps.test.ts +0 -20
- package/src/workflows/feature/steps.ts +1 -49
- package/src/workflows/feature/{seam-pin.feature → tier-pin.feature} +17 -11
- package/src/workflows/feature/tier.test.ts +3 -5
- package/src/workflows/feature/tier.ts +2 -2
- package/src/workflows/index.ts +0 -1
- package/src/workflows/shared/access.test.ts +0 -2
- package/src/workflows/shared/access.ts +0 -2
- package/src/workflows/shared/options.test.ts +2 -3
- package/src/workflows/shared/prose.ts +4 -11
- package/src/workflows/shared/reserved.ts +2 -2
- package/src/workflows/shared/skills.test.ts +0 -11
- package/src/workflows/shared/skills.ts +2 -9
- package/src/workflows/shared/steps.ts +0 -1
- package/src/workflows/shared/text.test.ts +4 -104
- package/src/workflows/shared/text.ts +15 -223
- package/src/workflows/shared/vars.ts +1 -3
- package/src/workflows/feature/packages.test.ts +0 -368
- package/src/workflows/feature/packages.ts +0 -173
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
|
-
`
|
|
78
|
-
|
|
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
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
`
|
|
431
|
-
|
|
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
|
|
471
|
-
|
|
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
|
|
499
|
-
|
|
500
|
-
under your note and the process stops at the review again —
|
|
501
|
-
`nit` is fixed in one batch, the suite must go green (a red
|
|
502
|
-
turns first), then a fresh review of the change stops at the
|
|
503
|
-
`praise` is dropped; a round of only praise signs off. When a
|
|
504
|
-
them, questions are answered and nits fixed first, then the edits
|
|
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
|
|
525
|
-
is fixed before
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
|
604
|
-
|
|
605
|
-
the
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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?)`.
|
package/claude/hooks/ship.ts
CHANGED
|
@@ -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)
|
|
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
|
-
//
|
|
560
|
-
// the
|
|
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
|
-
|
|
566
|
-
|
|
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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
720
|
-
|
|
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
|
-
|
|
727
|
-
if (!found.
|
|
728
|
-
|
|
729
|
-
return
|
|
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
|
-
|
|
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
|
|
767
|
-
|
|
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])
|