space-architect 3.0.0 → 5.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.
@@ -46,7 +46,20 @@ message, and prints back what changed (SHA + diff stat; `freeze` prints the
46
46
  frozen AC; `evidence` echoes the builder's STATUS line) — so you don't hand-edit
47
47
  the file or run a separate `git add`/`commit`, and you don't run three follow-ups
48
48
  to see what happened. You still author the *content*; the CLI owns the
49
- *persistence*.
49
+ *persistence*. Two disciplines fall out of that split:
50
+
51
+ - **Author in fresh files; canonical paths are CLI-owned.** Write your content
52
+ to a fresh scratch file (timestamp it — `tmp/` in the space) and hand it to
53
+ the command via `--from`/`--prompt`/`--stdin`. Never `Write`/`Edit` a path you
54
+ haven't read this session — the pre-existing file trips your harness's
55
+ read-before-write guard and burns a failed-write → read → rewrite round trip.
56
+ - **Write detailed commit messages.** Every committing command takes
57
+ `-m`/`--message` and `--message-from <file>`: your first line completes the
58
+ subject after a short canonical prefix (`I01 spec: <your subject>`), the rest
59
+ becomes the body. The space's git log is the loop's durable memory — record
60
+ the *why* (rejected alternatives, BRIEF §refs, judgment reasoning) there, not
61
+ just in your head. Prefer `--message-from <fresh file>` for multiline bodies
62
+ (shells mangle quotes).
50
63
 
51
64
  The builder **never** writes this file — the Acceptance Criteria must stay out
52
65
  of its editable blast radius. Each lane builder writes raw evidence to a scratch
@@ -62,7 +75,9 @@ of done) that span iterations. Every iteration's Grounds/Specification/Acceptanc
62
75
  Criteria/Verdict cites it as **BRIEF §N** (e.g. `(BRIEF §3.1)`), the way each gate
63
76
  addresses its intent back to one frozen reference: the Acceptance Criteria table
64
77
  carries a `Brief §` column, the Specification Objective cites it, the Verdict
65
- reads "diff vs BRIEF §1/§3.3 — CONTINUE". Scaffold it with `architect brief new`.
78
+ reads "diff vs BRIEF §1/§3.3 — CONTINUE". Author it in a fresh scratch file and
79
+ persist with `architect brief new --from <file>` (bare `architect brief new`
80
+ drops a placeholder template for a human to fill).
66
81
  The brief is frozen at the project level — edits to a §section are logged
67
82
  decisions in `ARCHITECT.md`, never silent per-iteration drift. Discovery projects
68
83
  that are still finding their shape defer the brief, cite per-iteration Grounds,
@@ -121,8 +136,11 @@ loop.
121
136
  gate (test/lint/typecheck/build commands) from docs or CI config.
122
137
  - Once per environment: `claude --version` and confirm the builder model
