@pmelab/gtd 52.1.0 → 54.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 +91 -129
- package/claude/hooks/drive.ts +0 -3
- package/claude/hooks/register.tsx +3 -68
- package/claude/hooks/ship.ts +85 -1595
- package/dist/chunks/{reserved-DxB5CROj.mjs → reserved-CQ7amcKG.mjs} +45 -132
- package/dist/chunks/{review-CKMSdvgW.mjs → review-CXzUXJHL.mjs} +12 -5
- package/dist/chunks/{tail-QZc7Uz2U.mjs → tail-JZ9PAOwp.mjs} +2 -14
- package/dist/chunks/{wire-BJuZWR3C.mjs → wire-C12dahrH.mjs} +2 -130
- package/dist/flows.mjs +1 -16
- package/dist/gtd.bundle.mjs +476 -982
- package/dist/testkit.mjs +58 -417
- package/dist/wire.mjs +2 -2
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +112 -363
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +321 -677
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +1 -86
- package/src/cli/Cli.ts +13 -133
- package/src/cli/Metrics.test.ts +21 -31
- package/src/cli/Metrics.ts +25 -36
- package/src/cli/Program.test.ts +1 -35
- package/src/cli/Program.ts +5 -64
- package/src/cli/ToolCommands.ts +0 -3
- package/src/edge/Edge.test.ts +20 -322
- package/src/edge/Edge.ts +10 -181
- package/src/edge/index.ts +0 -1
- package/src/flows/runtime.test.ts +0 -27
- package/src/flows/runtime.ts +1 -49
- package/src/install/Install.ts +15 -24
- package/src/replay/Replay.test.ts +0 -169
- package/src/replay/Replay.ts +4 -93
- package/src/replay/index.ts +0 -1
- package/src/testkit/Layers.ts +0 -1
- package/src/testkit/steps.ts +23 -0
- package/src/wire/BeatDocument.ts +2 -12
- package/src/wire/BeatStatus.ts +0 -3
- package/src/wire/WireSchema.test.ts +0 -18
- package/src/wire/index.ts +0 -2
- package/src/wire/types.ts +0 -7
- package/src/workflow/Workflow.ts +1 -9
- package/src/workflows/feature/criteria.test.ts +1 -14
- package/src/workflows/feature/criteria.ts +6 -38
- package/src/workflows/feature/feature.ts +34 -118
- package/src/workflows/feature/hands-on-skip.feature +2 -70
- package/src/workflows/feature/index.ts +0 -1
- package/src/workflows/feature/lens-review.feature +50 -0
- package/src/workflows/feature/one-step-land.feature +0 -4
- package/src/workflows/feature/prototype-rounds.feature +1 -1
- package/src/workflows/feature/prototype.feature +3 -3
- package/src/workflows/feature/prototype.ts +18 -29
- package/src/workflows/feature/refactor-absorbs.feature +3 -8
- package/src/workflows/feature/refactor-first.feature +0 -5
- package/src/workflows/feature/refactor-rebuild.feature +0 -6
- package/src/workflows/feature/refactor-review.feature +49 -11
- package/src/workflows/feature/refactor.ts +30 -64
- package/src/workflows/feature/review-gate.feature +1 -1
- package/src/workflows/feature/reviewer-walkthrough.feature +0 -31
- package/src/workflows/feature/seam-rejection.feature +0 -4
- package/src/workflows/feature/seam.test.ts +7 -26
- package/src/workflows/feature/seam.ts +52 -165
- package/src/workflows/index.ts +0 -1
- package/src/workflows/shared/lenses.ts +0 -13
- package/src/workflows/shared/options.test.ts +2 -11
- package/src/workflows/shared/options.ts +4 -21
- package/src/workflows/shared/review.test.ts +3 -3
- package/src/workflows/shared/review.ts +12 -2
- package/src/workflows/shared/steps.ts +0 -2
- package/src/workflows/shared/text.test.ts +10 -80
- package/src/workflows/shared/text.ts +35 -211
- package/src/workflows/shared/vars.ts +2 -8
- package/src/workflows/shared/workflows.steps.ts +0 -13
- package/claude/hooks/context.ts +0 -66
- package/src/replay/Slices.test.ts +0 -73
- package/src/replay/Slices.ts +0 -45
- package/src/wire/ContextLimit.fixture.ts +0 -30
- package/src/wire/ContextLimit.test.ts +0 -96
- package/src/wire/ContextLimit.ts +0 -169
- package/src/wire/Slices.ts +0 -25
- package/src/wire/golden/1/slices.json +0 -11
- package/src/workflows/feature/context-limit.feature +0 -37
- package/src/workflows/feature/continuation-cap.feature +0 -69
- package/src/workflows/feature/continuation-rebuild.feature +0 -92
- package/src/workflows/feature/continuation-slices.feature +0 -68
- package/src/workflows/feature/continuation.feature +0 -209
- package/src/workflows/feature/slice-findings.feature +0 -256
- package/src/workflows/feature/slicing-cap.feature +0 -23
- package/src/workflows/feature/slicing.feature +0 -297
- package/src/workflows/feature/slicing.test.ts +0 -49
- package/src/workflows/feature/slicing.ts +0 -105
- package/src/workflows/shared/continuation.ts +0 -92
package/README.md
CHANGED
|
@@ -310,18 +310,10 @@ cost on its landing, answers judge gates through `gtd judge run`, and maps the
|
|
|
310
310
|
workflow's `smart`/`base` model hints to real model names through
|
|
311
311
|
`GTD_LOOP_MODEL_SMART`/`GTD_LOOP_MODEL_BASE`.
|
|
312
312
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
workflows' `contextLimit` option, moves the two limits — see
|
|
318
|
-
[Context limits](https://github.com/pmelab/gtd/blob/main/docs/driver.md#context-limits).
|
|
319
|
-
In `feature`, a naive pass, refactor or build turn that wrapped up that way is a
|
|
320
|
-
snapshot: a fresh turn picks up from it with the handoff note and the criteria
|
|
321
|
-
still open, up to 3 times per phase, across its retries (`continuations`), and a
|
|
322
|
-
phase whose continuations keep stopping, or close nothing twice in a row
|
|
323
|
-
(`continuationStall`), rests for a person — see
|
|
324
|
-
[Continuations](https://github.com/pmelab/gtd/blob/main/docs/configuration.md#continuations).
|
|
313
|
+
Neither guards an agent turn's context: a long turn relies on the agent
|
|
314
|
+
harness's own compaction, and a pass that falls short is caught by its
|
|
315
|
+
acceptance tests and retried. Size the work before the sketch — a change too big
|
|
316
|
+
for one process is several processes.
|
|
325
317
|
|
|
326
318
|
### Or drive it from inside Claude Code
|
|
327
319
|
|
|
@@ -354,34 +346,23 @@ plan or the diff, records what you decide in the steering file the way the step
|
|
|
354
346
|
asks, and moves the process on only when you say so; a review you leave
|
|
355
347
|
untouched signs off. While a step runs you can keep asking; requests for changes
|
|
356
348
|
wait for the next rest. When the process finishes, Claude offers to squash it
|
|
357
|
-
and open its pull request
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
opens
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
a
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
request assigned to them, and `/gtd catch <pr>` picks it up exactly where it
|
|
375
|
-
waits, so whoever wrote the requirements can hand the build to someone else.
|
|
376
|
-
`/gtd pr` brings a pull request's unresolved review threads into the review:
|
|
377
|
-
each becomes a note in `.gtd/REVIEW.md` ticked "fix as suggested" (untick it for
|
|
378
|
-
"won't fix" plus a reason, or your own answer), the review door fixes them in
|
|
379
|
-
place, and each thread then gets a reply naming the fixing commit (or your
|
|
380
|
-
reason) and is resolved. Ship, hand-off and throw answer at once and run in the
|
|
381
|
-
background, with progress on the status line; they report by waking Claude, a
|
|
382
|
-
toast, a transcript line and a push notification, and a failed push or `gh` call
|
|
383
|
-
reports as a failure. While a ship or hand-off runs or is queued, `/gtd catch`,
|
|
384
|
-
the doors, `/gtd <requirements>` and bare `/gtd` wait for its report. See
|
|
349
|
+
and open its pull request (or run `/gtd ship`: one commit under one pull
|
|
350
|
+
request, see the [driver doc](docs/driver.md)); a process started for an issue
|
|
351
|
+
ships with `Closes #N` on its pull request; a closed pull request whose branch
|
|
352
|
+
was deleted from origin is left behind and a new one opens. A process can change
|
|
353
|
+
hands at any gate: ask Claude to hand it off (or run `/gtd throw @dev`) and it
|
|
354
|
+
opens a draft pull request assigned to them, and `/gtd catch <pr>` picks it up
|
|
355
|
+
exactly where it waits, so whoever wrote the requirements can hand the build to
|
|
356
|
+
someone else. `/gtd pr` brings a pull request's unresolved review threads into
|
|
357
|
+
the review: each becomes a note in `.gtd/REVIEW.md` ticked "fix as suggested"
|
|
358
|
+
(untick it for "won't fix" plus a reason, or your own answer), the review door
|
|
359
|
+
fixes them in place, and each thread then gets a reply naming the fixing commit
|
|
360
|
+
(or your reason) and is resolved. Ship, hand-off and throw answer at once and
|
|
361
|
+
run in the background, with progress on the status line; they report by waking
|
|
362
|
+
Claude, a toast, a transcript line and a push notification, and a failed push or
|
|
363
|
+
`gh` call reports as a failure. While a ship or hand-off runs or is queued,
|
|
364
|
+
`/gtd catch`, the doors, `/gtd <requirements>` and bare `/gtd` wait for its
|
|
365
|
+
report. See
|
|
385
366
|
[Inside Claude Code](https://github.com/pmelab/gtd/blob/main/docs/driver.md#inside-claude-code-the-gtd-mod).
|
|
386
367
|
|
|
387
368
|
### Then let an agent build your own
|
|
@@ -509,12 +490,8 @@ noted in steps 3 and 4 below.
|
|
|
509
490
|
unsure verdict (below `qualityTriageMinP`, `0.7`) is `design`; mechanical
|
|
510
491
|
findings the fix turns leave become notes too. This lap is where code quality
|
|
511
492
|
is looked at, and every round pays for it. It never replaces step 4 — your
|
|
512
|
-
review stays the final gate, and nothing here skips it.
|
|
513
|
-
|
|
514
|
-
and never rests for its findings: its notes are carried to the review after
|
|
515
|
-
the last slice, each naming its slice and lens, beside every slice's fixes.
|
|
516
|
-
The `fix` and `cleanup` doors triage their lap too, and hand its notes to
|
|
517
|
-
their own review.
|
|
493
|
+
review stays the final gate, and nothing here skips it. The `fix` and
|
|
494
|
+
`cleanup` doors triage their lap too, and hand its notes to their own review.
|
|
518
495
|
|
|
519
496
|
A red suite that keeps failing past a few fix attempts escalates instead of
|
|
520
497
|
retrying forever: an agent turn reads the failing output and writes
|
|
@@ -529,49 +506,49 @@ noted in steps 3 and 4 below.
|
|
|
529
506
|
`Gtd-Mark: flaky=<check>` (see
|
|
530
507
|
[Re-check before a person](docs/configuration.md#re-check-before-a-person)).
|
|
531
508
|
|
|
532
|
-
4. **You review.** After the
|
|
509
|
+
4. **You review.** After the quality lap a reviewer turn
|
|
533
510
|
(`build.review.reviewing`) walks you through the whole change: `## ` chunks
|
|
534
511
|
grouped by concern, **every chunk pointing at its code**
|
|
535
512
|
(`./path#start-end`), so the phone shows the diff beside each one. Every
|
|
536
|
-
design note the
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
513
|
+
design note the lap left gets a chunk of its own, pointing at the code it
|
|
514
|
+
cites; gtd adds the note's text and three tick boxes — **fix as suggested**,
|
|
515
|
+
**won't fix**, or **your own answer** written over `_your answer_`
|
|
516
|
+
(`build.review.carrying`) — then, for reference, no tick boxes, no answer
|
|
517
|
+
needed — what was **fixed automatically** (with the fixing commit) and what
|
|
518
|
+
was **dropped as noise**. A review with a chunk pointing at no code, or a
|
|
519
|
+
note left out, is refused (`review-pointers`). A pointer note the reviewer
|
|
520
|
+
opens with `Risk:` is fixed before you see the review
|
|
544
521
|
(`build.review.fix.risks.fixing`), then the review is written again. **What
|
|
545
522
|
matters most comes first:** a judge (`build.review.ranking`) rates every
|
|
546
523
|
chunk on the four severity questions below, and the review lists `critical`
|
|
547
524
|
chunks first (security, then one-way doors), then `should`, then `could`,
|
|
548
525
|
each led by its tier and reason (`_critical: security, one-way door_`) — the
|
|
549
526
|
phone shows it on the chunk's card. A chunk the judge is unsure of, or whose
|
|
550
|
-
evidence the budget cut, is `critical`. On `refactor-first` the refactor
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
527
|
+
evidence the budget cut, is `critical`. On `refactor-first` the refactor gets
|
|
528
|
+
a walkthrough of its own first, covering the whole refactor on every round,
|
|
529
|
+
before the feature is built; see [the naive pass](#the-naive-pass). **Every
|
|
530
|
+
note needs exactly one tick before `gtd land` accepts the review**
|
|
531
|
+
(`answer-completeness`; an open `- H:` thread waiting on the agent is the one
|
|
532
|
+
exception). Fix as suggested joins the nit batch below, unjudged; won't fix
|
|
533
|
+
drops the note and records it as `Gtd-Mark: wontfix=<title>` on the gate's
|
|
534
|
+
commit; your own answer is judged like a note. A note you answered is left
|
|
535
|
+
out of the fresh review after a nit fix. Land with every note answered and
|
|
536
|
+
nothing else changed to approve, or write what is wrong — disagreeing with a
|
|
537
|
+
fix or a drop means writing a note of your own. Approving ends the process
|
|
538
|
+
(at the refactor review, it moves on to the feature); feedback is judged note
|
|
539
|
+
by note, each as `edit`, `question`, `nit` or `praise`. An `edit` sends the
|
|
540
|
+
process back to step 2 for re-derived requirements — the built criteria keep
|
|
541
|
+
their IDs (reworded in place when their behaviour changes) and new behaviour
|
|
542
|
+
gets new ones — then the implementation is amended to meet them, with no new
|
|
543
|
+
naive pass or seam judge, the hands-on test runs again, the quality lap runs,
|
|
544
|
+
and you review again. At the refactor review an `edit` re-derives the
|
|
545
|
+
refactor instead. A `question` is answered inline under your note and the
|
|
546
|
+
process stops at the review again — no new plan. A `nit` is fixed in one
|
|
547
|
+
batch, the suite must go green (a red one gets fix turns first), then a fresh
|
|
548
|
+
review of the change stops at the review again. `praise` is dropped; a round
|
|
549
|
+
of only praise signs off. When a round mixes them, questions are answered and
|
|
550
|
+
nits fixed first, then the edits are planned — risk: the planning lap may
|
|
551
|
+
redo nit fixes it touches.
|
|
575
552
|
|
|
576
553
|
Each `edit` note also gets a severity tier from four judge questions
|
|
577
554
|
(security, reversibility, blast radius, new concept). Every tier is
|
|
@@ -684,45 +661,38 @@ Incidental changes land: the pass itself is the implementation, with no rebuild,
|
|
|
684
661
|
straight through the health loop and the quality lap. A needed seam change
|
|
685
662
|
resets: the pass is never applied. An agent reads its whole diff and writes a
|
|
686
663
|
refactor note (`.gtd/REFACTOR-NOTE.md`: which seams, which shape); the seam is
|
|
687
|
-
refactored first, flag-free with the existing suite green,
|
|
688
|
-
|
|
664
|
+
refactored first, flag-free with the existing suite green, on its own with its
|
|
665
|
+
own quality lap (`refactorQualityReviews`) and review; then the feature is
|
|
689
666
|
rebuilt on the refactored base, behind a release flag, with the note and the
|
|
690
667
|
naive diff as reference. The rebuild is refused until its tests carry every
|
|
691
668
|
criterion ID the naive pass's tests named, and the suite must be green with
|
|
692
669
|
them. A wrong approach is retried twice, then rests for you to steer or overrule
|
|
693
|
-
it.
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
at the
|
|
710
|
-
|
|
711
|
-
refactor-review change the refactor cannot build is refused, and the only way
|
|
670
|
+
it. A refactor that already carried the whole feature leaves the rebuild only
|
|
671
|
+
the carried-over tests, and the feature's quality lap and review cover the whole
|
|
672
|
+
change. Once the implementation is green, a judge asks, per acceptance
|
|
673
|
+
criterion, whether a person can observe it by using the software; an agent then
|
|
674
|
+
uses the change by hand for those — the real program in a scratch directory, the
|
|
675
|
+
real page, guided by a project skill or a manual testing section in `AGENTS.md`,
|
|
676
|
+
`CLAUDE.md` or `README.md` — and writes `.gtd/PROTOTYPE.md`: per criterion pass,
|
|
677
|
+
fail or blocked, with the steps run and what it saw. A criterion the judge finds
|
|
678
|
+
no one can observe (a refactor, something left unchanged, internal shape), or
|
|
679
|
+
one design tagged `(no hands-on)`, is skipped; a change with none left skips
|
|
680
|
+
this step. This is the one check that a criterion is met: with `prototypeRounds`
|
|
681
|
+
set to `0` nothing checks the criteria beyond the green suite. Failures are
|
|
682
|
+
fixed and re-tested before the quality lap, up to `prototypeRounds` (3) fix
|
|
683
|
+
rounds; past them, with only blocked items, or with no way to test found, it
|
|
684
|
+
rests for you. See [The hands-on test](docs/configuration.md#the-hands-on-test).
|
|
685
|
+
Feedback at the feature review re-runs design and the implementation, never the
|
|
686
|
+
naive pass; feedback at the refactor review re-runs the refactor, never design;
|
|
687
|
+
a refactor-review change the refactor cannot build is refused, and the only way
|
|
712
688
|
out is `gtd abandon`. A process from 27.5.0 or later whose judge ruled
|
|
713
|
-
`refactor-first` must `gtd abandon` too.
|
|
714
|
-
that slice. Details in
|
|
689
|
+
`refactor-first` must `gtd abandon` too. Details in
|
|
715
690
|
[the naive-pass path](docs/configuration.md#the-naive-pass-path).
|
|
716
691
|
|
|
717
|
-
**
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
health loop, quality lap — runs in order from where the previous one landed; the
|
|
722
|
-
review follows the last one and covers the whole change. `gtd slices` lists them
|
|
723
|
-
as `feature-1`, `feature-2`, … (`refactor-<n>` ahead of a refactored one). A
|
|
724
|
-
change within the cap is one `feature` slice, as before. See
|
|
725
|
-
[Coarse slices](docs/configuration.md#coarse-slices).
|
|
692
|
+
**One process, one build.** A change is built in one pass — one naive pass, one
|
|
693
|
+
build, one quality lap, one review — however many criteria it has. Sizing the
|
|
694
|
+
work is yours: split a change too big for one process into several before the
|
|
695
|
+
sketch.
|
|
726
696
|
|
|
727
697
|
**Flag mode (`flagMode`, on by default).** On, the naive pass's judge runs as
|
|
728
698
|
above and a rebuilt implementation is asked for a new release flag (none at a
|
|
@@ -787,8 +757,8 @@ When a process finishes, the landing that closes it also keeps the whole run
|
|
|
787
757
|
behind a ref, `refs/gtd/history/<entry>`, so a squash or a deleted branch
|
|
788
758
|
doesn't lose it. `/gtd ship` pushes this process's own ref to origin alongside
|
|
789
759
|
the branch (never the others) and writes a `Gtd-History: <ref>` trailer on the
|
|
790
|
-
commit
|
|
791
|
-
|
|
760
|
+
squashed commit. The push is forced, so it replaces origin's copy of that one
|
|
761
|
+
ref. If that push fails, the ship fails and nothing is squashed. Recover the
|
|
792
762
|
full trail with `gtd history fetch | sh`, then `gtd history show <ref>`.
|
|
793
763
|
|
|
794
764
|
```sh
|
|
@@ -803,12 +773,10 @@ gtd history fetch | sh # pull every kept ref from origin
|
|
|
803
773
|
— every ref, or the ones named. It reads commit subjects and trailers only, so
|
|
804
774
|
it never replays and a config changed since is no refusal:
|
|
805
775
|
|
|
806
|
-
- **refactor-first rate** — of the
|
|
776
|
+
- **refactor-first rate** — of the builds whose naive pass was decided, the
|
|
807
777
|
share rebuilt rather than landed as it is; a landed pass that no longer
|
|
808
778
|
applies and is built afresh counts as rebuilt (`refactored` counts the
|
|
809
779
|
rebuilds a refactor came first for)
|
|
810
|
-
- **continuations** — agent turns that picked up a turn stopped at its context
|
|
811
|
-
limit, per phase (`seam.naive`, `implement.building`, `refactor.building`, …)
|
|
812
780
|
- **cost** — the `Gtd-Cost` a driver recorded with `gtd land --cost --model`, by
|
|
813
781
|
model and by phase (`naive`, `refactor`, `implement`, `other`), and the
|
|
814
782
|
**double work** of a rebuild: its naive pass plus the implement turns that
|
|
@@ -824,7 +792,7 @@ it never replays and a config changed since is no refusal:
|
|
|
824
792
|
`--json` prints `{ processes, total }`; `--json=total.refactorFirstRate` prints
|
|
825
793
|
one value. Model, phase and lens keys may hold dots a path cannot reach: select
|
|
826
794
|
their record whole (`--json=total.cost.byModel`). A branch name selects every
|
|
827
|
-
process kept from that branch. A rate is `null` without a decided
|
|
795
|
+
process kept from that branch. A rate is `null` without a decided build. Each
|
|
828
796
|
process names its `workflow` (`null` for the default one); the total spans every
|
|
829
797
|
ref measured, so name the refs to leave another workflow's processes out.
|
|
830
798
|
|
|
@@ -908,7 +876,7 @@ gtd is one package, `@pmelab/gtd`, and every entrypoint shares its version and
|
|
|
908
876
|
|
|
909
877
|
| Import | What it is |
|
|
910
878
|
| -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
911
|
-
| `@pmelab/gtd/flows` | The flow API a workflow is written in: the steps (`agent`, `human`, `run`, …), `scope
|
|
879
|
+
| `@pmelab/gtd/flows` | The flow API a workflow is written in: the steps (`agent`, `human`, `run`, …), `scope`, workflow factories, `defineConfig` and `FLOWS_PROTOCOL`. |
|
|
912
880
|
| `@pmelab/gtd/wire` | The driver protocol: `WIRE_SCHEMA`, the exit codes and the types of every `--json` document. |
|
|
913
881
|
| `@pmelab/gtd/steering` | The steering-file formats (`qa`, `review`) and the review-note readers. |
|
|
914
882
|
| `@pmelab/gtd/workflow-kit` | Policy-free building blocks to compose a workflow from: health checks, quality laps, scenario freezing, review tails. |
|
|
@@ -921,17 +889,11 @@ install for any of these but `./testkit`: gtd resolves a `gtd.config.ts`'s
|
|
|
921
889
|
`@pmelab/gtd/*` imports to its own copy.
|
|
922
890
|
|
|
923
891
|
The `gtd` bin's commands — `next`, `land`, `abandon`, `restore`, `validate`,
|
|
924
|
-
`check`, `uncheck`, `install`, `summary`, `base`, `door`, `doors`, `
|
|
925
|
-
`
|
|
926
|
-
`history
|
|
927
|
-
`
|
|
892
|
+
`check`, `uncheck`, `install`, `summary`, `base`, `door`, `doors`, `version`,
|
|
893
|
+
`help`, `history list`, `history show`, `history metrics`, `history judges`,
|
|
894
|
+
`history fetch`, `strays`, and the tools `ui`, `lsp`, `judge` (`judge answer`,
|
|
895
|
+
`judge run`) — are listed in the
|
|
928
896
|
[CLI reference](https://github.com/pmelab/gtd/blob/main/docs/cli.md).
|
|
929
|
-
`gtd slices` lists the spans a flow's `slice()` calls mark. It refuses (exit 1)
|
|
930
|
-
when HEAD neither closes nor sits inside a process, or when today's config
|
|
931
|
-
disagrees with history about the process; a version mismatch only warns on
|
|
932
|
-
stderr. `gtd summary --slice <name>` prints the closing-message prompt for one
|
|
933
|
-
of those slices — its own range, human commits and cost — and inherits those
|
|
934
|
-
refusals; an unknown name is refused with every valid slice listed.
|
|
935
897
|
|
|
936
898
|
## License
|
|
937
899
|
|
package/claude/hooks/drive.ts
CHANGED
|
@@ -20,7 +20,6 @@ export type Beat = {
|
|
|
20
20
|
skills?: string[]
|
|
21
21
|
validate?: string
|
|
22
22
|
access?: AccessDef
|
|
23
|
-
context?: { soft?: number; hard?: number }
|
|
24
23
|
judge?: unknown
|
|
25
24
|
changes?: { status: string; path: string }[]
|
|
26
25
|
session?: { id?: string; resume?: boolean | string }
|
|
@@ -43,7 +42,6 @@ export type Turn = {
|
|
|
43
42
|
skills: readonly string[]
|
|
44
43
|
scope: string
|
|
45
44
|
access?: AccessDef
|
|
46
|
-
context?: { soft?: number; hard?: number }
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
// `isLost`: the scope's subagent no longer takes messages.
|
|
@@ -185,7 +183,6 @@ function turnOf(b: Beat): Turn {
|
|
|
185
183
|
skills: b.skills ?? [],
|
|
186
184
|
scope: memory.split("#")[0]!,
|
|
187
185
|
access: b.access,
|
|
188
|
-
context: b.context,
|
|
189
186
|
}
|
|
190
187
|
}
|
|
191
188
|
|
|
@@ -23,9 +23,6 @@ import { personaSpec, resumable } from "./persona"
|
|
|
23
23
|
import { JUDGE_SYSTEM, judgePrompt, judgeQuestions, judgeRunEnv, toVerdicts } from "./judge"
|
|
24
24
|
import type { Judgment } from "./judge"
|
|
25
25
|
import { catchFrom, throwTo } from "./handoff"
|
|
26
|
-
import { classicResult, decide, guardOf } from "./context"
|
|
27
|
-
import { contextHook } from "../../src/wire/ContextLimit.js"
|
|
28
|
-
import type { Guard, HookEvent } from "./context"
|
|
29
26
|
import { jobs } from "./jobs"
|
|
30
27
|
import { documents, readDocument } from "./schema"
|
|
31
28
|
import { READ_ONLY_GIT, ship } from "./ship"
|
|
@@ -82,23 +79,12 @@ const ours = new Set<string>()
|
|
|
82
79
|
const accesses = new Map<string, AccessDef>()
|
|
83
80
|
const setAccess = (id: string, a: AccessDef | undefined) =>
|
|
84
81
|
void (a ? accesses.set(id, a) : accesses.delete(id))
|
|
85
|
-
// Each turn agent's context limit, reset with its access; and why a worker
|
|
86
|
-
// was stopped at it, for the turn's end to report.
|
|
87
|
-
const guards = new Map<string, Guard>()
|
|
88
|
-
const contextStops = new Map<string, string>()
|
|
89
82
|
async function workerModel($: $, hint: string | undefined) {
|
|
90
83
|
return subagentModel(hint, {
|
|
91
84
|
smart: await $.env.get("GTD_PLANNERMODEL"),
|
|
92
85
|
base: await $.env.get("GTD_CODERMODEL"),
|
|
93
86
|
})
|
|
94
87
|
}
|
|
95
|
-
async function setGuard($: $, id: string, t: Pick<Turn, "context" | "model">) {
|
|
96
|
-
const model = await workerModel($, t.model)
|
|
97
|
-
const session = model === undefined ? (await $.session.usage()).context.window : undefined
|
|
98
|
-
const guard = guardOf(t.context, model, await $.env.get("GTD_LOOP_CONTEXT_WINDOW"), session)
|
|
99
|
-
if (guard) guards.set(id, guard)
|
|
100
|
-
else guards.delete(id)
|
|
101
|
-
}
|
|
102
88
|
const waiting = new Map<string, (end: TurnEnd) => void>()
|
|
103
89
|
const ended = new Map<string, TurnEnd>()
|
|
104
90
|
const personas = new Set<string>()
|
|
@@ -151,8 +137,6 @@ async function send($: $, agentId: string, text: string) {
|
|
|
151
137
|
async function forget($: $, memory: string, agentId: string) {
|
|
152
138
|
$.ui.log(lostLine(memory.split("#")[0]!))
|
|
153
139
|
accesses.delete(agentId)
|
|
154
|
-
guards.delete(agentId)
|
|
155
|
-
contextStops.delete(agentId)
|
|
156
140
|
await update($, scopes, (s) => {
|
|
157
141
|
const rest = { ...s }
|
|
158
142
|
if (rest[memory]?.agentId === agentId) delete rest[memory]
|
|
@@ -224,7 +208,6 @@ function io($: $): Io {
|
|
|
224
208
|
if (t.resume && entry && !known) $.ui.log(restartLine(t.scope))
|
|
225
209
|
if (t.resume && known) {
|
|
226
210
|
setAccess(known, t.access)
|
|
227
|
-
await setGuard($, known, t)
|
|
228
211
|
const end = send($, known, t.prompt)
|
|
229
212
|
await update($, run, (r) => ({ ...r, inflight: { agentId: known, memory: t.memory } }))
|
|
230
213
|
const ended = await end
|
|
@@ -244,7 +227,6 @@ function io($: $): Io {
|
|
|
244
227
|
if (!id) return settle($, { ok: false, why: spawned.deny ?? "subagent spawn refused" })
|
|
245
228
|
ours.add(id)
|
|
246
229
|
setAccess(id, t.access)
|
|
247
|
-
await setGuard($, id, t)
|
|
248
230
|
await update($, agents, (list) => [...list, id].slice(-200))
|
|
249
231
|
await update($, scopes, (s) => ({ ...s, [t.memory]: { agentId: id, persona: name } }))
|
|
250
232
|
await update($, run, (r) => ({ ...r, inflight: { agentId: id, memory: t.memory } }))
|
|
@@ -771,7 +753,7 @@ async function shipNow($: $, isDry: boolean, isInTurn: boolean) {
|
|
|
771
753
|
isInTurn,
|
|
772
754
|
async () => {
|
|
773
755
|
$.ui.status(isDry ? "previewing the pull request…" : "opening the pull request…")
|
|
774
|
-
return ship(shipIo($), isDry
|
|
756
|
+
return ship(shipIo($), isDry)
|
|
775
757
|
},
|
|
776
758
|
async (shipped) => {
|
|
777
759
|
// a failed ship leaves the run as it was; a dry run changes nothing
|
|
@@ -1076,40 +1058,6 @@ async function isOurs($: $, id: string) {
|
|
|
1076
1058
|
return isWorker
|
|
1077
1059
|
}
|
|
1078
1060
|
|
|
1079
|
-
// `gtd context-hook`'s decision for a worker: past its soft limit it is asked
|
|
1080
|
-
// for a handoff note; past its hard limit, or about to compact, its task is
|
|
1081
|
-
// stopped and its turn ends as a failure carrying the stop reason. Stopped off
|
|
1082
|
-
// the hook, which runs inside the worker's own tool call.
|
|
1083
|
-
async function stopWorker($: $, id: string, stopReason: string) {
|
|
1084
|
-
if (contextStops.has(id)) return
|
|
1085
|
-
contextStops.set(id, stopReason)
|
|
1086
|
-
$.clock.after(0, () => {
|
|
1087
|
-
void $.tool.call({ tool: "TaskStop", task_id: id }).catch((err: unknown) => {
|
|
1088
|
-
$.ui.log(`gtd could not stop a subagent at its context limit: ${String(err)}`)
|
|
1089
|
-
})
|
|
1090
|
-
})
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
async function contextGuard($: $, e: HookEvent) {
|
|
1094
|
-
const id = e.agent_id
|
|
1095
|
-
if (!id || !ours.has(id)) return undefined
|
|
1096
|
-
const decision = await decide(e, guards.get(id), async (path) =>
|
|
1097
|
-
(await $.fs.exists(path)) ? $.fs.read(path) : undefined,
|
|
1098
|
-
)
|
|
1099
|
-
if (decision?.kind === "hard") await stopWorker($, id, decision.stopReason)
|
|
1100
|
-
return decision && classicResult(decision)
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
// A worker's own compaction carries its id here (`classic.PreCompact` is not
|
|
1104
|
-
// documented to): compaction about to happen means its limit was missed.
|
|
1105
|
-
async function compactGuard($: $, agentId: string | undefined) {
|
|
1106
|
-
if (!agentId || !ours.has(agentId) || !guards.has(agentId)) return undefined
|
|
1107
|
-
const decision = contextHook({ event: "PreCompact", tokens: undefined, ...guards.get(agentId)! })
|
|
1108
|
-
if (decision?.kind !== "hard") return undefined
|
|
1109
|
-
await stopWorker($, agentId, decision.stopReason)
|
|
1110
|
-
return { skip: decision.stopReason }
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
1061
|
// What a subagent this mod spawned may not do: write outside its step's
|
|
1114
1062
|
// access, or, answering into a file, anything but that.
|
|
1115
1063
|
function subagentDenial(agentId: string, e: Record<string, unknown>) {
|
|
@@ -1180,13 +1128,8 @@ export const register: Register = (on) => {
|
|
|
1180
1128
|
void launchPending($)
|
|
1181
1129
|
}
|
|
1182
1130
|
if (e.agentId && ours.has(e.agentId)) {
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
const end: TurnEnd = stopped
|
|
1186
|
-
? { ok: false, why: stopped }
|
|
1187
|
-
: e.reason === "answer"
|
|
1188
|
-
? { ok: true }
|
|
1189
|
-
: { ok: false, why: `agent turn ended: ${e.reason}` }
|
|
1131
|
+
const end: TurnEnd =
|
|
1132
|
+
e.reason === "answer" ? { ok: true } : { ok: false, why: `agent turn ended: ${e.reason}` }
|
|
1190
1133
|
delegates.delete(e.agentId)
|
|
1191
1134
|
const resolve = waiting.get(e.agentId)
|
|
1192
1135
|
waiting.delete(e.agentId)
|
|
@@ -1196,11 +1139,6 @@ export const register: Register = (on) => {
|
|
|
1196
1139
|
return next(e)
|
|
1197
1140
|
})
|
|
1198
1141
|
|
|
1199
|
-
// A worker's tool call hands settings hooks the MAIN session's
|
|
1200
|
-
// `transcript_path`, marked only by `agent_id`/`agent_type`; its own
|
|
1201
|
-
// transcript (`agent_transcript_path`) is named only at SubagentStop, too
|
|
1202
|
-
// late to measure a running worker's context.
|
|
1203
|
-
//
|
|
1204
1142
|
// The loop lands a turn when it ends; work left running in the background
|
|
1205
1143
|
// would still be writing the tree gtd is committing.
|
|
1206
1144
|
on("tool.call", async ($, e, next) => {
|
|
@@ -1217,9 +1155,6 @@ export const register: Register = (on) => {
|
|
|
1217
1155
|
return next(e)
|
|
1218
1156
|
})
|
|
1219
1157
|
|
|
1220
|
-
on("classic.PostToolUse", async ($, e, next) => (await contextGuard($, e)) ?? next(e))
|
|
1221
|
-
on("session.compact", async ($, e, next) => (await compactGuard($, e.agentId)) ?? next(e))
|
|
1222
|
-
|
|
1223
1158
|
// A subagent's hand-back arrives as a prompt to the main session, whose model
|
|
1224
1159
|
// would then act on it. gtd reads the turn's result from git instead.
|
|
1225
1160
|
on("prompt.submit", async ($, e, next) => {
|