@pmelab/gtd 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,11 +5,12 @@
5
5
  > in the first place. Now I have something that actually helps me.
6
6
 
7
7
  A git-aware CLI that drives a turn-taking loop between a human and an autonomous
8
- coding agent: capture an idea, grill it into a plan, decompose it into work
9
- packages, execute with parallel subagents, test, agentically review each
10
- package, walk a human through a review, distill durable lessons from the cycle
11
- into the project's own docs, and finally squash the whole cycle into one
12
- conventional-commits commit at the end.
8
+ coding agent: capture an idea, grill it into a product-level plan, grill that
9
+ into a technical architecture, decompose it into work packages, execute with
10
+ parallel subagents, test, agentically review each package, walk a human through
11
+ a review, distill durable lessons from the cycle into the project's own docs,
12
+ and finally squash the whole cycle into one conventional-commits commit at the
13
+ end.
13
14
 
14
15
  Internally, gtd is a **pure fold** over git history. The decision core
15
16
  (`src/Machine.ts`) is a single IO-free function, `resolve(events)` — **no
@@ -19,7 +20,7 @@ merge-base with the default branch (whole-history fallback when there is no
19
20
  default branch, when HEAD equals the merge-base, or when there is no merge-base)
20
21
  plus the working tree, turns them into a `COMMIT[]` + single terminal `RESOLVE`
21
22
  event stream, and folds them through the machine. The fold lands on exactly
22
- **one** of 20 states, plus which actor (human or agent) is awaited there. A
23
+ **one** of 21 states, plus which actor (human or agent) is awaited there. A
23
24
  single call resolves to a single state.
24
25
 
25
26
  Steering is entirely **machine-authored commit subjects** — there are no marker
@@ -28,7 +29,8 @@ files, sentinels, or auto-advance tails to parse. A turn commit looks like
28
29
  the machine performs itself between turns) looks like `gtd: tests green`.
29
30
  `src/Subjects.ts` is the closed grammar both the machine and the edge read.
30
31
 
31
- All workflow state lives under **`.gtd/`**: the plan (`.gtd/TODO.md`), work
32
+ All workflow state lives under **`.gtd/`**: the product plan (`.gtd/TODO.md`),
33
+ the technical architecture it converges into (`.gtd/ARCHITECTURE.md`), work
32
34
  packages (`.gtd/01-…/`), review records (`.gtd/REVIEW.md`, `.gtd/FEEDBACK.md`),
33
35
  and loop bookkeeping (`.gtd/ERRORS.md`, `.gtd/HEALTH.md`, `.gtd/LEARNINGS.md`,
34
36
  `.gtd/SQUASH_MSG.md`). One rule follows for every agent in the loop: **never
@@ -56,9 +58,10 @@ An agent loop is a two-beat protocol repeated forever:
56
58
  act, then go back to step 1; at a pending checkpoint (`prompt` is null) go
57
59
  straight back to step 1.
58
60
 
59
- A human acts by editing files (answering questions in `.gtd/TODO.md`, annotating
60
- `.gtd/REVIEW.md`, fixing code) and then running `gtd step` to capture the edit
61
- as their turn and hand control back to the agent side of the loop.
61
+ A human acts by editing files (answering questions in `.gtd/TODO.md` or
62
+ `.gtd/ARCHITECTURE.md`, annotating `.gtd/REVIEW.md`, fixing code) and then
63
+ running `gtd step` to capture the edit as their turn and hand control back to
64
+ the agent side of the loop.
62
65
 
63
66
  ```bash
64
67
  gtd step-agent # advance the machine's own bookkeeping
@@ -166,8 +169,8 @@ invocation authored (oldest→newest), then a final `state: <state>` line:
166
169
 
167
170
  ```
168
171
  committed: gtd(human): grilling
169
- committed: gtd: grilled
170
- state: grilled
172
+ committed: gtd: architecting
173
+ state: architecting
171
174
  ```
172
175
 
173
176
  `--json` emits `{state, actions, commits}` instead (see
@@ -296,9 +299,9 @@ single-line JSON output instead of plain text.
296
299
 
297
300
  ```json
298
301
  {
299
- "state": "grilled",
302
+ "state": "architecting",
300
303
  "actions": ["capture the human turn as \"gtd(human): grilling\""],
301
- "commits": ["gtd(human): grilling", "gtd: grilled"]
304
+ "commits": ["gtd(human): grilling", "gtd: architecting"]
302
305
  }