123
138
  resolves (`echo ok | claude -p --model <builder-model> --max-turns 1`;
124
- details in `dispatch.md`). First dispatch in a new environment is a canary —
125
- confirm it starts cleanly before fanning out.
139
+ details in `dispatch.md`). Past that one-time check, every foreground dispatch
140
+ self-verifies it prints a liveness line naming the streamed model and
141
+ confirming the run log is growing (a WARN line instead if the streamed model
142
+ disagrees with the pin or the log isn't growing) — so no lone lane needs
143
+ launching-and-watching before the fan-out.
126
144
  - Read `architecture/ARCHITECT.md` (the cross-iteration table of contents),
127
145
  `architecture/BRIEF.md` if present (the durable §-numbered project contract you
128
146
  cite as BRIEF §N), and the iteration file `architecture/I<NN>-<name>.md` for any
@@ -245,9 +263,17 @@ contract, self-contained:
245
263
  task.
246
264
  - **Lane plan** — split the iteration into 1–4 parallel lanes, each declaring
247
265
  its **target repo + file-touch set, checked for overlap**: name the repo
248
- (`repos/<repo>`) and every file each lane may touch. Lanes in *different*
249
- repos are inherently disjoint; same-repo lanes with any file overlap run as
250
- one. Each lane gets its own objective, output format, and boundaries. Most
266
+ (`repos/<repo>`) and every file each lane may touch. The machine-readable
267
+ declaration lives in a fenced ` ```lanes ` block in the Specification — one
268
+ entry per lane (`name`, `repo`, `touch` globs) the single frozen source of
269
+ truth `architect freeze` records into `space.yaml` and `architect provision`
270
+ materializes. The touch-set now lives *with* the frozen spec by design: it
271
+ closes the drift where a `worktree add --touch` flag could diverge from the
272
+ spec's intent. The scaffold ships a commented ` ```lanes ` stub in the
273
+ Specification (see `templates/iteration.md.erb`) — uncomment it. Lanes in
274
+ *different* repos are inherently disjoint; same-repo lanes with any file
275
+ overlap run as one. Each lane gets its own objective, output format, and
276
+ boundaries. Most
251
277
  iterations are one lane — fan out only when the work is genuinely parallel (a
252
278
  cross-repo project often is). Two first-class patterns — runnable recipes in `dispatch.md`: **parallel +
253
279
  fast-follow** (disjoint lanes integrate first; a fast-follow lane off
@@ -293,20 +319,35 @@ once a frozen section changed afterward.
293
319
 
294
320
  ### 5. Dispatch (one fresh `claude -p` per lane, worktree-isolated)
295
321
 
296
- Per the mechanics in `dispatch.md`:
297
-
298
- - **1 lane** → dispatch in the target repo's checkout (`repos/<repo>`).
299
- - **2–4 lanes** → `architect worktree add <repo> <iteration> <lane>
300
- [--base <repo-base>]` per lane (creates `build/<id>-<lane>/wt` off the target
301
- repo's base commit a repo commit, distinct from the freeze, which is a
302
- space commit — and records it in `space.yaml`).
322
+ Per the mechanics in `dispatch.md`. The lane lifecycle is **declare → freeze →
323
+ provision → write prompts → dispatch** — every lane gets a worktree; there is no
324
+ dispatch-in-the-checkout path:
325
+
326
+ - **Declare** at spec time, each lane is one entry in the Specification's
327
+ fenced ` ```lanes ` block (§4): `name`, `repo`, `touch` globs.
328
+ - **Freeze**`architect freeze` parses that block and records each lane
329
+ (name, repo, touch_set) into `space.yaml`.
330
+ - **Provision** — `architect provision <iteration>` materializes every declared
331
+ lane's worktree + `lane/<id>-<lane>` branch in one shot, each off the resolved
332
+ base (`--base` override, else `project/<slug>` when it exists, else the repo's
333
+ default branch — a repo commit, distinct from the freeze, which is a space
334
+ commit) and recorded in `space.yaml`; it is idempotent, and `--lane <name>`
335
+ provisions a single lane. The manual `architect worktree add` it wraps stays
336
+ registered as an internal primitive for edge cases — not a step you run.
337
+ - `dispatch`, `integrate`, and `gate` auto-materialize a lane whose worktree is
338
+ missing from its frozen declaration, so a lane you didn't pre-provision can't
339
+ dead-end the flow.
303
340
 
304
341
  Assemble each lane's lane-prompt (the template in `dispatch.md` + this lane's
305
342
  section of the Specification + the frozen Acceptance Criteria) and write it to
306
- `build/<id>-<lane>/prompt.md` (fed to the builder on stdin); record it in the
343
+ a **fresh timestamped scratch file**, `tmp/prompts/<id>-<lane>-<hhmmss>.md`
344
+ never to `build/<id>-<lane>/prompt.md` directly (a pre-existing canonical file
345
+ trips your harness's read-before-write guard). Record it in the
307
346
  iteration file's **Builder Prompt** section — the dispatched-prompt provenance —
308
347
  with `architect section <iteration> prompt --append --lane <lane> --from
309
- build/<id>-<lane>/prompt.md`. Then run `architect dispatch <iteration> <lane>` — it assembles the
348
+ <that scratch file>`. Then run `architect dispatch <iteration> <lane> --prompt
349
+ <that scratch file>` — it copies the prompt to the canonical
350
+ `build/<id>-<lane>/prompt.md` (fed to the builder on stdin), assembles the
310
351
  canonical `claude -p` argv, pins the model, and streams stream-json to
311
352
  `build/<id>-<lane>/run.jsonl`. Launch one dispatch per worktree — each as its
312
353
  **own background Bash tool call** (your harness's `run_in_background`), **not**
@@ -363,9 +404,9 @@ it (never hand-resolve). A cross-repo project yields one `project/<slug>` branch
363
404
  per touched repo. `main` is never touched per-iteration — `--teardown` deletes
364
405
  only the per-lane `lane/<iteration>-<lane>` branches and worktrees, never the
365
406
  project branch. Update the iteration index in `architecture/ARCHITECT.md`
366
- (recording the `project/<slug>` branch), remove the worktrees (`architect
367
- integrate … --teardown`, or `architect worktree remove <iteration> <lane>`), and
368
- commit the space.
407
+ (recording the `project/<slug>` branch), remove the provisioned worktrees
408
+ (`architect integrate … --teardown`, or `architect worktree remove <iteration>
409
+ <lane>`), and commit the space.
369
410
 
370
411
  **Run the frozen gates cold** — `architect gate <iteration>` runs the frozen
371
412
  gate commands against the integration tree and streams raw output (a runner, not
@@ -377,7 +418,9 @@ PASS/FAIL/INVALID, the KILL/CONTINUE call.
377
418
  At project end, landing is yours, not the CLI's — the PR body is judgment
378
419
  output, the same class as a Verdict. Per touched repo: write the PR body
379
420
  yourself — from the iteration verdicts, the integrated diff, and the BRIEF —
380
- to `build/land/<repo>-pr-body.md`, then present the paste-and-run block to the
421
+ to a fresh timestamped file `build/land/<repo>-pr-body-<yyyymmdd-hhmm>.md`
422
+ (never rewrite a prior session's body in place — fresh file, fresh Write),
423
+ then present the paste-and-run block to the
381
424
  human: `cd` to the repo checkout, `git push -u origin project/<slug>`, and
382
425
  `gh pr create --base main --head project/<slug> --title … --body-file …` —
383
426
  paths `~`-contracted, the multi-flag command broken with trailing ` \` at flag
@@ -27,45 +27,60 @@ pattern match), so the post-flight `git log` is what the loop actually trusts.
27
27
  If a lane committed, treat the worktree as tampered: reset and re-dispatch.
28
28
 
29
29
  **Preflight (once per environment):** run `claude --version`, and confirm the
30
- builder model resolves with a one-shot canary
30
+ builder model resolves with a one-shot
31
31
  (`echo ok | claude -p --model <builder-model> --max-turns 1`). No API key —
32
32
  the builder runs on your Claude plan — but note headless `claude -p` draws on
33
33
  the Agent SDK credit pool (separate from interactive usage since June 15 2026;
34
- see `docs/DESIGN.md` §4). On the first real dispatch in a new environment, launch
35
- ONE canary lane and confirm it starts cleanly before fanning anything out.
34
+ see `docs/DESIGN.md` §4). Past that one-time check, no dispatch needs a manual
35
+ start-check: every foreground dispatch self-verifies. Shortly after launch it
36
+ prints a liveness line to stderr naming the streamed model and confirming the
37
+ run log is growing — or a WARN line instead when the streamed model disagrees
38
+ with the pinned `<builder-model>` or the log isn't growing.
36
39
 
37
40
  ## Canonical dispatch — `architect dispatch <iteration> <lane>`
38
41
 
39
- The canonical path is `architect dispatch <iteration> <lane>`. The tool
40
- assembles the canonical `claude -p` argv, pins the builder model (the lane's
42
+ The canonical path is `architect dispatch <iteration> <lane> --prompt <file>`.
43
+ The tool copies your prompt file to `build/<id>-<lane>/prompt.md` (the CLI owns
44
+ that canonical path — you never write it directly), assembles the canonical
45
+ `claude -p` argv, pins the builder model (the lane's
41
46
  configured model or the CLI's reference default; see `docs/DESIGN.md` §4),
42
- reads the lane prompt from `build/<id>-<lane>/prompt.md` on stdin, and streams
47
+ feeds the copied lane prompt to the builder on stdin, and streams
43
48
  `--output-format stream-json --verbose` output to
44
49
  `build/<id>-<lane>/run.jsonl`. Run each lane as its own **background Bash tool
45
50
  call** (`run_in_background`) so your turn doesn't block for the full run (30–60
46
51
  minutes is typical).
47
52
 
48
- Write the lane's prompt to `build/<id>-<lane>/prompt.md` first (never pass a
49
- big prompt as a shell argument — shells mangle quotes), then:
53
+ Author the lane's prompt in a **fresh timestamped scratch file**
54
+ (`tmp/prompts/<id>-<lane>-<hhmmss>.md` never a pre-existing canonical path,
55
+ which trips your harness's read-before-write guard; never a shell argument —
56
+ shells mangle quotes), then hand it to dispatch:
50
57
 
51
58
  ```bash
52
- # single-lane iteration — run from the space root
53
- architect dispatch <iteration> <lane>
59
+ # single-lane iteration — run from the space root; dispatch copies the prompt to
60
+ # build/<id>-<lane>/prompt.md and runs in the lane's provisioned worktree
61
+ # (materialized on demand from the frozen declaration)
62
+ architect dispatch <iteration> <lane> --prompt tmp/prompts/<id>-<lane>-<hhmmss>.md
54
63
  ```
55
64
 
56
- For multi-lane iterations, create worktrees first (one per lane), then dispatch
57
- each lane from its worktree:
65
+ For multi-lane iterations, materialize every declared lane in one shot with
66
+ `architect provision`, then dispatch each lane from its worktree:
58
67
 
59
68
  ```bash
60
- # per lane:
61
- architect worktree add <repo> <iteration> <lane> [--base <repo-base>]
62
- architect dispatch <iteration> <lane>
69
+ architect provision <iteration> # all declared lanes: worktree + lane/<id>-<lane> branch
70
+ architect dispatch <iteration> lane-a --prompt <lane-a scratch file> # own background Bash call each
71
+ architect dispatch <iteration> lane-b --prompt <lane-b scratch file>
63
72
  ```
64
73
 
65
- `architect worktree add` creates `build/<id>-<lane>/wt` off the target repo's
66
- base commit (a repo commit — distinct from the freeze, which is a space
67
- commit), adds it with a `lane/<iteration>-<lane>` branch, and records it in
68
- `space.yaml`.
74
+ `architect provision` reads the frozen lane declarations from `space.yaml` and,
75
+ per lane, creates `build/<id>-<lane>/wt` off the resolved base (`--base`
76
+ override, else `project/<slug>` when it exists, else the repo's default branch —
77
+ a repo commit, distinct from the freeze, which is a space commit), adds it with a
78
+ `lane/<iteration>-<lane>` branch, and records it in `space.yaml`. It is
79
+ idempotent (`--lane <name>` provisions a single lane), and wraps the
80
+ `architect worktree add` primitive — still registered for edge cases, not a step
81
+ in the flow. `dispatch`, `integrate`, and `gate` also lazily materialize a lane
82
+ whose worktree is missing from its frozen declaration, so the flow can't
83
+ dead-end on a missing worktree.
69
84
 
70
85
  Issue each dispatch as its **own background Bash tool call** — one call per
71
86
  lane. Never use a shell `&` loop. A `for … & done` launcher is a *launcher*
@@ -82,7 +97,8 @@ survives the full run and reports completion per lane.
82
97
  and as the manual fallback:
83
98
 
84
99
  ```bash
85
- # write prompt to build/<id>-<lane>/prompt.md first, then:
100
+ # dispatch --prompt copies the scratch prompt to build/<id>-<lane>/prompt.md; the
101
+ # manual equivalent is that copy followed by:
86
102
  ( cd build/<id>-<lane>/wt && \
87
103
  claude -p --model <builder-model> \
88
104
  --permission-mode acceptEdits \
@@ -113,13 +129,16 @@ architect integrate <iteration> --lanes <passing-set> # e.g. --lanes lane-a,la
113
129
  architect gate <iteration> # integration smoke (raw output; verdict stays yours)
114
130
  architect integrate <iteration> --lanes <passing-set> --teardown # or remove worktrees + lane branches after
115
131
  # end of project: landing is the architect's, not a CLI command — write the PR
116
- # body to build/land/<repo>-pr-body.md yourself, then present the paste-and-run
117
- # block (cd, git push -u origin project/<slug>, gh pr create) — see SKILL.md §6
132
+ # body to a fresh build/land/<repo>-pr-body-<yyyymmdd-hhmm>.md yourself, then
133
+ # present the paste-and-run block (cd, git push -u origin project/<slug>,
134
+ # gh pr create) — see SKILL.md §6
118
135
  ```
119
136
 
120
137
  `architect integrate` commits each named lane on its branch and merges it
121
138
  `--no-ff` into the repo's stable `project/<slug>` branch (slug of `space.title`,
122
- persistent across all iterations), in order. It **refuses** a lane that left
139
+ persistent across all iterations), in order. Pass `-m`/`--message-from` the
140
+ lane commit lands in the repo's PR history, so say what the lane did and why,
141
+ not just that it integrated. It **refuses** a lane that left
123
142
  builder commits or wrote out-of-bounds (the mechanical post-flight checks), and
124
143
  aborts on a merge conflict. A merge conflict = the lane plan wasn't disjoint = a
125
144
  spec defect: kill the conflicting lane and re-spec; don't hand-resolve builder
@@ -140,8 +159,9 @@ git -C repos/<repo> merge --no-ff lane/<iteration>-<lane>
140
159
  <run the gate commands> # integration smoke after every merge
141
160
  architect worktree remove <iteration> <lane>
142
161
  git -C repos/<repo> branch -d lane/<iteration>-<lane>
143
- # at project end there is no CLI step: the architect writes
144
- # build/land/<repo>-pr-body.md and presents the push + gh pr create block
162
+ # at project end there is no CLI step: the architect writes a fresh
163
+ # build/land/<repo>-pr-body-<yyyymmdd-hhmm>.md and presents the push +
164
+ # gh pr create block
145
165
  ```
146
166
 
147
167
  ### Parallel + fast-follow
@@ -157,10 +177,12 @@ integrate without conflict.
157
177
  fast-follow lane's `--touch` set and exclude them from every parallel lane's
158
178
  touch-set — so the parallel set is disjoint by construction.
159
179
 
160
- 2. **Create worktrees and dispatch the parallel lanes** (off the repo base):
180
+ 2. **Provision and dispatch the parallel lanes** (off the repo base). Provision
181
+ only the parallel lanes by name — leave the fast-follow lane unmaterialized
182
+ until step 4, so it can root at the integrated tip:
161
183
  ```bash
162
- architect worktree add <repo> <iteration> lane-a
163
- architect worktree add <repo> <iteration> lane-b
184
+ architect provision <iteration> --lane lane-a
185
+ architect provision <iteration> --lane lane-b
164
186
  architect dispatch <iteration> lane-a # own background Bash call each
165
187
  architect dispatch <iteration> lane-b
166
188
  ```
@@ -172,11 +194,11 @@ integrate without conflict.
172
194
  architect gate <iteration>
173
195
  ```
174
196
 
175
- 4. **Create the fast-follow lane off the integrated tip.** `--base` accepts any
176
- git ref — passing `project/<slug>` roots the new worktree at the merged tip
177
- (the keystone move):
197
+ 4. **Provision the fast-follow lane off the integrated tip.** `--base` accepts
198
+ any git ref — passing `project/<slug>` roots the new worktree at the merged
199
+ tip (the keystone move); `--lane ff` provisions just that lane:
178
200
  ```bash
179
- architect worktree add <repo> <iteration> ff --base project/<slug>
201
+ architect provision <iteration> --lane ff --base project/<slug>
180
202
  architect dispatch <iteration> ff
181
203
  ```
182
204
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: space-architect
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Jacobs
@@ -272,6 +272,7 @@ files:
272
272
  - lib/space_core/cli/helpers.rb
273
273
  - lib/space_core/cli/init.rb
274
274
  - lib/space_core/cli/list.rb
275
+ - lib/space_core/cli/loop_status.rb
275
276
  - lib/space_core/cli/new.rb
276
277
  - lib/space_core/cli/pack.rb
277
278
  - lib/space_core/cli/path.rb