303
306
  ```
304
307
 
@@ -429,9 +432,9 @@ GTD_LOOP_AGENT_CMD='my-agent-cli --prompt "$GTD_LOOP_PROMPT"' gtd-loop
429
432
 
430
433
  ## States & subjects overview
431
434
 
432
- `resolve()` lands on exactly one of **20 states**: `grilling`, `grilled`,
433
- `planning`, `building`, `testing`, `fixing`, `escalate`, `agentic-review`,
434
- `close-package`, `review`, `await-review`, `done`, `learning`,
435
+ `resolve()` lands on exactly one of **21 states**: `grilling`, `architecting`,
436
+ `grilled`, `planning`, `building`, `testing`, `fixing`, `escalate`,
437
+ `agentic-review`, `close-package`, `review`, `await-review`, `done`, `learning`,
435
438
  `await-learning-review`, `learning-apply`, `learning-applied`, `squashing`,
436
439
  `idle`, `health-check`, `health-fixing`. Each state has a fixed awaited actor
437
440
  (see `awaitedActor` in `src/Machine.ts`): `idle`, `escalate`, `await-review`,
@@ -448,7 +451,8 @@ The closed set of gates:
448
451
 
449
452
  | Gate | Authored by |
450
453
  | ---------------- | ------------------------------------------------------------------ |
451
- | `grilling` | human (answers) / agent (plan iteration) |
454
+ | `grilling` | human (answers) / agent (product-plan iteration) |
455
+ | `architecting` | human (answers) / agent (architecture iteration) |
452
456
  | `grilled` | agent (converged, ready to decompose) |
453
457
  | `building` | agent (package work, or human feedback while agent is out of turn) |
454
458
  | `fixing` | agent (test-fix or review-fix round) |
@@ -463,8 +467,8 @@ The closed set of gates:
463
467
  ### Routing commits — `gtd: <phase>`
464
468
 
465
469
  Bookkeeping the machine authors itself between turns, never a turn a human or
466
- agent "wins": `gtd: grilled`, `gtd: planning`, `gtd: tests green`,
467
- `gtd: errors`, `gtd: package done`, `gtd: awaiting review`,
470
+ agent "wins": `gtd: architecting`, `gtd: grilled`, `gtd: planning`,
471
+ `gtd: tests green`, `gtd: errors`, `gtd: package done`, `gtd: awaiting review`,
468
472
  `gtd: review feedback`, `gtd: done`, `gtd: squash template`,
469
473
  `gtd: reviewing <hash>` (parameterized, from `gtd review`), `gtd: health-check`,
470
474
  `gtd: health-fix`, `gtd: learning template`, `gtd: learning drafted`,
@@ -477,25 +481,39 @@ this matters on upgrade.
477
481
 
478
482
  ## Workflow walkthroughs
479
483
 
480
- ### Grilling
484
+ ### Grilling: two phases, product then architecture
481
485
 
482
486
  A dirty tree at a boundary HEAD (a fresh idea, sketched in a file or just left
483
487
  as pending code) is captured in **one** human turn: `gtd step` commits
484
488
  everything pending as `gtd(human): grilling` — nothing is reverted or seeded,
485
489
  the captured files stay in history. `gtd next` hands the agent that turn's diff;
486
- the agent develops `.gtd/TODO.md` into a concrete plan **in one turn**,
487
- proposing a **suggested default** for every open question, and leaves
488
- `.gtd/TODO.md` uncommitted for `gtd(agent): grilling`.
490
+ the agent develops `.gtd/TODO.md` into a concrete **product-level** plan **in
491
+ one turn** user-facing decisions only, no architecture — proposing a
492
+ **suggested default** for every open question, and leaves `.gtd/TODO.md`
493
+ uncommitted for `gtd(agent): grilling`.
489
494
 
490
495
  There are no markers to answer — the human either:
491
496
 
492
497
  - **Accepts the suggested defaults**: runs a clean `gtd step` at the answer
493
- gate. An empty `gtd(human): grilling` turn plus routing `gtd: grilled` lands
494
- automatically, and `gtd next` emits the decompose prompt.
498
+ gate. An empty `gtd(human): grilling` turn plus routing `gtd: architecting`
499
+ lands automatically `.gtd/ARCHITECTURE.md` is seeded from the converged
500
+ `.gtd/TODO.md` content and `.gtd/TODO.md` is deleted, in that one commit.
495
501
  - **Edits `.gtd/TODO.md`** with real answers, then runs `gtd step`, which
496
502
  captures the edit as a fresh `gtd(human): grilling` turn and hands it back to
497
503
  the agent for another round.
498
504
 
505
+ Technical architecting works exactly the same way, one file later: the agent
506
+ develops `.gtd/ARCHITECTURE.md` into a concrete **technical** plan — file/module
507
+ structure, data model, tech-stack choices — and the human answers or accepts
508
+ defaults at the `architecting` gate. Accepting converges to `gtd: grilled` and
509
+ `gtd next` emits the decompose prompt (which now reads `.gtd/ARCHITECTURE.md`).
510
+
511
+ **Escape hatch for already-technical input:** if the human's initial dirty tree
512
+ already contains `.gtd/ARCHITECTURE.md` (their own technical sketch), `gtd step`
513
+ captures the entry turn as `gtd(human): architecting` directly, skipping product
514
+ grilling for that cycle entirely — no CLI flag needed, it's driven purely by
515
+ which steering file is present.
516
+
499
517
  ### Build lifecycle: budgets
500
518
 
501
519
  Once decomposed, `.gtd/` holds ordered work packages. `gtd next` at
@@ -531,16 +549,16 @@ the threshold simply closes the package as usual.) Setting
531
549
  `agenticReview: false` force-approves every package immediately.
532
550
 
533
551
  A **do-nothing agent invocation** — `gtd step-agent` on a clean tree at ANY
534
- agent-awaited rest whose move is a file artifact (`grilling`, `grilled`,
535
- `building`, `fixing`, `agentic-review`, `review`, `squashing` while
552
+ agent-awaited rest whose move is a file artifact (`grilling`, `architecting`,
553
+ `grilled`, `building`, `fixing`, `agentic-review`, `review`, `squashing` while
536
554
  `.gtd/SQUASH_MSG.md` still holds the unmodified template, `learning` while
537
555
  `.gtd/LEARNINGS.md` still holds the unmodified template, and `learning-apply`
538
556
  unconditionally) — is inert: zero commits, no state consumed; `gtd next`
539
557
  re-emits the same prompt. This is load-bearing for the loop protocol, whose
540
558
  every iteration opens with `gtd step-agent` before the agent has acted: without
541
559
  the guard that opening beat would author junk empty turns — and worse, consume
542
- workflow state (an empty decompose turn would delete `.gtd/TODO.md` with no
543
- packages written; an empty squashing turn would squash the cycle under the
560
+ workflow state (an empty decompose turn would delete `.gtd/ARCHITECTURE.md` with
561
+ no packages written; an empty squashing turn would squash the cycle under the
544
562
  placeholder template). The same guards hold at the classification layer for
545
563
  histories that already carry such turns: a `gtd(agent): grilled` HEAD only
546
564
  routes to `gtd: planning` when packages exist, a `gtd(agent): review` HEAD only
@@ -549,8 +567,8 @@ routes to `gtd: awaiting review` when `.gtd/REVIEW.md` exists, and a squashing
549
567
  deliberate exception is `health-fixing`, whose empty turn is meaningful (the
550
568
  failure may have been environmental — the machine removes `.gtd/HEALTH.md` and
551
569
  re-tests). Human gates are unaffected: an empty **human** turn stays a signal
552
- (accept-defaults at grilling, clean approval at review, accept-the-draft-as-is
553
- at the learning review gate).
570
+ (accept-defaults at grilling/architecting, clean approval at review,
571
+ accept-the-draft-as-is at the learning review gate).
554
572
 
555
573
  ### Human review gate
556
574
 
@@ -622,15 +640,16 @@ With `squash: true` (the default), `gtd: done` (or, once learning has run,
622
640
  to `gtd: squash template`, writing and committing a `.gtd/SQUASH_MSG.md`
623
641
  template. `gtd next` then emits the squashing prompt: the agent overwrites
624
642
  `.gtd/SQUASH_MSG.md` with a real conventional-commits message (drawing on
625
- grilling-round decisions from history) and finishes its turn. `gtd step-agent`
626
- then performs the squash itself: `git reset --soft <base>` + `git commit`,
627
- collapsing every intermediate `gtd: *` commit of the cycle into one — including
628
- any review-feedback detours, and the learning phase's own commits if learning
629
- ran: the squash base is the cycle's ORIGINAL start (the first grilling run since
630
- the previous `gtd: done` boundary, or the `gtd: reviewing <hash>` anchor for an
631
- ad-hoc review cycle), not the most recent re-grilling round the collapse folds
632
- the whole cycle into one, using the overwritten message's content verbatim (turn
633
- position, not message content, triggers the squash). Doc edits made during
643
+ grilling- and architecting-round decisions from history) and finishes its turn.
644
+ `gtd step-agent` then performs the squash itself: `git reset --soft <base>` +
645
+ `git commit`, collapsing every intermediate `gtd: *` commit of the cycle into
646
+ one — including any review-feedback detours, and the learning phase's own
647
+ commits if learning ran: the squash base is the cycle's ORIGINAL start (the
648
+ first grilling or, via the escape hatch, architecting turn since the previous
649
+ `gtd: done` boundary, or the `gtd: reviewing <hash>` anchor for an ad-hoc review
650
+ cycle), not the most recent re-grilling round the collapse folds the whole
651
+ cycle into one, using the overwritten message's content verbatim (turn position,
652
+ not message content, triggers the squash). Doc edits made during
634
653
  `learning-apply` survive in the squashed tree, not as their own commit. With
635
654
  `squash: false`, `gtd: done` (or `gtd: learning applied`) is the resting
636
655
  boundary and no template is ever written.
@@ -659,6 +678,7 @@ fix-attempt budget to zero.
659
678
  | State | Awaits | Turn/routing subject at rest |
660
679
  | ----------------------- | -------------- | ---------------------------------------------------------------- |
661
680
  | `grilling` | human or agent | `gtd(human): grilling` / `gtd(agent): grilling` |
681
+ | `architecting` | human or agent | `gtd: architecting` / `gtd(agent): architecting` |
662
682
  | `grilled` | agent | `gtd: grilled` |
663
683
  | `planning` | agent | `.gtd/` modified |
664
684
  | `building` | agent | `gtd: planning` / `gtd: package done` |
@@ -721,13 +741,14 @@ built-in defaults apply. Supported filenames (searched in this order):
721
741
  phase entirely — independent of `squash`.
722
742
  - **`models`** — model selection for the subagent-spawning states:
723
743
  - `planning` — high-reasoning tier (default `claude-opus-4-8`), used by
724
- `decompose` (the `grilled`/`planning` states), `grilling`, `agentic-review`,
725
- and `clean` (the `review`/`squashing`/`learning`/`learning-apply` states).
744
+ `decompose` (the `grilled`/`planning` states), `grilling`, `architecting`,
745
+ `agentic-review`, and `clean` (the `review`/`squashing`/`learning`/
746
+ `learning-apply` states).
726
747
  - `execution` — everyday tier (default `claude-sonnet-4-8`), used by
727
748
  `building` and `fixing`.
728
749
  - `states.*` — per-state overrides keyed by `decompose`, `grilling`,
729
- `building`, `fixing`, `agentic-review`, `clean`. Unknown `states` keys are
730
- **rejected**.
750
+ `architecting`, `building`, `fixing`, `agentic-review`, `clean`. Unknown
751
+ `states` keys are **rejected**.
731
752
  - **`$schema`** (string, optional) — stripped before validation, so it never
732
753
  counts as an unknown key. Point it at the published schema for editor-backed
733
754
  autocompletion. A `schema.json` is generated from the config schema at build
@@ -815,7 +836,7 @@ models:
815
836
  ### Decompose
816
837
 
817
838
  The Grilled/Planning states spawn a planning-model subagent that breaks
818
- `.gtd/TODO.md` into executable work packages under `.gtd/`:
839
+ `.gtd/ARCHITECTURE.md` into executable work packages under `.gtd/`:
819
840
 
820
841
  ```
821
842
  .gtd/
@@ -367039,6 +367039,7 @@ var Cwd = class _Cwd extends Context_exports.Tag("Cwd")() {
367039
367039
  var stateTier = {
367040
367040
  decompose: "planning",
367041
367041
  grilling: "planning",
367042
+ architecting: "planning",
367042
367043
  building: "execution",
367043
367044
  fixing: "execution",
367044
367045
  "agentic-review": "planning",
@@ -367057,6 +367058,7 @@ var DEFAULT_REVIEW_THRESHOLD = 3;
367057
367058
  var ModelStatesSchema = Schema_exports.Struct({
367058
367059
  decompose: Schema_exports.optional(Schema_exports.String),
367059
367060
  grilling: Schema_exports.optional(Schema_exports.String),
367061
+ architecting: Schema_exports.optional(Schema_exports.String),
367060
367062
  building: Schema_exports.optional(Schema_exports.String),
367061
367063
  fixing: Schema_exports.optional(Schema_exports.String),
367062
367064
  "agentic-review": Schema_exports.optional(Schema_exports.String),
@@ -387758,6 +387760,7 @@ var formatFile = (path16) => Effect_exports.gen(function* () {
387758
387760
  // src/Subjects.ts
387759
387761
  var turnSubject = (actor, gate) => `gtd(${actor}): ${gate}`;
387760
387762
  var ROUTING_SUBJECT = {
387763
+ architecting: "gtd: architecting",
387761
387764
  grilled: "gtd: grilled",
387762
387765
  planning: "gtd: planning",
387763
387766
  "tests-green": "gtd: tests green",
@@ -387778,6 +387781,7 @@ var reviewingSubject = (baseHash) => `gtd: reviewing ${baseHash}`;
387778
387781
  var TURN_RE = /^gtd\((human|agent)\): (.+)$/;
387779
387782
  var TURN_GATES = /* @__PURE__ */ new Set([
387780
387783
  "grilling",
387784
+ "architecting",
387781
387785
  "grilled",
387782
387786
  "building",
387783
387787
  "fixing",
@@ -387822,6 +387826,7 @@ var parseSubject = (subject) => {
387822
387826
  // src/Events.ts
387823
387827
  var GTD_DIR = ".gtd";
387824
387828
  var TODO_FILE = `${GTD_DIR}/TODO.md`;
387829
+ var ARCHITECTURE_FILE = `${GTD_DIR}/ARCHITECTURE.md`;
387825
387830
  var REVIEW_FILE = `${GTD_DIR}/REVIEW.md`;
387826
387831
  var FEEDBACK_FILE = `${GTD_DIR}/FEEDBACK.md`;
387827
387832
  var ERRORS_FILE = `${GTD_DIR}/ERRORS.md`;
@@ -387834,6 +387839,7 @@ var DONE_SUBJECT = "gtd: done";
387834
387839
  var WORKFLOW_FILE_EXCLUDES = [GTD_DIR];
387835
387840
  var GATE_OWN_STEERING_FILE = {
387836
387841
  grilling: TODO_FILE,
387842
+ architecting: ARCHITECTURE_FILE,
387837
387843
  review: REVIEW_FILE
387838
387844
  };
387839
387845
  var SQUASH_OR_LEARNING_ROUTING_PHASES = /* @__PURE__ */ new Set([
@@ -388037,6 +388043,7 @@ var gatherEvents = (invoker) => (
388037
388043
  const gtdModified = entries2.some((e7) => isPackagePath(e7.path));
388038
388044
  const codeDirty = entries2.some((e7) => !isGtdPath(e7.path));
388039
388045
  const todoExists = yield* fs9.exists(resolve5(TODO_FILE));
388046
+ const architectureExists = yield* fs9.exists(resolve5(ARCHITECTURE_FILE));
388040
388047
  const reviewPresent = yield* fs9.exists(resolve5(REVIEW_FILE));
388041
388048
  const feedbackPresent = yield* fs9.exists(resolve5(FEEDBACK_FILE));
388042
388049
  const errorsPresent = yield* fs9.exists(resolve5(ERRORS_FILE));
@@ -388051,6 +388058,7 @@ var gatherEvents = (invoker) => (
388051
388058
  const reviewCommitted = reviewTrackedAtHead && workingTreeClean;
388052
388059
  const reviewDirty = reviewTrackedAtHead && !workingTreeClean;
388053
388060
  const todoCommitted = todoExists && !isUncommitted(TODO_FILE);
388061
+ const architectureCommitted = architectureExists && !isUncommitted(ARCHITECTURE_FILE);
388054
388062
  const pendingErrorsDeletion = entries2.some(
388055
388063
  (e7) => e7.path === ERRORS_FILE && e7.status.includes("D")
388056
388064
  );
@@ -388085,7 +388093,7 @@ var gatherEvents = (invoker) => (
388085
388093
  }
388086
388094
  const isGrillingTurn = (message) => {
388087
388095
  const parsed = parseSubject(subjectOf(message));
388088
- return parsed.kind === "turn" && parsed.gate === "grilling";
388096
+ return parsed.kind === "turn" && (parsed.gate === "grilling" || parsed.gate === "architecting");
388089
388097
  };
388090
388098
  const firstGrilling = currentCycle.find((c7) => isGrillingTurn(c7.message));
388091
388099
  const lastAwaitingReview = (() => {
@@ -388133,7 +388141,7 @@ var gatherEvents = (invoker) => (
388133
388141
  const squashCycle = squashHistory.slice(prevDoneIdx + 1, lastDoneIdxForSquash + 1);
388134
388142
  const isGrillingTurnSubject = (subject) => {
388135
388143
  const parsed = parseSubject(subject);
388136
- return parsed.kind === "turn" && parsed.gate === "grilling";
388144
+ return parsed.kind === "turn" && (parsed.gate === "grilling" || parsed.gate === "architecting");
388137
388145
  };
388138
388146
  const isReviewingAnchor = (subject) => {
388139
388147
  const parsed = parseSubject(subject);
@@ -388213,6 +388221,8 @@ var gatherEvents = (invoker) => (
388213
388221
  ...headTurnReviewSubstantive !== void 0 ? { headTurnReviewSubstantive } : {},
388214
388222
  todoExists,
388215
388223
  todoCommitted,
388224
+ architectureExists,
388225
+ architectureCommitted,
388216
388226
  packagesPresent: packages.length > 0,
388217
388227
  reviewPresent,
388218
388228
  feedbackPresent,
@@ -388272,6 +388282,7 @@ var SQUASH_TEMPLATE = [
388272
388282
  "Longer description of the change, if needed.",
388273
388283
  ""
388274
388284
  ].join("\n");
388285
+ var ARCHITECTURE_SEED_BANNER = "<!-- gtd: seeded from the converged product plan (.gtd/TODO.md); decide the technical/architectural questions below. -->\n\n";
388275
388286
  var LEARNING_TEMPLATE = [
388276
388287
  "<!-- gtd: replace this file's content with the actual distilled learnings for this cycle. -->",
388277
388288
  "<!-- Keep only durable, generalizable lessons \u2014 delete anything that's a one-off detail. -->",
@@ -388290,22 +388301,38 @@ var perform = (action) => (
388290
388301
  const resolve5 = (p4) => join10(root2, p4);
388291
388302
  const ensureGtdDir = fs9.makeDirectory(resolve5(GTD_DIR), { recursive: true }).pipe(Effect_exports.catchAll(() => Effect_exports.void));
388292
388303
  switch (action.kind) {
388293
- // Capture a human/agent turn: format the pending TODO.md (best-effort),
388294
- // then commit-all under `gtd(<actor>): <gate>` (--allow-empty).
388304
+ // Capture a human/agent turn: format the pending TODO.md/ARCHITECTURE.md
388305
+ // (best-effort), then commit-all under `gtd(<actor>): <gate>` (--allow-empty).
388295
388306
  case "captureTurn": {
388296
388307
  const todoExists = yield* fs9.exists(resolve5(TODO_FILE));
388297
388308
  if (todoExists) {
388298
388309
  yield* formatFile(resolve5(TODO_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.void));
388299
388310
  }
388311
+ const architectureExists = yield* fs9.exists(resolve5(ARCHITECTURE_FILE));
388312
+ if (architectureExists) {
388313
+ yield* formatFile(resolve5(ARCHITECTURE_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.void));
388314
+ }
388300
388315
  yield* git.commitAllWithPrefix(turnSubject(action.actor, action.gate));
388301
388316
  return { stop: false };
388302
388317
  }
388303
388318
  // Routing bookkeeping: delete the flagged files FIRST so their removal
388304
388319
  // lands in this same commit, then commit-all under `subject`.
388320
+ // `seedArchitectureFromTodo` instead reads TODO.md, writes it (with a
388321
+ // scaffold banner) as ARCHITECTURE.md, and deletes TODO.md — the
388322
+ // grilling→architecting hand-off, in this same commit.
388305
388323
  case "commitRouting": {
388306
- if (action.removeTodo === true) {
388324
+ if (action.seedArchitectureFromTodo === true) {
388325
+ const todoContent = yield* fs9.readFileString(resolve5(TODO_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.succeed("")));
388326
+ yield* ensureGtdDir;
388327
+ yield* fs9.writeFileString(
388328
+ resolve5(ARCHITECTURE_FILE),
388329
+ ARCHITECTURE_SEED_BANNER + todoContent
388330
+ );
388307
388331
  yield* fs9.remove(resolve5(TODO_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.void));
388308
388332
  }
388333
+ if (action.removeArchitecture === true) {
388334
+ yield* fs9.remove(resolve5(ARCHITECTURE_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.void));
388335
+ }
388309
388336
  if (action.removeReview === true) {
388310
388337
  yield* fs9.remove(resolve5(REVIEW_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.void));
388311
388338
  }
@@ -388467,6 +388494,8 @@ var DEFAULT_PAYLOAD = {
388467
388494
  headTurnIsEmpty: false,
388468
388495
  todoExists: false,
388469
388496
  todoCommitted: false,
388497
+ architectureExists: false,
388498
+ architectureCommitted: false,
388470
388499
  packagesPresent: false,
388471
388500
  reviewPresent: false,
388472
388501
  feedbackPresent: false,
@@ -388556,6 +388585,9 @@ var learningFileConflictRules = [
388556
388585
  var isReviewPackagesConflict = (p4) => p4.reviewPresent && p4.packagesPresent;
388557
388586
  var isReviewCommittedTodoConflict = (p4) => p4.reviewPresent && p4.todoCommitted;
388558
388587
  var isUncommittedReviewWithTodoConflict = (p4) => p4.reviewPresent && !(p4.reviewCommitted || p4.reviewDirty) && p4.todoExists;
388588
+ var isReviewCommittedArchitectureConflict = (p4) => p4.reviewPresent && p4.architectureCommitted;
388589
+ var isUncommittedReviewWithArchitectureConflict = (p4) => p4.reviewPresent && !(p4.reviewCommitted || p4.reviewDirty) && p4.architectureExists;
388590
+ var isTodoArchitectureConflict = (p4) => p4.todoExists && p4.architectureExists;
388559
388591
  var isFeedbackReviewConflict = (p4) => p4.feedbackPresent && p4.reviewPresent;
388560
388592
  var isFeedbackWithoutPackages = (p4) => p4.feedbackPresent && !p4.packagesPresent;
388561
388593
  var isErrorsFeedbackConflict = (p4) => p4.errorsPresent && p4.feedbackPresent;
@@ -388571,6 +388603,15 @@ var reviewAndFeedbackRules = [
388571
388603
  isViolated: isUncommittedReviewWithTodoConflict,
388572
388604
  message: "uncommitted .gtd/REVIEW.md + .gtd/TODO.md"
388573
388605
  },
388606
+ {
388607
+ isViolated: isReviewCommittedArchitectureConflict,
388608
+ message: ".gtd/REVIEW.md + committed .gtd/ARCHITECTURE.md"
388609
+ },
388610
+ {
388611
+ isViolated: isUncommittedReviewWithArchitectureConflict,
388612
+ message: "uncommitted .gtd/REVIEW.md + .gtd/ARCHITECTURE.md"
388613
+ },
388614
+ { isViolated: isTodoArchitectureConflict, message: ".gtd/TODO.md + .gtd/ARCHITECTURE.md" },
388574
388615
  { isViolated: isFeedbackReviewConflict, message: ".gtd/FEEDBACK.md + .gtd/REVIEW.md" },
388575
388616
  { isViolated: isFeedbackWithoutPackages, message: ".gtd/FEEDBACK.md without packages" },
388576
388617
  { isViolated: isErrorsFeedbackConflict, message: ".gtd/ERRORS.md + .gtd/FEEDBACK.md" },
@@ -388636,15 +388677,30 @@ var classifyHead = (subject, flags) => {
388636
388677
  kind: "mid-chain",
388637
388678
  state: "grilling",
388638
388679
  actor: "agent",
388639
- action: { kind: "commitRouting", subject: "gtd: grilled" }
388680
+ action: {
388681
+ kind: "commitRouting",
388682
+ subject: "gtd: architecting",
388683
+ seedArchitectureFromTodo: true
388684
+ }
388640
388685
  } : { kind: "rest", state: "grilling", actor: "agent" };
388641
388686
  }
388687
+ if (gate === "architecting") {
388688
+ if (actor === "agent") {
388689
+ return flags.headTurnIsEmpty ? { kind: "rest", state: "architecting", actor: "agent" } : { kind: "rest", state: "architecting", actor: "human" };
388690
+ }
388691
+ return flags.headTurnIsEmpty ? {
388692
+ kind: "mid-chain",
388693
+ state: "architecting",
388694
+ actor: "agent",
388695
+ action: { kind: "commitRouting", subject: "gtd: grilled" }
388696
+ } : { kind: "rest", state: "architecting", actor: "agent" };
388697
+ }
388642
388698
  if (actor === "agent" && gate === "grilled") {
388643
388699
  return flags.hasPackages ? {
388644
388700
  kind: "mid-chain",
388645
388701
  state: "grilled",
388646
388702
  actor: "agent",
388647
- action: { kind: "commitRouting", subject: "gtd: planning", removeTodo: true }
388703
+ action: { kind: "commitRouting", subject: "gtd: planning", removeArchitecture: true }
388648
388704
  } : { kind: "rest", state: "grilled", actor: "agent" };
388649
388705
  }
388650
388706
  if (actor === "agent" && gate === "building") {
@@ -388718,6 +388774,8 @@ var classifyHead = (subject, flags) => {
388718
388774
  }
388719
388775
  if (parsed.kind === "routing") {
388720
388776
  switch (parsed.phase) {
388777
+ case "architecting":
388778
+ return { kind: "rest", state: "architecting", actor: "agent" };
388721
388779
  case "grilled":
388722
388780
  return { kind: "rest", state: "grilled", actor: "agent" };
388723
388781
  case "planning":
@@ -388764,6 +388822,7 @@ var classifyHead = (subject, flags) => {
388764
388822
  };
388765
388823
  var STATE_IS_OWN_GATE = /* @__PURE__ */ new Set([
388766
388824
  "grilling",
388825
+ "architecting",
388767
388826
  "grilled",
388768
388827
  "building",
388769
388828
  "fixing",
@@ -388781,6 +388840,7 @@ var GATE_OVERRIDES = {
388781
388840
  var gateForState = (state) => STATE_IS_OWN_GATE.has(state) ? state : GATE_OVERRIDES[state] ?? "review";
388782
388841
  var INERT_EMPTY_AGENT_GATES = /* @__PURE__ */ new Set([
388783
388842
  "grilling",
388843
+ "architecting",
388784
388844
  "grilled",
388785
388845
  "building",
388786
388846
  "fixing",
@@ -388883,6 +388943,9 @@ var resolveBaseline = (p4, counters, head7, corrupt, lastTurn) => {
388883
388943
  if (p4.todoExists) {
388884
388944
  return { kind: "rest", state: "grilling", actor: "agent" };
388885
388945
  }
388946
+ if (p4.architectureExists) {
388947
+ return { kind: "rest", state: "architecting", actor: "agent" };
388948
+ }
388886
388949
  if (p4.packagesPresent && p4.packages.length > 0 && lastTurn?.actor === "agent" && lastTurn.gate === "building") {
388887
388950
  return {
388888
388951
  kind: "mid-chain",
@@ -388891,7 +388954,7 @@ var resolveBaseline = (p4, counters, head7, corrupt, lastTurn) => {
388891
388954
  action: { kind: "runTest", errorCount: 0, capReached: false }
388892
388955
  };
388893
388956
  }
388894
- if (!p4.packagesPresent && !p4.reviewPresent && !p4.feedbackPresent && !p4.errorsPresent && !p4.healthPresent && !p4.todoExists) {
388957
+ if (!p4.packagesPresent && !p4.reviewPresent && !p4.feedbackPresent && !p4.errorsPresent && !p4.healthPresent && !p4.todoExists && !p4.architectureExists) {
388895
388958
  return resolveIdleOrHealth(p4);
388896
388959
  }
388897
388960
  return corrupt();
@@ -388904,13 +388967,14 @@ var resolveIdleOrHealth = (p4) => {
388904
388967
  var applyTurnTaking = (p4, counters, head7, baseline) => {
388905
388968
  const context10 = buildContext(p4, counters);
388906
388969
  const invoker = p4.invoker;
388907
- const isDirtyBoundaryEntry = invoker === "human" && !p4.workingTreeClean && !p4.todoCommitted && !p4.packagesPresent && !p4.reviewPresent && !p4.feedbackPresent && !p4.errorsPresent && !p4.healthPresent && (parseSubject(head7).kind === "boundary" || head7 === "gtd: done");
388970
+ const isDirtyBoundaryEntry = invoker === "human" && !p4.workingTreeClean && !p4.todoCommitted && !p4.architectureCommitted && !p4.packagesPresent && !p4.reviewPresent && !p4.feedbackPresent && !p4.errorsPresent && !p4.healthPresent && (parseSubject(head7).kind === "boundary" || head7 === "gtd: done");
388908
388971
  if (isDirtyBoundaryEntry) {
388972
+ const entryGate = p4.architectureExists ? "architecting" : "grilling";
388909
388973
  return {
388910
- state: "grilling",
388974
+ state: entryGate,
388911
388975
  actor: "human",
388912
388976
  pending: false,
388913
- edgeAction: { kind: "captureTurn", actor: "human", gate: "grilling" },
388977
+ edgeAction: { kind: "captureTurn", actor: "human", gate: entryGate },
388914
388978
  context: context10
388915
388979
  };
388916
388980
  }
@@ -389020,9 +389084,14 @@ var predictTurn = (events) => {
389020
389084
  const subject = baseline.action.kind === "commitRouting" ? baseline.action.subject : baseline.action.kind === "captureTurn" ? `gtd(${baseline.action.actor}): ${baseline.action.gate}` : null;
389021
389085
  return { actor: baseline.actor, subject, state: baseline.state };
389022
389086
  }
389023
- const isDirtyBoundaryEntry = !payload.workingTreeClean && !payload.todoCommitted && !payload.packagesPresent && !payload.reviewPresent && !payload.feedbackPresent && !payload.errorsPresent && !payload.healthPresent && (parseSubject(head7).kind === "boundary" || head7 === "gtd: done");
389087
+ const isDirtyBoundaryEntry = !payload.workingTreeClean && !payload.todoCommitted && !payload.architectureCommitted && !payload.packagesPresent && !payload.reviewPresent && !payload.feedbackPresent && !payload.errorsPresent && !payload.healthPresent && (parseSubject(head7).kind === "boundary" || head7 === "gtd: done");
389024
389088
  if (isDirtyBoundaryEntry) {
389025
- return { actor: "human", subject: "gtd(human): grilling", state: "grilling" };
389089
+ const entryGate = payload.architectureExists ? "architecting" : "grilling";
389090
+ return {
389091
+ actor: "human",
389092
+ subject: `gtd(human): ${entryGate}`,
389093
+ state: entryGate
389094
+ };
389026
389095
  }
389027
389096
  if (baseline.state === "idle") {
389028
389097
  return { actor: "human", subject: null, state: "idle" };
@@ -389511,13 +389580,19 @@ var package_default = '<%\n const lines = ["", "### Package: `" + it.pkg.name +
389511
389580
  var agent_turn_default = "Finish your turn by running `gtd step-agent`. Then run `gtd next` and follow\nits output \u2014 repeat this cycle as long as the output is addressed to you (the\nagent); when it awaits the human, stop and hand off.\n";
389512
389581
 
389513
389582
  // src/prompts/grilling-agent.md
389514
- var grilling_agent_default = '<%~ include("@header") %>\n\n`.gtd/TODO.md` holds the plan under development. Develop it into a concrete,\nimplementation-ready plan **in this one turn** \u2014 use subagents / internal\niteration to go as deep as needed; there is no further agent-only round after\nthis one.\n\n### Develop the plan\n\nSpawn a **planning-model subagent** using model `<%= it.model %>` to develop the plan.\nThe subagent works entirely by editing `.gtd/TODO.md`:\n\n1. **Explore the codebase before asking anything** \u2014 read the relevant files,\n tests, and docs so every question below is one the codebase genuinely\n cannot answer.\n2. **Replace the captured input / seed template with a real implementation\n plan** \u2014 the files to change, exactly what changes, and why, grounded in\n the codebase.\n3. **Iterate internally** (spawn further subagents, reconsider, cross-check)\n until the plan is as complete as it can get without human input. Do not\n leave this to a future round \u2014 there isn\'t one before the human is asked.\n4. For every remaining open question, write it near the top of the file with\n a **suggested default** \u2014 your best-guess answer, stated plainly, that the\n human can accept as-is. A question with no suggested default is incomplete.\n5. Leave `.gtd/TODO.md` **uncommitted** \u2014 the human\'s turn (`gtd step`) reads it\n next.\n<% if (it.context.turnDiff && it.context.turnDiff.trim()) { %>\n<%~ include("@diff", { heading: "Latest human input (answers / sketch / review feedback)", diff: it.context.turnDiff, fenceFor: it.fenceFor }) %>\n\nRead the diff above as **feedback, not finished work**:\n\n- Code changes are suggestions \u2014 fold them into the plan and re-implement them\n properly, including test coverage, rather than restoring them verbatim.\n- Code comments are positional feedback about the code at that location.\n- Plan-text changes are global feedback on the plan (or the reviewed work) as\n a whole.\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
389583
+ var grilling_agent_default = '<%~ include("@header") %>\n\n`.gtd/TODO.md` holds the plan under development. Develop it into a concrete,\nproduct-level plan **in this one turn** \u2014 use subagents / internal iteration\nto go as deep as needed; there is no further agent-only round after this one.\n\n**Scope: product and user-facing decisions only.** Do not decide\nimplementation details in this file \u2014 file/module structure, data models,\nlibrary or tech-stack choices, and other architecture questions belong to the\nnext phase (`.gtd/ARCHITECTURE.md`) and must not be treated as open questions\nhere.\n\n### Develop the plan\n\nSpawn a **planning-model subagent** using model `<%= it.model %>` to develop the plan.\nThe subagent works entirely by editing `.gtd/TODO.md`:\n\n1. **Explore the codebase before asking anything** \u2014 read the relevant files,\n tests, and docs so every question below is one the codebase genuinely\n cannot answer.\n2. **Replace the captured input / seed template with a real product plan** \u2014\n what to build and why, and the user-facing behavior it should have,\n grounded in the codebase. Leave *how* to build it for the next phase.\n3. **Iterate internally** (spawn further subagents, reconsider, cross-check)\n until the plan is as complete as it can get without human input. Do not\n leave this to a future round \u2014 there isn\'t one before the human is asked.\n4. For every remaining open question, write it near the top of the file with\n a **suggested default** \u2014 your best-guess answer, stated plainly, that the\n human can accept as-is. A question with no suggested default is incomplete.\n5. Leave `.gtd/TODO.md` **uncommitted** \u2014 the human\'s turn (`gtd step`) reads it\n next.\n<% if (it.context.turnDiff && it.context.turnDiff.trim()) { %>\n<%~ include("@diff", { heading: "Latest human input (answers / sketch / review feedback)", diff: it.context.turnDiff, fenceFor: it.fenceFor }) %>\n\nRead the diff above as **feedback, not finished work**:\n\n- Code changes are suggestions \u2014 fold them into the plan and re-implement them\n properly, including test coverage, rather than restoring them verbatim.\n- Code comments are positional feedback about the code at that location.\n- Plan-text changes are global feedback on the plan (or the reviewed work) as\n a whole.\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
389515
389584
 
389516
389585
  // src/prompts/grilling-answers.md
389517
- var grilling_answers_default = '<%~ include("@header") %>\n\n`.gtd/TODO.md` holds the plan under development, with open questions written near\nthe top \u2014 each one carrying a suggested default.\n\nThis is a human gate; there is nothing for the agent to do.\n\nTell the user:\n\n- To answer a question, edit `.gtd/TODO.md` in place (replace the question\'s text\n with your answer, or annotate it), then run `gtd step`.\n- To accept **all** suggested defaults as-is, run `gtd step` with **no\n edits** \u2014 this converges the plan and moves on to decomposition.\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
389586
+ var grilling_answers_default = '<%~ include("@header") %>\n\n`.gtd/TODO.md` holds the plan under development, with open questions written near\nthe top \u2014 each one carrying a suggested default.\n\nThis is a human gate; there is nothing for the agent to do.\n\nTell the user:\n\n- To answer a question, edit `.gtd/TODO.md` in place (replace the question\'s text\n with your answer, or annotate it), then run `gtd step`.\n- To accept **all** suggested defaults as-is, run `gtd step` with **no\n edits** \u2014 this converges the plan and moves on to technical architecting.\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
389587
+
389588
+ // src/prompts/architecting-agent.md
389589
+ var architecting_agent_default = '<%~ include("@header") %>\n\n`.gtd/ARCHITECTURE.md` holds the technical plan under development \u2014 seeded\nfrom the converged product plan, or, if this is the very first turn on\nalready-technical input, written directly by the human. Develop it into a concrete,\nimplementation-ready technical plan **in this one turn** \u2014 use subagents /\ninternal iteration to go as deep as needed; there is no further agent-only\nround after this one.\n\n**Scope: technical/architectural decisions only.** Decide file/module\nstructure, data models, module boundaries, library or tech-stack choices, and\nerror-handling/concurrency strategy \u2014 the *how*, building on the *what*\nalready settled in this file\'s product content. Do not re-open or re-litigate\nproduct/user-facing decisions from the prior phase; treat them as settled\ncontext.\n\n### Develop the architecture\n\nSpawn a **planning-model subagent** using model `<%= it.model %>` to develop the\narchitecture. The subagent works entirely by editing `.gtd/ARCHITECTURE.md`:\n\n1. **Explore the codebase before asking anything** \u2014 read the relevant files,\n tests, and docs so every question below is one the codebase genuinely\n cannot answer.\n2. **Replace the seeded/captured content with a real architecture** \u2014 the\n files to change, module/data-model structure, and why, grounded in the\n codebase.\n3. **Iterate internally** (spawn further subagents, reconsider, cross-check)\n until the architecture is as complete as it can get without human input.\n Do not leave this to a future round \u2014 there isn\'t one before the human is\n asked.\n4. For every remaining open question, write it near the top of the file with\n a **suggested default** \u2014 your best-guess answer, stated plainly, that the\n human can accept as-is. A question with no suggested default is incomplete.\n5. Leave `.gtd/ARCHITECTURE.md` **uncommitted** \u2014 the human\'s turn (`gtd step`)\n reads it next.\n<% if (it.context.turnDiff && it.context.turnDiff.trim()) { %>\n<%~ include("@diff", { heading: "Latest human input (answers / sketch / review feedback)", diff: it.context.turnDiff, fenceFor: it.fenceFor }) %>\n\nRead the diff above as **feedback, not finished work**:\n\n- Code changes are suggestions \u2014 fold them into the architecture and\n re-implement them properly, including test coverage, rather than restoring\n them verbatim.\n- Code comments are positional feedback about the code at that location.\n- Plan-text changes are global feedback on the architecture (or the reviewed\n work) as a whole.\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
389590
+
389591
+ // src/prompts/architecting-answers.md
389592
+ var architecting_answers_default = '<%~ include("@header") %>\n\n`.gtd/ARCHITECTURE.md` holds the technical plan under development, with open\nquestions written near the top \u2014 each one carrying a suggested default.\n\nThis is a human gate; there is nothing for the agent to do.\n\nTell the user:\n\n- To answer a question, edit `.gtd/ARCHITECTURE.md` in place (replace the\n question\'s text with your answer, or annotate it), then run `gtd step`.\n- To accept **all** suggested defaults as-is, run `gtd step` with **no\n edits** \u2014 this converges the architecture and moves on to decomposition.\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
389518
389593
 
389519
389594
  // src/prompts/decompose.md
389520
- var decompose_default = '<%~ include("@header") %>\n\n`.gtd/TODO.md` contains an implementation plan. Decompose it into an ordered set of\nexecutable work packages stored in the `.gtd/` directory. If `.gtd/` already\nholds packages from an earlier turn, immediately abort and raise an error.\n\nSpawn a **planning-model subagent** using model `<%= it.model %>` to perform the\ndecomposition. It creates numbered package directories, each holding numbered\ntask files:\n\n```\n.gtd/\n 01-<package-name>/\n 01-<task-name>.md\n 02-<task-name>.md\n 02-<package-name>/\n ...\n```\n\n**Rules for the subagent:**\n\n1. **Packages are sequential, in dependency order** \u2014 ordinal-prefixed\n directories (`01-`, `02-`, \u2026) execute in that order and the set is frozen\n once written. Order them so each package depends only on lower-numbered ones.\n\n2. **Each package is green on its own** \u2014 the test suite runs after every\n package. Never split a feature so the tree stays red until a later package\n lands.\n\n3. **Tasks within a package are parallel and file-disjoint** \u2014 all tasks in a\n package run simultaneously, each via one subagent, against the same working\n tree with no isolation. Two tasks that would touch the same file must be\n **merged** into one. If task B depends on task A, put them in separate\n packages.\n\n4. **Vertical slices, not horizontal** \u2014 each package is a thin, end-to-end\n slice that is demoable or verifiable on its own. Prefer many thin packages\n over few thick ones; never a "set up infrastructure" package.\n\n5. **Task files are self-contained** \u2014 each task `.md` includes a clear\n description of what to build, acceptance criteria as `- [ ] Criterion`\n checkboxes, the relevant file paths to examine, and any constraints or edge\n cases. It is the only context building agents will receive to work on the\n task.\n\n6. **Specs must never reference `.gtd/` files** \u2014 `.gtd/TODO.md` is consumed\n and **deleted** the moment decomposition completes, and every other file\n under `.gtd/` (including these task specs) is machine-managed workflow\n state. No task description, acceptance criterion, or constraint may require\n creating, preserving, updating, or checking any `.gtd/` file \u2014 such a\n criterion can never be satisfied and would derail the workflow.\n\n**DO NOT** commit any changes. This process runs within a larger orchestration\nthat depends on uncommitted changes.\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
389595
+ var decompose_default = '<%~ include("@header") %>\n\n`.gtd/ARCHITECTURE.md` contains a converged architecture/technical plan.\nDecompose it into an ordered set of executable work packages stored in the\n`.gtd/` directory. If `.gtd/` already holds packages from an earlier turn,\nimmediately abort and raise an error.\n\nSpawn a **planning-model subagent** using model `<%= it.model %>` to perform the\ndecomposition. It creates numbered package directories, each holding numbered\ntask files:\n\n```\n.gtd/\n 01-<package-name>/\n 01-<task-name>.md\n 02-<task-name>.md\n 02-<package-name>/\n ...\n```\n\n**Rules for the subagent:**\n\n1. **Packages are sequential, in dependency order** \u2014 ordinal-prefixed\n directories (`01-`, `02-`, \u2026) execute in that order and the set is frozen\n once written. Order them so each package depends only on lower-numbered ones.\n\n2. **Each package is green on its own** \u2014 the test suite runs after every\n package. Never split a feature so the tree stays red until a later package\n lands.\n\n3. **Tasks within a package are parallel and file-disjoint** \u2014 all tasks in a\n package run simultaneously, each via one subagent, against the same working\n tree with no isolation. Two tasks that would touch the same file must be\n **merged** into one. If task B depends on task A, put them in separate\n packages.\n\n4. **Vertical slices, not horizontal** \u2014 each package is a thin, end-to-end\n slice that is demoable or verifiable on its own. Prefer many thin packages\n over few thick ones; never a "set up infrastructure" package.\n\n5. **Task files are self-contained** \u2014 each task `.md` includes a clear\n description of what to build, acceptance criteria as `- [ ] Criterion`\n checkboxes, the relevant file paths to examine, and any constraints or edge\n cases. It is the only context building agents will receive to work on the\n task.\n\n6. **Specs must never reference `.gtd/` files** \u2014 `.gtd/ARCHITECTURE.md` is\n consumed and **deleted** the moment decomposition completes, and every other file\n under `.gtd/` (including these task specs) is machine-managed workflow\n state. No task description, acceptance criterion, or constraint may require\n creating, preserving, updating, or checking any `.gtd/` file \u2014 such a\n criterion can never be satisfied and would derail the workflow.\n\n**DO NOT** commit any changes. This process runs within a larger orchestration\nthat depends on uncommitted changes.\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
389521
389596
 
389522
389597
  // src/prompts/building.md
389523
389598
  var building_default = '<%~ include("@header") %>\n\nBuild the package described below by orchestrating the execution \u2014 you do not\nimplement the tasks yourself. Spawn **one subagent per task**, all in\n**parallel**, each using model `<%= it.model %>`:\n\n- **Context**: the task content only (it is self-contained).\n- **Fresh context**: each worker starts cold, with no shared history.\n- **Hands off `.gtd/`** (inline rule for workers): never create, edit, or\n delete anything under `.gtd/` \u2014 it is workflow state owned by the machine.\n If the task spec mentions a `.gtd/` file, skip that part.\n- **TDD discipline** (inline rules for workers):\n - Write ONE test \u2192 implement \u2192 pass \u2192 repeat (vertical slices).\n - **Do NOT** write all tests first then implement (horizontal slicing).\n - Tests verify behavior through public interfaces, not implementation details\n \u2014 a good test survives a refactor.\n\nWait for all workers to complete. **If any worker fails** (crash, timeout, error\n\u2014 not a test failure): report which tasks failed and ask the user whether to\nretry the failed tasks, skip and continue, or abort.\n\nLeave all changes uncommitted. An outside process handles that.\n<% if (it.context.packages[0]) { %>\n<%~ include("@package", { pkg: it.context.packages[0], fenceFor: it.fenceFor }) %>\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
@@ -389538,7 +389613,7 @@ var review_default = '<%~ include("@header") %>\n\nSpawn a **planning-model suba
389538
389613
  var await_review_default = '<%~ include("@header") %>\n\n`.gtd/REVIEW.md` holds the review record for the completed work. This is a human\ngate; there is nothing for the agent to do.\n\nTell the user:\n\n- Read `.gtd/REVIEW.md` (and the underlying work it points at).\n- To **approve**: run `gtd step` with no edits, or only tick checkboxes in\n `.gtd/REVIEW.md` \u2014 either way ends the review with an approval.\n- To **request changes**: write substantive edits or annotations into\n `.gtd/REVIEW.md` (or the code) describing what\'s wrong, then run `gtd step` \u2014\n this sends the feedback back for another round.\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
389539
389614
 
389540
389615
  // src/prompts/squashing.md
389541
- var squashing_default = '<%~ include("@header") %>\n\nThe process is **approved and done**. `.gtd/SQUASH_MSG.md` has already been\ncommitted with a template \u2014 your job is to overwrite it with the real\nconventional-commits squash message and finish your turn.\n\n### Step 1 \u2014 Extract decisions from grilling rounds\n\nScan the git history of recent `gtd: ...` / `gtd(agent): ...` /\n`gtd(human): ...` commits. Look for changes to `.gtd/TODO.md` \u2014 specifically the\n`## Captured input` sections and any edits to plan text. Extract **key\ndecisions, trade-offs, and design choices** made during grilling rounds. These\nwill appear in the commit body so the history is self-documenting.\n\n### Step 2 \u2014 Draft the commit message\n\nDraft ONE conventional-commits message:\n\n```\ntype(scope): subject\n\nbody (explain the why \u2014 motivation, trade-offs, key decisions from grilling)\n```\n\n- **type**: `feat` / `fix` / `refactor` / `chore` / `docs` / `test`\n- **subject**: imperative mood, \u2264 72 characters, lowercase after the colon\n- **body**: include the important decisions / trade-offs from grilling\n sessions. Omit if there were no meaningful decisions to capture.\n\n### Step 3 \u2014 Overwrite .gtd/SQUASH_MSG.md\n\nReplace the **entire content** of `.gtd/SQUASH_MSG.md` (plain text, no markdown\nwrapper, no leftover template scaffolding) with the message from Step 2, then\nleave it uncommitted and finish your turn \u2014 `gtd step-agent` performs the\nsquash using this file\'s content.\n<% if (it.context.squashDiff && it.context.squashDiff.trim()) { %>\n<%= it.context.squashBase !== undefined ? "\\nSquash base: " + it.context.squashBase + "\\n" : "" %>\n<%~ include("@diff", { heading: "Full-process diff (`git diff " + (it.context.squashBase !== undefined ? it.context.squashBase : "<squashBase>") + " HEAD`)", diff: it.context.squashDiff, fenceFor: it.fenceFor }) %>\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
389616
+ var squashing_default = '<%~ include("@header") %>\n\nThe process is **approved and done**. `.gtd/SQUASH_MSG.md` has already been\ncommitted with a template \u2014 your job is to overwrite it with the real\nconventional-commits squash message and finish your turn.\n\n### Step 1 \u2014 Extract decisions from grilling rounds\n\nScan the git history of recent `gtd: ...` / `gtd(agent): ...` /\n`gtd(human): ...` commits. Look for changes to `.gtd/TODO.md` and\n`.gtd/ARCHITECTURE.md` \u2014 specifically the `## Captured input` sections and any\nedits to plan/architecture text. Extract **key decisions, trade-offs, and\ndesign choices** made during grilling and architecting rounds. These will\nappear in the commit body so the history is self-documenting.\n\n### Step 2 \u2014 Draft the commit message\n\nDraft ONE conventional-commits message:\n\n```\ntype(scope): subject\n\nbody (explain the why \u2014 motivation, trade-offs, key decisions from grilling)\n```\n\n- **type**: `feat` / `fix` / `refactor` / `chore` / `docs` / `test`\n- **subject**: imperative mood, \u2264 72 characters, lowercase after the colon\n- **body**: include the important decisions / trade-offs from grilling\n sessions. Omit if there were no meaningful decisions to capture.\n\n### Step 3 \u2014 Overwrite .gtd/SQUASH_MSG.md\n\nReplace the **entire content** of `.gtd/SQUASH_MSG.md` (plain text, no markdown\nwrapper, no leftover template scaffolding) with the message from Step 2, then\nleave it uncommitted and finish your turn \u2014 `gtd step-agent` performs the\nsquash using this file\'s content.\n<% if (it.context.squashDiff && it.context.squashDiff.trim()) { %>\n<%= it.context.squashBase !== undefined ? "\\nSquash base: " + it.context.squashBase + "\\n" : "" %>\n<%~ include("@diff", { heading: "Full-process diff (`git diff " + (it.context.squashBase !== undefined ? it.context.squashBase : "<squashBase>") + " HEAD`)", diff: it.context.squashDiff, fenceFor: it.fenceFor }) %>\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
389542
389617
 
389543
389618
  // src/prompts/learning.md
389544
389619
  var learning_default = '<%~ include("@header") %>\n\nThe process is **approved and done**, and headed for a squash. Before that\nhappens, `.gtd/LEARNINGS.md` has been committed with a template \u2014 your job is\nto overwrite it with the real, distilled learnings from this cycle so they\nsurvive the squash even though the granular history won\'t.\n\n### Step 1 \u2014 Walk the cycle\'s history\n\nScan the git history back to the cycle\'s start (the full-process diff is\ninlined below, and `git log` over the same range shows every `gtd: ...` /\n`gtd(agent): ...` / `gtd(human): ...` commit). Look specifically for:\n\n- **Test failures and fixes** \u2014 each `gtd: errors` round and the fix that\n followed it. What broke, and what actually made it pass?\n- **Review feedback** \u2014 human `.gtd/REVIEW.md` notes and `gtd: review\n feedback` detours. What did the human have to correct that the agent got\n wrong on its own?\n- **Health-check rounds** \u2014 any `gtd: health-check` / `gtd: health-fix`\n cycles. Environmental or systemic issues surfaced outside normal building?\n- **Grilling decisions** \u2014 `.gtd/TODO.md`\'s "## Captured input" sections and\n plan edits. Trade-offs or conventions the human specified that a future\n agent should already know.\n\n### Step 2 \u2014 Distill, don\'t transcribe\n\nFor each thing you found, ask: is this a **durable, generalizable lesson**\n(a convention, a gotcha, a pattern this project wants followed next time), or\na **one-off detail** specific to this change? Keep only the former. A future\nagent reading this should learn something that changes how it works on this\nproject, not a recap of what happened.\n\n### Step 3 \u2014 Overwrite .gtd/LEARNINGS.md\n\nReplace the **entire content** of `.gtd/LEARNINGS.md` with your distilled\nlearnings (plain markdown, no leftover template scaffolding). If nothing\ndurable surfaced this cycle, write a short explicit note saying so \u2014 don\'t\nleave the placeholder text in place. Leave the file uncommitted and finish\nyour turn; a human reviews it next before it\'s integrated into the project\'s\nown memory (CLAUDE.md/AGENTS.md/docs).\n<% if (it.context.squashDiff && it.context.squashDiff.trim()) { %>\n<%= it.context.squashBase !== undefined ? "\\nCycle base: " + it.context.squashBase + "\\n" : "" %>\n<%~ include("@diff", { heading: "Full-process diff (`git diff " + (it.context.squashBase !== undefined ? it.context.squashBase : "<squashBase>") + " HEAD`)", diff: it.context.squashDiff, fenceFor: it.fenceFor }) %>\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n';
@@ -389558,6 +389633,7 @@ var idle_default = "The repository is idle \u2014 no work cycle is in progress a
389558
389633
  // src/Prompt.ts
389559
389634
  var PROMPT_STATES = /* @__PURE__ */ new Set([
389560
389635
  "grilling",
389636
+ "architecting",
389561
389637
  "grilled",
389562
389638
  "building",
389563
389639
  "fixing",
@@ -389575,6 +389651,7 @@ var PROMPT_STATES = /* @__PURE__ */ new Set([
389575
389651
  var isPromptState = (state) => PROMPT_STATES.has(state);
389576
389652
  var MODEL_STATE = {
389577
389653
  grilling: "grilling",
389654
+ architecting: "architecting",
389578
389655
  grilled: "decompose",
389579
389656
  building: "building",
389580
389657
  fixing: "fixing",
@@ -389599,6 +389676,8 @@ eta.loadTemplate("@package", package_default);
389599
389676
  eta.loadTemplate("@agent-turn", agent_turn_default);
389600
389677
  eta.loadTemplate("@grilling-agent", grilling_agent_default);
389601
389678
  eta.loadTemplate("@grilling-answers", grilling_answers_default);
389679
+ eta.loadTemplate("@architecting-agent", architecting_agent_default);
389680
+ eta.loadTemplate("@architecting-answers", architecting_answers_default);
389602
389681
  eta.loadTemplate("@decompose", decompose_default);
389603
389682
  eta.loadTemplate("@building", building_default);
389604
389683
  eta.loadTemplate("@fixing", fixing_default);
@@ -389641,6 +389720,8 @@ var buildPrompt = (result, resolveModel = builtinResolveModel, output = "plain")
389641
389720
  let templateName;
389642
389721
  if (promptState === "grilling") {
389643
389722
  templateName = result.actor === "human" ? "@grilling-answers" : "@grilling-agent";
389723
+ } else if (promptState === "architecting") {
389724
+ templateName = result.actor === "human" ? "@architecting-answers" : "@architecting-agent";
389644
389725
  } else {
389645
389726
  templateName = STATE_TEMPLATE[promptState];
389646
389727
  }
@@ -389731,8 +389812,11 @@ var edgeActionHandlers = {
389731
389812
  captureTurn: (a5) => `capture the ${a5.actor} turn as "gtd(${a5.actor}): ${a5.gate}"`,
389732
389813
  commitRouting: (a5) => {
389733
389814
  let msg = `commit routing as "${a5.subject}"`;
389815
+ if (a5.seedArchitectureFromTodo) {
389816
+ msg += " (seeding .gtd/ARCHITECTURE.md from .gtd/TODO.md)";
389817
+ }
389734
389818
  const removed = [];
389735
- if (a5.removeTodo) removed.push(".gtd/TODO.md");
389819
+ if (a5.removeArchitecture) removed.push(".gtd/ARCHITECTURE.md");
389736
389820
  if (a5.removeReview) removed.push(".gtd/REVIEW.md");
389737
389821
  if (a5.removeFeedback) removed.push(".gtd/FEEDBACK.md");
389738
389822
  if (a5.removeHealth) removed.push(".gtd/HEALTH.md");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {
package/schema.json CHANGED
@@ -33,6 +33,9 @@
33
33
  "grilling": {
34
34
  "type": "string"
35
35
  },
36
+ "architecting": {
37
+ "type": "string"
38
+ },
36
39
  "building": {
37
40
  "type": "string"
38
41
  },