create-religion 1.5.0 → 1.6.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/CHANGELOG.md +22 -0
- package/dist/lib/state.js +24 -9
- package/dist/lib/status.js +22 -7
- package/package.json +1 -1
- package/template/.agents/skills/auto/SKILL.md +120 -2
- package/template/.agents/skills/complete/reference/land-on-top.md +14 -0
- package/template/.agents/skills/feature/SKILL.md +3 -1
- package/template/.agents/skills/implement/SKILL.md +3 -2
- package/template/.agents/skills/implement/reference/item-worktree.md +23 -6
- package/template/.claude/hooks/write-handoff.mjs +14 -2
- package/template/.claude/skills/auto/SKILL.md +120 -2
- package/template/.claude/skills/complete/reference/land-on-top.md +14 -0
- package/template/.claude/skills/feature/SKILL.md +3 -1
- package/template/.claude/skills/implement/SKILL.md +3 -2
- package/template/.claude/skills/implement/reference/item-worktree.md +23 -6
- package/template/AGENTS.md +10 -0
- package/template/CLAUDE.md +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# create-religion
|
|
2
2
|
|
|
3
|
+
## 1.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6834ede: `auto refresh` catches a run up when another run's aggregate pull request merged into the
|
|
8
|
+
default branch first: it brings the default branch into the run's integration branch with a
|
|
9
|
+
merge commit, takes the landed plan, ledger and journal and redoes the run's own share on top,
|
|
10
|
+
runs `Verify`, then pushes the branch and notes the refresh in the aggregate pull request. It asks
|
|
11
|
+
for that push and edit before starting, and never rebases or force-pushes.
|
|
12
|
+
- 6834ede: With `workflow.parallelItems` on under `git.mode: pull-request`, an `auto` run works in its own
|
|
13
|
+
git worktree beside the project, on an integration branch named from the date and its items,
|
|
14
|
+
such as `auto/2026-10-26-6-10`, so two sessions can each run a named set of items at once.
|
|
15
|
+
Before any work a named run stops on an item another run holds, and names the items it will
|
|
16
|
+
pass over because they wait on another run's. A named run now releases its items only as its
|
|
17
|
+
last write, after the aggregate pull request is open.
|
|
18
|
+
- 9e73d9a: `auto` takes an explicit list or range of plan items, such as `1-5` or `6, 7, 8`, and records
|
|
19
|
+
them in its activity record as `items`. `religion status --json` reports them under `activity`
|
|
20
|
+
and under each `inFlight` entry, and lists a worktree whose run sits between items by the items
|
|
21
|
+
it holds. `religion status` and `feature` pass over items a run in another worktree holds,
|
|
22
|
+
including the ones it has not reached yet, and the handoff names them. A held plan item
|
|
23
|
+
holds every sub-item under it.
|
|
24
|
+
|
|
3
25
|
## 1.5.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/dist/lib/state.js
CHANGED
|
@@ -336,7 +336,7 @@ const CONTROL = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
|
|
|
336
336
|
* write the record controls every field in it.
|
|
337
337
|
*/
|
|
338
338
|
export function parseActivity(source, skills) {
|
|
339
|
-
const none = { recorded: false, command: null, status: null, summary: null, updatedAt: null, resumeCommand: null };
|
|
339
|
+
const none = { recorded: false, command: null, status: null, summary: null, updatedAt: null, resumeCommand: null, items: [] };
|
|
340
340
|
if (!source)
|
|
341
341
|
return none;
|
|
342
342
|
let record;
|
|
@@ -348,7 +348,7 @@ export function parseActivity(source, skills) {
|
|
|
348
348
|
}
|
|
349
349
|
if (!record || typeof record !== "object")
|
|
350
350
|
return none;
|
|
351
|
-
const { command, status, summary, updatedAt, resumeCommand } = record;
|
|
351
|
+
const { command, status, summary, updatedAt, resumeCommand, items } = record;
|
|
352
352
|
if (typeof command !== "string" || !SKILL_NAME.test(command) || !skills.has(command))
|
|
353
353
|
return none;
|
|
354
354
|
if (typeof summary !== "string")
|
|
@@ -361,9 +361,18 @@ export function parseActivity(source, skills) {
|
|
|
361
361
|
status: status,
|
|
362
362
|
summary: summary.replace(CONTROL, " "),
|
|
363
363
|
updatedAt: typeof updatedAt === "string" ? updatedAt.replace(CONTROL, " ") : null,
|
|
364
|
-
resumeCommand: resumes(command, resumeCommand) ? resumeCommand : null
|
|
364
|
+
resumeCommand: resumes(command, resumeCommand) ? resumeCommand : null,
|
|
365
|
+
items: command === "auto" ? heldItems(items) : []
|
|
365
366
|
};
|
|
366
367
|
}
|
|
368
|
+
const HELD_MAX = 100;
|
|
369
|
+
/** A malformed list degrades to fewer items, never to an unreadable record. */
|
|
370
|
+
function heldItems(items) {
|
|
371
|
+
if (!Array.isArray(items))
|
|
372
|
+
return [];
|
|
373
|
+
const held = items.filter((i) => typeof i === "string" && /^\d+[a-z]?$/i.test(i)).map((i) => i.toLowerCase());
|
|
374
|
+
return [...new Set(held)].slice(0, HELD_MAX);
|
|
375
|
+
}
|
|
367
376
|
function resumes(command, resumeCommand) {
|
|
368
377
|
if (typeof resumeCommand !== "string" || resumeCommand.length > RESUME_MAX)
|
|
369
378
|
return false;
|
|
@@ -552,6 +561,7 @@ async function readPlain(file, within) {
|
|
|
552
561
|
}
|
|
553
562
|
}
|
|
554
563
|
/** Active specs across the repository's worktrees; empty outside a repository. */
|
|
564
|
+
const AUTO = new Set(["auto"]);
|
|
555
565
|
export async function readInFlight(root) {
|
|
556
566
|
const listed = await listWorktrees(root);
|
|
557
567
|
const self = await fs.realpath(root).catch(() => null);
|
|
@@ -564,13 +574,17 @@ export async function readInFlight(root) {
|
|
|
564
574
|
// A wave's step worktree holds a copy of its parent item's spec, not an item of its own.
|
|
565
575
|
if (/^step-\d+$/.test(path.basename(worktree)) && (await readIfPresent(path.join(path.dirname(worktree), "base"))) !== null)
|
|
566
576
|
continue;
|
|
567
|
-
const
|
|
568
|
-
// This worktree's
|
|
569
|
-
const
|
|
577
|
+
const base = path.join(worktree, offset);
|
|
578
|
+
// This worktree's files are read as `work` reads them; another's come from someone else's branch.
|
|
579
|
+
const read = (file) => (worktree === listed.home ? readIfPresent(file) : readPlain(file, worktree));
|
|
580
|
+
const source = await read(statePath(base, "context", "current-work.md"));
|
|
581
|
+
// Only an `auto` run holds items, so no other skill needs to be installed for its record to count.
|
|
582
|
+
const { items } = parseActivity(await read(statePath(base, ".state", "run.json")), AUTO);
|
|
570
583
|
const work = parseWork(source);
|
|
571
|
-
|
|
584
|
+
// A run between items has no spec, yet still holds the items it has not reached.
|
|
585
|
+
if (!work.active && items.length === 0)
|
|
572
586
|
continue;
|
|
573
|
-
const plan = field(source, "From build plan");
|
|
587
|
+
const plan = work.active ? field(source, "From build plan") : null;
|
|
574
588
|
found.push({
|
|
575
589
|
worktree,
|
|
576
590
|
branch,
|
|
@@ -579,7 +593,8 @@ export async function readInFlight(root) {
|
|
|
579
593
|
title: work.title,
|
|
580
594
|
stepsDone: work.stepsDone,
|
|
581
595
|
stepsTotal: work.stepsTotal,
|
|
582
|
-
here: worktree === listed.home
|
|
596
|
+
here: worktree === listed.home,
|
|
597
|
+
items
|
|
583
598
|
});
|
|
584
599
|
}
|
|
585
600
|
return found;
|
package/dist/lib/status.js
CHANGED
|
@@ -12,9 +12,20 @@ export function computeStatus(state) {
|
|
|
12
12
|
const done = leaves.filter((i) => i.done).length;
|
|
13
13
|
const { waits, problems } = waiting(state.plan, new Set(state.settledSpikes));
|
|
14
14
|
const waitsOn = (item) => (item.number ? waits.get(item.number) : undefined);
|
|
15
|
-
// An item another worktree is building is not this checkout's to start.
|
|
16
|
-
const elsewhere = new Map(state.inFlight.filter((i) => !i.here
|
|
17
|
-
|
|
15
|
+
// An item another worktree is building, or that a run there holds, is not this checkout's to start.
|
|
16
|
+
const elsewhere = new Map(state.inFlight.filter((i) => !i.here).flatMap((i) => [...i.items, ...(i.item ? [i.item] : [])].map((n) => [n, i.branch ?? i.worktree])));
|
|
17
|
+
// A run holding a parent holds its sub-items, which are what actually get built.
|
|
18
|
+
const holder = new Map();
|
|
19
|
+
const parents = [];
|
|
20
|
+
for (const item of state.plan) {
|
|
21
|
+
while (parents.length && parents[parents.length - 1].depth >= item.depth)
|
|
22
|
+
parents.pop();
|
|
23
|
+
const by = [...parents, item].map((p) => p.number && elsewhere.get(p.number)).find(Boolean);
|
|
24
|
+
if (by)
|
|
25
|
+
holder.set(item, by);
|
|
26
|
+
parents.push(item);
|
|
27
|
+
}
|
|
28
|
+
const busy = (item) => holder.has(item);
|
|
18
29
|
const nextItem = leaves.find((i) => !i.done && !waitsOn(i) && !busy(i)) ?? null;
|
|
19
30
|
const before = leaves.slice(0, nextItem ? leaves.indexOf(nextItem) : leaves.length).filter((i) => !i.done);
|
|
20
31
|
const passedOver = before.filter((i) => waitsOn(i));
|
|
@@ -31,8 +42,9 @@ export function computeStatus(state) {
|
|
|
31
42
|
const named = passedOver.map((i) => `${i.number} (${waitsOn(i).join(", ")})`);
|
|
32
43
|
warnings.push(`passed over plan items that are waiting: ${named.join("; ")}`);
|
|
33
44
|
}
|
|
34
|
-
|
|
35
|
-
|
|
45
|
+
// An unnumbered line under a held parent is passed over too, but has no number to name.
|
|
46
|
+
if (building.some((i) => i.number)) {
|
|
47
|
+
const named = building.filter((i) => i.number).map((i) => `${i.number} (${holder.get(i)})`);
|
|
36
48
|
warnings.push(`passed over plan items in flight in another worktree: ${named.join("; ")}`);
|
|
37
49
|
}
|
|
38
50
|
warnings.push(...problems.map((p) => `the build plan: ${p}`));
|
|
@@ -112,8 +124,11 @@ export function renderStatus(status, cmd = "/") {
|
|
|
112
124
|
(status.work.status ? ` (${status.work.status})` : "")
|
|
113
125
|
: "Active: nothing in progress");
|
|
114
126
|
for (const item of status.inFlight.filter((i) => !i.here)) {
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
const holding = item.items.length > 0 ? `holding items ${item.items.join(", ")}` : "";
|
|
128
|
+
// A run between items has no spec to describe, only the items it holds.
|
|
129
|
+
const between = holding && !item.item && !item.title && item.stepsTotal === 0;
|
|
130
|
+
const work = between ? null : `${item.item ? `item ${item.item} ` : ""}${item.title ?? "untitled"} - step ${item.stepsDone}/${item.stepsTotal}`;
|
|
131
|
+
out.push(`In flight: ${work ? work + (holding ? `, ${holding}` : "") : `auto run ${holding}`} on ${item.branch ?? item.worktree}`);
|
|
117
132
|
}
|
|
118
133
|
out.push(`Plan: ${status.plan.done}/${status.plan.total} complete (${pct}%)`);
|
|
119
134
|
const run = status.activity;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-religion",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "A file-backed, spec-driven AI development workflow. Spec before code, small reviewed diffs, and project state in markdown so a cleared context costs nothing.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nate",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: auto
|
|
3
3
|
summary: run the loop unattended, within explicit bounds
|
|
4
|
-
description: "Run the reviewed loop without stopping between steps, within bounds you set. With no argument it takes one work item and stops before completing it; `all` runs the remaining build plan; a number runs that many items; `fix` works through the findings ledger instead, every class most severe first or one class such as `fix P1`, one fix per file and class, re-reviewing every repair and folding any finding a repair surfaces into the same fix. Specs, implements, verifies, applies the configured gates, repairs confirmed high-severity findings, and completes each item. Its invocation grants committing, dependency installs, and remote reads for the run, and grants nothing else: it still stops to ask before any merge. Under git.mode pull-request it lands every item on one shared integration branch and ends by opening a single aggregate review packet it never merges, after stating that whole remote budget up front and being told yes. Use only when the user runs $auto or explicitly asks for an unattended run."
|
|
4
|
+
description: "Run the reviewed loop without stopping between steps, within bounds you set. With no argument it takes one work item and stops before completing it; `all` runs the remaining build plan; a number runs that many items; a range or list such as `1-5` or `6, 7, 8` runs exactly those items; `refresh` brings the default branch into a run's integration branch after another run landed first; `fix` works through the findings ledger instead, every class most severe first or one class such as `fix P1`, one fix per file and class, re-reviewing every repair and folding any finding a repair surfaces into the same fix. Specs, implements, verifies, applies the configured gates, repairs confirmed high-severity findings, and completes each item. Its invocation grants committing, dependency installs, and remote reads for the run, and grants nothing else: it still stops to ask before any merge. Under git.mode pull-request it lands every item on one shared integration branch and ends by opening a single aggregate review packet it never merges, after stating that whole remote budget up front and being told yes. Use only when the user runs $auto or explicitly asks for an unattended run."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# auto - the same loop, without the pauses
|
|
@@ -18,7 +18,11 @@ between steps.
|
|
|
18
18
|
- `$auto all` - the remaining build plan, completing each item, until it runs out or hits
|
|
19
19
|
a bound.
|
|
20
20
|
- `$auto 3` - the next three items, completing each.
|
|
21
|
+
- `$auto 1-5` or `$auto 6, 7, 8` - exactly those plan items, completing each. A range or
|
|
22
|
+
a comma list names items; a bare number is always a count. See Named items.
|
|
21
23
|
- `$auto resume` - continue an interrupted run from the file-backed state.
|
|
24
|
+
- `$auto refresh` - bring the default branch into a run's integration branch after another
|
|
25
|
+
run's aggregate landed first. See Refresh.
|
|
22
26
|
- `$auto fix` - the findings ledger instead of the plan; see Fix mode.
|
|
23
27
|
|
|
24
28
|
`auto.maxItems` caps successful completions. `auto.maxRepairAttempts` caps repeated
|
|
@@ -67,7 +71,7 @@ Before the first item:
|
|
|
67
71
|
than one that never began.
|
|
68
72
|
- The working tree is clean, or the only dirt belongs to work being resumed.
|
|
69
73
|
- The plan has unchecked items, or in fix mode the ledger has `open` or `fixed` findings in
|
|
70
|
-
range.
|
|
74
|
+
range. A named run needs at least one of its own items unchecked.
|
|
71
75
|
- Under `pull-request`, the repository can host one: a remote exists and the host command
|
|
72
76
|
works. A repository that cannot is a stop, not a reason to fall back to local merges. The
|
|
73
77
|
mode exists to produce review gates, and silently producing a different history instead
|
|
@@ -84,6 +88,24 @@ if one exists that does not descend from the recorded commit, stop, because the
|
|
|
84
88
|
moved and that needs a person. Never rebase, reset, or force-push it: it is append-only for
|
|
85
89
|
the run.
|
|
86
90
|
|
|
91
|
+
A named run's integration branch adds its items to the date, `<prefix><date>-<first>-<last>`,
|
|
92
|
+
or `-<item>` for one, with labels as the plan writes them: `auto/2026-10-26-6-10`. Two runs on
|
|
93
|
+
one day then never share a name, and a resume finds its own.
|
|
94
|
+
|
|
95
|
+
**With `workflow.parallelItems` on**, under `pull-request`, the run works in its own worktree so
|
|
96
|
+
another session can run beside it. The main checkout must be on the default branch at the
|
|
97
|
+
recorded commit, since the worktree is made from `HEAD`; anything else stops. Make the
|
|
98
|
+
integration branch with blocks 1 and 2 of the routine $implement keeps in
|
|
99
|
+
`reference/item-worktree.md`, with `move='no'`, push it from the worktree, and work from there
|
|
100
|
+
from then on: move the session into it where the tool can, and otherwise address every file and
|
|
101
|
+
command to its path. Item branches stay plain branches, made and removed inside that worktree,
|
|
102
|
+
so the main checkout stays on the default branch for the next session. Under
|
|
103
|
+
`branch-per-item`, which merges each item into the default branch only one checkout can hold, a
|
|
104
|
+
run stays in the main checkout and runs alone.
|
|
105
|
+
|
|
106
|
+
**Resuming from the main checkout** while a run's worktree exists names that worktree and its
|
|
107
|
+
branch and stops: the run resumes from there, never as a second copy here.
|
|
108
|
+
|
|
87
109
|
State the bounds, the gates in force, and how many items are in range. Then start.
|
|
88
110
|
|
|
89
111
|
## Step 2 - each item
|
|
@@ -115,6 +137,102 @@ nothing left to build and stops there.
|
|
|
115
137
|
|
|
116
138
|
An item counts against `auto.maxItems` once it has landed, not once it is built.
|
|
117
139
|
|
|
140
|
+
## Named items
|
|
141
|
+
|
|
142
|
+
Given a range or a list, the run builds those items and no others.
|
|
143
|
+
|
|
144
|
+
- **Plan order**, whatever order they were given in, since the waiting rule assumes it.
|
|
145
|
+
- **A ticked item** is named and passed over. **A number the plan does not have** stops the
|
|
146
|
+
run before any work starts, naming it: a typo should not quietly shrink the run.
|
|
147
|
+
- **The waiting rule still applies.** An item waiting on one outside the list, or on a
|
|
148
|
+
spike, is passed over and named in the final report, as in Step 2.
|
|
149
|
+
- **Refuse what another run holds.** Before any work, read `religion status --json`, or the
|
|
150
|
+
handoff without that command. A named item that an `inFlight` entry with `here` false holds
|
|
151
|
+
under `items` or is building, counting a parent and its sub-items as one, stops the run,
|
|
152
|
+
naming each such item and the branch that has it: two runs over one item is what holding
|
|
153
|
+
exists to prevent.
|
|
154
|
+
- **Name what waits on another run.** A named item that waits on an item held elsewhere will
|
|
155
|
+
be passed over, since that item lands on the other run's integration branch, not this one.
|
|
156
|
+
Name each when stating the bounds, with what it waits on and the branch that holds it, so a
|
|
157
|
+
run that will build fewer items than it was given says so before it starts.
|
|
158
|
+
- **Hold them.** The first write, made before the configuration is read, holds no `items`.
|
|
159
|
+
Once preflight knows where the run will run, write the listed items, lowercased, as `items`
|
|
160
|
+
in the record there, and keep them in every rewrite. A run that moves into its own worktree
|
|
161
|
+
writes them only in the worktree, then rewrites the main checkout's record as `completed`,
|
|
162
|
+
holding none and with no resume command, its summary naming the worktree: nothing resumes
|
|
163
|
+
from the main checkout, and a record holding the items there would read, from the run's own
|
|
164
|
+
worktree, as another run holding them. That is what lets `status`, the handoff and
|
|
165
|
+
$feature in another worktree pass over the items this run has not reached yet, which no
|
|
166
|
+
spec shows.
|
|
167
|
+
- **Release them only when none is left to build**, each named item completed, already
|
|
168
|
+
ticked, or passed over as waiting. That release is the run's last write, made after Step 3
|
|
169
|
+
has opened the aggregate pull request where the mode has one, and it is `completed` although
|
|
170
|
+
the run ends waiting for review: `completed` is what says nothing remains to resume. It keeps
|
|
171
|
+
just the items the run completed, so the ones it passed over are free for another worktree to
|
|
172
|
+
take; a parent number stays only when every sub-item under it was completed, since a parent
|
|
173
|
+
still held holds all of them. A run that completed nothing has no aggregate to open, so it
|
|
174
|
+
releases at once, `completed` and holding none. Any other stop, a merge question, a bound, a
|
|
175
|
+
failure, an aggregate pull request opened early, or one Step 3 should have opened and did not,
|
|
176
|
+
keeps every item and writes `blocked` with `$auto resume`.
|
|
177
|
+
- **Resume.** A record holding `items` resumes those still unticked, and when every one is
|
|
178
|
+
ticked it resumes at Step 3; a `completed` one has nothing left to resume. A record without
|
|
179
|
+
`items` is a count, `all` or fix run, and resumes as one. A named run whose record is gone
|
|
180
|
+
stops and asks for the list again rather than guessing which items it had.
|
|
181
|
+
|
|
182
|
+
Plain numbers and sub-item labels are both items (`39a, 39b`); a range covers whole numbers
|
|
183
|
+
only. A number that has sub-items stands for its unticked sub-items: build those, and record
|
|
184
|
+
their labels in `items` beside the number, so readers that do not walk the plan still see
|
|
185
|
+
them.
|
|
186
|
+
|
|
187
|
+
## Refresh
|
|
188
|
+
|
|
189
|
+
`$auto refresh` catches a run up when another run's aggregate pull request merged into the
|
|
190
|
+
default branch first. Both wrote the plan, the ledger and the journal, so this run's aggregate
|
|
191
|
+
now conflicts on them every time. It runs where the run's integration branch is checked out, in
|
|
192
|
+
the run's own worktree when it has one, and only when all of these hold; otherwise it stops and
|
|
193
|
+
says which:
|
|
194
|
+
|
|
195
|
+
- the checked-out branch starts with `git.integrationBranchPrefix` and is not the default
|
|
196
|
+
branch, and the run's aggregate pull request is open with it as head and the default branch
|
|
197
|
+
as base
|
|
198
|
+
- the run's own record, here, is `blocked` or `completed`, never `running`, and the spec is the
|
|
199
|
+
stub and the tree clean
|
|
200
|
+
- after fetching it, the branch is at its remote-tracking copy or can fast-forward to it, which
|
|
201
|
+
is how an item merged on the host since reaches it; or it is ahead only by an earlier
|
|
202
|
+
refresh's own merge, one commit whose second parent is on the default branch, left by a
|
|
203
|
+
rejected push. Ahead by that merge and behind as well means the merge was made on a stale
|
|
204
|
+
branch: stop, naming the merge commit and saying why. Dropping it is a reset, first-tier and
|
|
205
|
+
the person's to take, after which a refresh starts again from the remote copy. Any other
|
|
206
|
+
difference stops
|
|
207
|
+
|
|
208
|
+
A refresh writes **no activity record**, the one exception to writing one first: the record is
|
|
209
|
+
the run's, and the `items` it holds are what keep other worktrees off the run's work.
|
|
210
|
+
|
|
211
|
+
Its remote budget is asked for **before any work starts**, in full, as a run's is:
|
|
212
|
+
|
|
213
|
+
This refresh will, without asking again:
|
|
214
|
+
push <integration branch>, adding one merge commit that brings in <default branch>
|
|
215
|
+
update the body of its aggregate pull request <number>
|
|
216
|
+
|
|
217
|
+
It will not merge or push <default branch>, will not force-push,
|
|
218
|
+
and will not merge the aggregate pull request.
|
|
219
|
+
|
|
220
|
+
Then, when the branch is behind, fast-forward it. When an earlier refresh's merge is waiting,
|
|
221
|
+
push it as it is, add the body line, and end. Otherwise fetch the default branch and follow "A
|
|
222
|
+
run catching up" in $complete's `reference/land-on-top.md`: blocks 1 to 3 with the
|
|
223
|
+
default branch's remote-tracking name as the target, redoing the share of every item the run
|
|
224
|
+
completed; `nothing landed since` ends the refresh with nothing to do. Run the full `Verify`.
|
|
225
|
+
Only when it passes, commit the merge, push the integration branch by name, never forced, and
|
|
226
|
+
add a line to the aggregate's body naming the default-branch commit brought in and the files
|
|
227
|
+
redone. A rejected push is a stop that keeps the merge commit locally and says so. Once the
|
|
228
|
+
cause is put right, `$auto refresh` again pushes it; when the push was refused because the
|
|
229
|
+
branch moved on the host, it stops on the stale merge as above.
|
|
230
|
+
|
|
231
|
+
Stop, keeping the state as it is, on a conflict outside the shared files, which the routine
|
|
232
|
+
reports and abandons; on a failing `Verify`, abandoning the merge with the routine's block; or
|
|
233
|
+
when nothing landed since. A refresh never rebases, resets or force-pushes the integration
|
|
234
|
+
branch, and the run's activity record and the items it holds are left as they were.
|
|
235
|
+
|
|
118
236
|
## Fix mode
|
|
119
237
|
|
|
120
238
|
`$auto fix` works through the findings ledger instead of the build plan, under the same
|
|
@@ -158,6 +158,20 @@ the target it brought in, as **Base**, and that merge commit and anything after
|
|
|
158
158
|
rollback reverses. Without a merge, the record is as `$complete` describes. Commit the
|
|
159
159
|
record: in a worktree, an uncommitted file goes when the worktree is removed.
|
|
160
160
|
|
|
161
|
+
## A run catching up
|
|
162
|
+
|
|
163
|
+
`$auto refresh` uses blocks 1 to 3 for a whole run rather than one item, when another run's
|
|
164
|
+
aggregate pull request merged into the default branch first. The checkout is the run's
|
|
165
|
+
integration branch, between items, and the target is the default branch's remote-tracking name,
|
|
166
|
+
such as `origin/main`, fetched first. The merge is a commit on the integration branch, which
|
|
167
|
+
stays append-only: nothing is rebased, reset or force-pushed.
|
|
168
|
+
|
|
169
|
+
Block 2's share is every item the run completed, each row as the table gives it: every one of
|
|
170
|
+
the run's plan ticks, and a parent's once all its sub-items are ticked; the ledger entries
|
|
171
|
+
`HEAD` holds beyond the merge base, as for one item; and each journal entry whose `**Archive:**`
|
|
172
|
+
names one of the run's archives. Block 4 does not apply: each item's `## Landed` record already
|
|
173
|
+
names its own commits on the integration branch, and the refresh's merge belongs to none of them.
|
|
174
|
+
|
|
161
175
|
## When the state directory is ignored
|
|
162
176
|
|
|
163
177
|
Then none of these files are in git, and the merge carries none of them: this item's archive,
|
|
@@ -67,7 +67,9 @@ those is unticked, or says `spike first` and no archive under `religion/history/
|
|
|
67
67
|
Name each item passed over and what it waits on, and point a spike-first one at
|
|
68
68
|
$spike. An item another worktree is already building is passed over too: one that
|
|
69
69
|
`religion status --json` lists under `inFlight` with `here` false, or, without the `religion`
|
|
70
|
-
command, one the handoff lists as in flight in another worktree.
|
|
70
|
+
command, one the handoff lists as in flight in another worktree. So is an item an automated
|
|
71
|
+
run in another worktree holds: one listed under `items` on an `inFlight` entry with `here`
|
|
72
|
+
false, or named as held in the handoff. Name it with its branch.
|
|
71
73
|
State which item you are spec'ing before going further.
|
|
72
74
|
|
|
73
75
|
Asked by number for an item that waits, say what it waits on and stop: speccing it now
|
|
@@ -64,8 +64,9 @@ second one.
|
|
|
64
64
|
plan starts in its own worktree instead: follow `reference/item-worktree.md`, which creates
|
|
65
65
|
it on that branch beside the project and moves the spec there. Creating it is part of
|
|
66
66
|
positioning the work, as creating the branch is; removing it is deleting files, and asks.
|
|
67
|
-
An automated run keeps
|
|
68
|
-
|
|
67
|
+
An automated run keeps a plain branch per item, inside the run's own worktree when it has
|
|
68
|
+
one, since it removes each item's branch once it lands, and a worktree holding the branch would
|
|
69
|
+
stop that. From then on the item is the worktree's.
|
|
69
70
|
Move the session into it where the tool can; otherwise address every file and
|
|
70
71
|
command to the worktree's path, and tell the user to open later sessions there, since hooks
|
|
71
72
|
keep running where a session started. A resume inside the worktree is the ordinary resume
|
|
@@ -4,7 +4,8 @@ Used when `workflow.parallelItems` is `true`, `git.mode` is `branch-per-item` or
|
|
|
4
4
|
`pull-request`, the spec is a feature from the build plan, and no automated run is underway.
|
|
5
5
|
The item gets its own git worktree beside the project, on the branch Step 1 would otherwise
|
|
6
6
|
create, and its spec moves there. The main checkout is then idle, and `$feature` can spec
|
|
7
|
-
the next item from it.
|
|
7
|
+
the next item from it. An automated run makes its own worktree with the same blocks; see
|
|
8
|
+
the last section.
|
|
8
9
|
|
|
9
10
|
Run each block as **one** command. Each runs in a subshell, so a stop ends that block and never
|
|
10
11
|
the shell a tool keeps between commands, and nothing set in one block survives into the next:
|
|
@@ -38,7 +39,8 @@ project's parent directory: `religion` building `feature/export-reports` gets
|
|
|
38
39
|
`religion.feature-export-reports`. Landing the item later looks for it there, so never choose
|
|
39
40
|
another. A path or branch that already exists is another item's, or a start that stopped after
|
|
40
41
|
this block: when the path is a worktree on this item's branch and the main checkout still holds
|
|
41
|
-
the spec,
|
|
42
|
+
the spec, or for a run when it is a worktree on the run's branch, go on to block 2; otherwise
|
|
43
|
+
stop and say which, never reusing or replacing it.
|
|
42
44
|
|
|
43
45
|
## 2. Copy what git does not carry, then move the spec
|
|
44
46
|
|
|
@@ -48,12 +50,15 @@ block 1:
|
|
|
48
50
|
(
|
|
49
51
|
root=$(git rev-parse --show-toplevel) || exit 1
|
|
50
52
|
branch='<the branch Step 1 names>'
|
|
53
|
+
move='yes'
|
|
51
54
|
tree="${root%/*}/${root##*/}.$(printf '%s' "$branch" | tr / -)"
|
|
52
55
|
[ "$(git -C "$tree" branch --show-current 2>/dev/null)" = "$branch" ] || { echo "not this item's worktree: $tree"; exit 1; }
|
|
53
56
|
spec="religion/context/current-work.md"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
if [ "$move" = yes ]; then
|
|
58
|
+
! grep -q '_Nothing in progress' "$root/$spec" || { echo "the main checkout holds no spec to move"; exit 1; }
|
|
59
|
+
if [ -f "$tree/$spec" ] && ! grep -q '_Nothing in progress' "$tree/$spec" && ! cmp -s "$root/$spec" "$tree/$spec"; then
|
|
60
|
+
echo "the worktree already holds another spec"; exit 1
|
|
61
|
+
fi
|
|
57
62
|
fi
|
|
58
63
|
for f in .claude/settings.json .claude/settings.local.json .claude/hooks; do
|
|
59
64
|
if [ -e "$root/$f" ] && git -C "$root" check-ignore -q "$f"; then
|
|
@@ -75,7 +80,9 @@ block 1:
|
|
|
75
80
|
if git -C "$root" check-ignore -q religion && [ -f "$root/religion/context/findings.md" ]; then
|
|
76
81
|
mkdir -p "$tree/religion/.state" && cp -p "$root/religion/context/findings.md" "$tree/religion/.state/findings-at-start.md" || exit 1
|
|
77
82
|
fi
|
|
78
|
-
|
|
83
|
+
if [ "$move" = yes ]; then
|
|
84
|
+
mkdir -p "$tree/religion/context" && cp -p "$root/$spec" "$tree/$spec" && cmp -s "$root/$spec" "$tree/$spec" || exit 1
|
|
85
|
+
fi
|
|
79
86
|
echo "copied"
|
|
80
87
|
)
|
|
81
88
|
|
|
@@ -114,3 +121,13 @@ command, the handoff and the two specs say the same.
|
|
|
114
121
|
|
|
115
122
|
From here on, every file and command of this item is the worktree's. Removing the worktree is
|
|
116
123
|
deleting files, so it asks, every time.
|
|
124
|
+
|
|
125
|
+
## A run's worktree
|
|
126
|
+
|
|
127
|
+
`$auto` under `pull-request` with `workflow.parallelItems` on makes its worktree with blocks
|
|
128
|
+
1 and 2: `branch` is the run's integration branch, and block 2 sets `move='no'`, since a run
|
|
129
|
+
starts with no spec to move. Block 2 then copies only what git does not carry; the worktree's
|
|
130
|
+
spec is whatever its branch holds, the stub. Block 3 is an item's and never runs for a run.
|
|
131
|
+
|
|
132
|
+
Once the run writes its activity record there, `religion status` in the main checkout lists the
|
|
133
|
+
run under `inFlight` with the items it holds, as block 4 describes for an item.
|
|
@@ -103,6 +103,15 @@ const readPlain = (file, within) =>
|
|
|
103
103
|
closeSync(fd);
|
|
104
104
|
}
|
|
105
105
|
});
|
|
106
|
+
// The rule `religion status` applies: only an `auto` run holds items, each a plan number, capped.
|
|
107
|
+
const heldItems = (source) =>
|
|
108
|
+
quiet(() => {
|
|
109
|
+
const record = JSON.parse(source ?? "");
|
|
110
|
+
if (record?.command !== "auto" || typeof record.summary !== "string" || !["running", "blocked", "ready", "completed"].includes(record.status)) return [];
|
|
111
|
+
if (!Array.isArray(record.items)) return [];
|
|
112
|
+
const held = record.items.filter((i) => typeof i === "string" && /^\d+[a-z]?$/i.test(i)).map((i) => i.toLowerCase());
|
|
113
|
+
return [...new Set(held)].slice(0, 100);
|
|
114
|
+
}) ?? [];
|
|
106
115
|
const elsewhere = (() => {
|
|
107
116
|
const self = quiet(() => realpathSync(root));
|
|
108
117
|
let home = self;
|
|
@@ -125,12 +134,15 @@ const elsewhere = (() => {
|
|
|
125
134
|
// A wave's step worktree holds a copy of its parent item's spec, not an item of its own.
|
|
126
135
|
if (/^step-\d+$/.test(path.basename(tree)) && existsSync(path.join(path.dirname(tree), "base"))) return [];
|
|
127
136
|
const spec = readPlain(path.join(tree, offset, STATE, "context", "current-work.md"), tree) ?? "";
|
|
128
|
-
|
|
137
|
+
const items = heldItems(readPlain(path.join(tree, offset, STATE, ".state", "run.json"), tree));
|
|
138
|
+
const holding = items.length > 0 ? `holding items ${items.join(", ")}` : "";
|
|
139
|
+
const on = `on \`${plain(headBranch(git) ?? tree)}\``;
|
|
140
|
+
if (!spec.trim() || /_Nothing in progress\./.test(spec)) return holding ? [`- **An automated run** ${on}, ${holding}.`] : [];
|
|
129
141
|
const item = spec.match(/^\*\*From build plan:\*\*[ \t]*item[ \t]+([0-9a-z]+)/im)?.[1]?.toLowerCase();
|
|
130
142
|
const name = plain(spec.match(/^#\s+(.+)$/m)?.[1] ?? "Active work");
|
|
131
143
|
// Spaces and tabs only: `\s` crosses newlines, which makes blank lines quadratic.
|
|
132
144
|
const steps = `${(spec.match(/^[ \t]*- \[x\]/gim) ?? []).length} of ${(spec.match(/^[ \t]*- \[[ x]\]/gim) ?? []).length}`;
|
|
133
|
-
return [`- **${name}**${item ? ` (item ${plain(item)})` : ""} on
|
|
145
|
+
return [`- **${name}**${item ? ` (item ${plain(item)})` : ""} ${on}: ${steps} step(s) done${holding ? `, ${holding}` : ""}.`];
|
|
134
146
|
});
|
|
135
147
|
})();
|
|
136
148
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: auto
|
|
3
3
|
summary: run the loop unattended, within explicit bounds
|
|
4
|
-
description: "Run the reviewed loop without stopping between steps, within bounds you set. With no argument it takes one work item and stops before completing it; `all` runs the remaining build plan; a number runs that many items; `fix` works through the findings ledger instead, every class most severe first or one class such as `fix P1`, one fix per file and class, re-reviewing every repair and folding any finding a repair surfaces into the same fix. Specs, implements, verifies, applies the configured gates, repairs confirmed high-severity findings, and completes each item. Its invocation grants committing, dependency installs, and remote reads for the run, and grants nothing else: it still stops to ask before any merge. Under git.mode pull-request it lands every item on one shared integration branch and ends by opening a single aggregate review packet it never merges, after stating that whole remote budget up front and being told yes. Use only when the user runs /auto or explicitly asks for an unattended run."
|
|
4
|
+
description: "Run the reviewed loop without stopping between steps, within bounds you set. With no argument it takes one work item and stops before completing it; `all` runs the remaining build plan; a number runs that many items; a range or list such as `1-5` or `6, 7, 8` runs exactly those items; `refresh` brings the default branch into a run's integration branch after another run landed first; `fix` works through the findings ledger instead, every class most severe first or one class such as `fix P1`, one fix per file and class, re-reviewing every repair and folding any finding a repair surfaces into the same fix. Specs, implements, verifies, applies the configured gates, repairs confirmed high-severity findings, and completes each item. Its invocation grants committing, dependency installs, and remote reads for the run, and grants nothing else: it still stops to ask before any merge. Under git.mode pull-request it lands every item on one shared integration branch and ends by opening a single aggregate review packet it never merges, after stating that whole remote budget up front and being told yes. Use only when the user runs /auto or explicitly asks for an unattended run."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# auto - the same loop, without the pauses
|
|
@@ -18,7 +18,11 @@ between steps.
|
|
|
18
18
|
- `/auto all` - the remaining build plan, completing each item, until it runs out or hits
|
|
19
19
|
a bound.
|
|
20
20
|
- `/auto 3` - the next three items, completing each.
|
|
21
|
+
- `/auto 1-5` or `/auto 6, 7, 8` - exactly those plan items, completing each. A range or
|
|
22
|
+
a comma list names items; a bare number is always a count. See Named items.
|
|
21
23
|
- `/auto resume` - continue an interrupted run from the file-backed state.
|
|
24
|
+
- `/auto refresh` - bring the default branch into a run's integration branch after another
|
|
25
|
+
run's aggregate landed first. See Refresh.
|
|
22
26
|
- `/auto fix` - the findings ledger instead of the plan; see Fix mode.
|
|
23
27
|
|
|
24
28
|
`auto.maxItems` caps successful completions. `auto.maxRepairAttempts` caps repeated
|
|
@@ -67,7 +71,7 @@ Before the first item:
|
|
|
67
71
|
than one that never began.
|
|
68
72
|
- The working tree is clean, or the only dirt belongs to work being resumed.
|
|
69
73
|
- The plan has unchecked items, or in fix mode the ledger has `open` or `fixed` findings in
|
|
70
|
-
range.
|
|
74
|
+
range. A named run needs at least one of its own items unchecked.
|
|
71
75
|
- Under `pull-request`, the repository can host one: a remote exists and the host command
|
|
72
76
|
works. A repository that cannot is a stop, not a reason to fall back to local merges. The
|
|
73
77
|
mode exists to produce review gates, and silently producing a different history instead
|
|
@@ -84,6 +88,24 @@ if one exists that does not descend from the recorded commit, stop, because the
|
|
|
84
88
|
moved and that needs a person. Never rebase, reset, or force-push it: it is append-only for
|
|
85
89
|
the run.
|
|
86
90
|
|
|
91
|
+
A named run's integration branch adds its items to the date, `<prefix><date>-<first>-<last>`,
|
|
92
|
+
or `-<item>` for one, with labels as the plan writes them: `auto/2026-10-26-6-10`. Two runs on
|
|
93
|
+
one day then never share a name, and a resume finds its own.
|
|
94
|
+
|
|
95
|
+
**With `workflow.parallelItems` on**, under `pull-request`, the run works in its own worktree so
|
|
96
|
+
another session can run beside it. The main checkout must be on the default branch at the
|
|
97
|
+
recorded commit, since the worktree is made from `HEAD`; anything else stops. Make the
|
|
98
|
+
integration branch with blocks 1 and 2 of the routine /implement keeps in
|
|
99
|
+
`reference/item-worktree.md`, with `move='no'`, push it from the worktree, and work from there
|
|
100
|
+
from then on: move the session into it where the tool can, and otherwise address every file and
|
|
101
|
+
command to its path. Item branches stay plain branches, made and removed inside that worktree,
|
|
102
|
+
so the main checkout stays on the default branch for the next session. Under
|
|
103
|
+
`branch-per-item`, which merges each item into the default branch only one checkout can hold, a
|
|
104
|
+
run stays in the main checkout and runs alone.
|
|
105
|
+
|
|
106
|
+
**Resuming from the main checkout** while a run's worktree exists names that worktree and its
|
|
107
|
+
branch and stops: the run resumes from there, never as a second copy here.
|
|
108
|
+
|
|
87
109
|
State the bounds, the gates in force, and how many items are in range. Then start.
|
|
88
110
|
|
|
89
111
|
## Step 2 - each item
|
|
@@ -115,6 +137,102 @@ nothing left to build and stops there.
|
|
|
115
137
|
|
|
116
138
|
An item counts against `auto.maxItems` once it has landed, not once it is built.
|
|
117
139
|
|
|
140
|
+
## Named items
|
|
141
|
+
|
|
142
|
+
Given a range or a list, the run builds those items and no others.
|
|
143
|
+
|
|
144
|
+
- **Plan order**, whatever order they were given in, since the waiting rule assumes it.
|
|
145
|
+
- **A ticked item** is named and passed over. **A number the plan does not have** stops the
|
|
146
|
+
run before any work starts, naming it: a typo should not quietly shrink the run.
|
|
147
|
+
- **The waiting rule still applies.** An item waiting on one outside the list, or on a
|
|
148
|
+
spike, is passed over and named in the final report, as in Step 2.
|
|
149
|
+
- **Refuse what another run holds.** Before any work, read `religion status --json`, or the
|
|
150
|
+
handoff without that command. A named item that an `inFlight` entry with `here` false holds
|
|
151
|
+
under `items` or is building, counting a parent and its sub-items as one, stops the run,
|
|
152
|
+
naming each such item and the branch that has it: two runs over one item is what holding
|
|
153
|
+
exists to prevent.
|
|
154
|
+
- **Name what waits on another run.** A named item that waits on an item held elsewhere will
|
|
155
|
+
be passed over, since that item lands on the other run's integration branch, not this one.
|
|
156
|
+
Name each when stating the bounds, with what it waits on and the branch that holds it, so a
|
|
157
|
+
run that will build fewer items than it was given says so before it starts.
|
|
158
|
+
- **Hold them.** The first write, made before the configuration is read, holds no `items`.
|
|
159
|
+
Once preflight knows where the run will run, write the listed items, lowercased, as `items`
|
|
160
|
+
in the record there, and keep them in every rewrite. A run that moves into its own worktree
|
|
161
|
+
writes them only in the worktree, then rewrites the main checkout's record as `completed`,
|
|
162
|
+
holding none and with no resume command, its summary naming the worktree: nothing resumes
|
|
163
|
+
from the main checkout, and a record holding the items there would read, from the run's own
|
|
164
|
+
worktree, as another run holding them. That is what lets `status`, the handoff and
|
|
165
|
+
/feature in another worktree pass over the items this run has not reached yet, which no
|
|
166
|
+
spec shows.
|
|
167
|
+
- **Release them only when none is left to build**, each named item completed, already
|
|
168
|
+
ticked, or passed over as waiting. That release is the run's last write, made after Step 3
|
|
169
|
+
has opened the aggregate pull request where the mode has one, and it is `completed` although
|
|
170
|
+
the run ends waiting for review: `completed` is what says nothing remains to resume. It keeps
|
|
171
|
+
just the items the run completed, so the ones it passed over are free for another worktree to
|
|
172
|
+
take; a parent number stays only when every sub-item under it was completed, since a parent
|
|
173
|
+
still held holds all of them. A run that completed nothing has no aggregate to open, so it
|
|
174
|
+
releases at once, `completed` and holding none. Any other stop, a merge question, a bound, a
|
|
175
|
+
failure, an aggregate pull request opened early, or one Step 3 should have opened and did not,
|
|
176
|
+
keeps every item and writes `blocked` with `/auto resume`.
|
|
177
|
+
- **Resume.** A record holding `items` resumes those still unticked, and when every one is
|
|
178
|
+
ticked it resumes at Step 3; a `completed` one has nothing left to resume. A record without
|
|
179
|
+
`items` is a count, `all` or fix run, and resumes as one. A named run whose record is gone
|
|
180
|
+
stops and asks for the list again rather than guessing which items it had.
|
|
181
|
+
|
|
182
|
+
Plain numbers and sub-item labels are both items (`39a, 39b`); a range covers whole numbers
|
|
183
|
+
only. A number that has sub-items stands for its unticked sub-items: build those, and record
|
|
184
|
+
their labels in `items` beside the number, so readers that do not walk the plan still see
|
|
185
|
+
them.
|
|
186
|
+
|
|
187
|
+
## Refresh
|
|
188
|
+
|
|
189
|
+
`/auto refresh` catches a run up when another run's aggregate pull request merged into the
|
|
190
|
+
default branch first. Both wrote the plan, the ledger and the journal, so this run's aggregate
|
|
191
|
+
now conflicts on them every time. It runs where the run's integration branch is checked out, in
|
|
192
|
+
the run's own worktree when it has one, and only when all of these hold; otherwise it stops and
|
|
193
|
+
says which:
|
|
194
|
+
|
|
195
|
+
- the checked-out branch starts with `git.integrationBranchPrefix` and is not the default
|
|
196
|
+
branch, and the run's aggregate pull request is open with it as head and the default branch
|
|
197
|
+
as base
|
|
198
|
+
- the run's own record, here, is `blocked` or `completed`, never `running`, and the spec is the
|
|
199
|
+
stub and the tree clean
|
|
200
|
+
- after fetching it, the branch is at its remote-tracking copy or can fast-forward to it, which
|
|
201
|
+
is how an item merged on the host since reaches it; or it is ahead only by an earlier
|
|
202
|
+
refresh's own merge, one commit whose second parent is on the default branch, left by a
|
|
203
|
+
rejected push. Ahead by that merge and behind as well means the merge was made on a stale
|
|
204
|
+
branch: stop, naming the merge commit and saying why. Dropping it is a reset, first-tier and
|
|
205
|
+
the person's to take, after which a refresh starts again from the remote copy. Any other
|
|
206
|
+
difference stops
|
|
207
|
+
|
|
208
|
+
A refresh writes **no activity record**, the one exception to writing one first: the record is
|
|
209
|
+
the run's, and the `items` it holds are what keep other worktrees off the run's work.
|
|
210
|
+
|
|
211
|
+
Its remote budget is asked for **before any work starts**, in full, as a run's is:
|
|
212
|
+
|
|
213
|
+
This refresh will, without asking again:
|
|
214
|
+
push <integration branch>, adding one merge commit that brings in <default branch>
|
|
215
|
+
update the body of its aggregate pull request <number>
|
|
216
|
+
|
|
217
|
+
It will not merge or push <default branch>, will not force-push,
|
|
218
|
+
and will not merge the aggregate pull request.
|
|
219
|
+
|
|
220
|
+
Then, when the branch is behind, fast-forward it. When an earlier refresh's merge is waiting,
|
|
221
|
+
push it as it is, add the body line, and end. Otherwise fetch the default branch and follow "A
|
|
222
|
+
run catching up" in /complete's `reference/land-on-top.md`: blocks 1 to 3 with the
|
|
223
|
+
default branch's remote-tracking name as the target, redoing the share of every item the run
|
|
224
|
+
completed; `nothing landed since` ends the refresh with nothing to do. Run the full `Verify`.
|
|
225
|
+
Only when it passes, commit the merge, push the integration branch by name, never forced, and
|
|
226
|
+
add a line to the aggregate's body naming the default-branch commit brought in and the files
|
|
227
|
+
redone. A rejected push is a stop that keeps the merge commit locally and says so. Once the
|
|
228
|
+
cause is put right, `/auto refresh` again pushes it; when the push was refused because the
|
|
229
|
+
branch moved on the host, it stops on the stale merge as above.
|
|
230
|
+
|
|
231
|
+
Stop, keeping the state as it is, on a conflict outside the shared files, which the routine
|
|
232
|
+
reports and abandons; on a failing `Verify`, abandoning the merge with the routine's block; or
|
|
233
|
+
when nothing landed since. A refresh never rebases, resets or force-pushes the integration
|
|
234
|
+
branch, and the run's activity record and the items it holds are left as they were.
|
|
235
|
+
|
|
118
236
|
## Fix mode
|
|
119
237
|
|
|
120
238
|
`/auto fix` works through the findings ledger instead of the build plan, under the same
|
|
@@ -158,6 +158,20 @@ the target it brought in, as **Base**, and that merge commit and anything after
|
|
|
158
158
|
rollback reverses. Without a merge, the record is as `/complete` describes. Commit the
|
|
159
159
|
record: in a worktree, an uncommitted file goes when the worktree is removed.
|
|
160
160
|
|
|
161
|
+
## A run catching up
|
|
162
|
+
|
|
163
|
+
`/auto refresh` uses blocks 1 to 3 for a whole run rather than one item, when another run's
|
|
164
|
+
aggregate pull request merged into the default branch first. The checkout is the run's
|
|
165
|
+
integration branch, between items, and the target is the default branch's remote-tracking name,
|
|
166
|
+
such as `origin/main`, fetched first. The merge is a commit on the integration branch, which
|
|
167
|
+
stays append-only: nothing is rebased, reset or force-pushed.
|
|
168
|
+
|
|
169
|
+
Block 2's share is every item the run completed, each row as the table gives it: every one of
|
|
170
|
+
the run's plan ticks, and a parent's once all its sub-items are ticked; the ledger entries
|
|
171
|
+
`HEAD` holds beyond the merge base, as for one item; and each journal entry whose `**Archive:**`
|
|
172
|
+
names one of the run's archives. Block 4 does not apply: each item's `## Landed` record already
|
|
173
|
+
names its own commits on the integration branch, and the refresh's merge belongs to none of them.
|
|
174
|
+
|
|
161
175
|
## When the state directory is ignored
|
|
162
176
|
|
|
163
177
|
Then none of these files are in git, and the merge carries none of them: this item's archive,
|
|
@@ -67,7 +67,9 @@ those is unticked, or says `spike first` and no archive under `religion/history/
|
|
|
67
67
|
Name each item passed over and what it waits on, and point a spike-first one at
|
|
68
68
|
/spike. An item another worktree is already building is passed over too: one that
|
|
69
69
|
`religion status --json` lists under `inFlight` with `here` false, or, without the `religion`
|
|
70
|
-
command, one the handoff lists as in flight in another worktree.
|
|
70
|
+
command, one the handoff lists as in flight in another worktree. So is an item an automated
|
|
71
|
+
run in another worktree holds: one listed under `items` on an `inFlight` entry with `here`
|
|
72
|
+
false, or named as held in the handoff. Name it with its branch.
|
|
71
73
|
State which item you are spec'ing before going further.
|
|
72
74
|
|
|
73
75
|
Asked by number for an item that waits, say what it waits on and stop: speccing it now
|
|
@@ -64,8 +64,9 @@ second one.
|
|
|
64
64
|
plan starts in its own worktree instead: follow `reference/item-worktree.md`, which creates
|
|
65
65
|
it on that branch beside the project and moves the spec there. Creating it is part of
|
|
66
66
|
positioning the work, as creating the branch is; removing it is deleting files, and asks.
|
|
67
|
-
An automated run keeps
|
|
68
|
-
|
|
67
|
+
An automated run keeps a plain branch per item, inside the run's own worktree when it has
|
|
68
|
+
one, since it removes each item's branch once it lands, and a worktree holding the branch would
|
|
69
|
+
stop that. From then on the item is the worktree's.
|
|
69
70
|
Move the session into it where the tool can; otherwise address every file and
|
|
70
71
|
command to the worktree's path, and tell the user to open later sessions there, since hooks
|
|
71
72
|
keep running where a session started. A resume inside the worktree is the ordinary resume
|
|
@@ -4,7 +4,8 @@ Used when `workflow.parallelItems` is `true`, `git.mode` is `branch-per-item` or
|
|
|
4
4
|
`pull-request`, the spec is a feature from the build plan, and no automated run is underway.
|
|
5
5
|
The item gets its own git worktree beside the project, on the branch Step 1 would otherwise
|
|
6
6
|
create, and its spec moves there. The main checkout is then idle, and `/feature` can spec
|
|
7
|
-
the next item from it.
|
|
7
|
+
the next item from it. An automated run makes its own worktree with the same blocks; see
|
|
8
|
+
the last section.
|
|
8
9
|
|
|
9
10
|
Run each block as **one** command. Each runs in a subshell, so a stop ends that block and never
|
|
10
11
|
the shell a tool keeps between commands, and nothing set in one block survives into the next:
|
|
@@ -38,7 +39,8 @@ project's parent directory: `religion` building `feature/export-reports` gets
|
|
|
38
39
|
`religion.feature-export-reports`. Landing the item later looks for it there, so never choose
|
|
39
40
|
another. A path or branch that already exists is another item's, or a start that stopped after
|
|
40
41
|
this block: when the path is a worktree on this item's branch and the main checkout still holds
|
|
41
|
-
the spec,
|
|
42
|
+
the spec, or for a run when it is a worktree on the run's branch, go on to block 2; otherwise
|
|
43
|
+
stop and say which, never reusing or replacing it.
|
|
42
44
|
|
|
43
45
|
## 2. Copy what git does not carry, then move the spec
|
|
44
46
|
|
|
@@ -48,12 +50,15 @@ block 1:
|
|
|
48
50
|
(
|
|
49
51
|
root=$(git rev-parse --show-toplevel) || exit 1
|
|
50
52
|
branch='<the branch Step 1 names>'
|
|
53
|
+
move='yes'
|
|
51
54
|
tree="${root%/*}/${root##*/}.$(printf '%s' "$branch" | tr / -)"
|
|
52
55
|
[ "$(git -C "$tree" branch --show-current 2>/dev/null)" = "$branch" ] || { echo "not this item's worktree: $tree"; exit 1; }
|
|
53
56
|
spec="religion/context/current-work.md"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
if [ "$move" = yes ]; then
|
|
58
|
+
! grep -q '_Nothing in progress' "$root/$spec" || { echo "the main checkout holds no spec to move"; exit 1; }
|
|
59
|
+
if [ -f "$tree/$spec" ] && ! grep -q '_Nothing in progress' "$tree/$spec" && ! cmp -s "$root/$spec" "$tree/$spec"; then
|
|
60
|
+
echo "the worktree already holds another spec"; exit 1
|
|
61
|
+
fi
|
|
57
62
|
fi
|
|
58
63
|
for f in .claude/settings.json .claude/settings.local.json .claude/hooks; do
|
|
59
64
|
if [ -e "$root/$f" ] && git -C "$root" check-ignore -q "$f"; then
|
|
@@ -75,7 +80,9 @@ block 1:
|
|
|
75
80
|
if git -C "$root" check-ignore -q religion && [ -f "$root/religion/context/findings.md" ]; then
|
|
76
81
|
mkdir -p "$tree/religion/.state" && cp -p "$root/religion/context/findings.md" "$tree/religion/.state/findings-at-start.md" || exit 1
|
|
77
82
|
fi
|
|
78
|
-
|
|
83
|
+
if [ "$move" = yes ]; then
|
|
84
|
+
mkdir -p "$tree/religion/context" && cp -p "$root/$spec" "$tree/$spec" && cmp -s "$root/$spec" "$tree/$spec" || exit 1
|
|
85
|
+
fi
|
|
79
86
|
echo "copied"
|
|
80
87
|
)
|
|
81
88
|
|
|
@@ -114,3 +121,13 @@ command, the handoff and the two specs say the same.
|
|
|
114
121
|
|
|
115
122
|
From here on, every file and command of this item is the worktree's. Removing the worktree is
|
|
116
123
|
deleting files, so it asks, every time.
|
|
124
|
+
|
|
125
|
+
## A run's worktree
|
|
126
|
+
|
|
127
|
+
`/auto` under `pull-request` with `workflow.parallelItems` on makes its worktree with blocks
|
|
128
|
+
1 and 2: `branch` is the run's integration branch, and block 2 sets `move='no'`, since a run
|
|
129
|
+
starts with no spec to move. Block 2 then copies only what git does not carry; the worktree's
|
|
130
|
+
spec is whatever its branch holds, the stub. Block 3 is an item's and never runs for a run.
|
|
131
|
+
|
|
132
|
+
Once the run writes its activity record there, `religion status` in the main checkout lists the
|
|
133
|
+
run under `inFlight` with the items it holds, as block 4 describes for an item.
|
package/template/AGENTS.md
CHANGED
|
@@ -222,6 +222,16 @@ its intended handoff, such as an unattended run waiting for review. Use `blocked
|
|
|
222
222
|
exact recovery command when work can resume. `boundary` is `read-only`, `reviewed`, or
|
|
223
223
|
`local-only`. Everything after `updatedAt` is optional.
|
|
224
224
|
|
|
225
|
+
An `auto` run given explicit items also writes `items`, the plan numbers it holds as strings,
|
|
226
|
+
such as `["6", "7", "8"]`, on every write in the checkout it runs from, starting once preflight
|
|
227
|
+
knows which that is. Other worktrees pass those items over, including the ones the run has not
|
|
228
|
+
reached yet. Only when none of them is left to build, after any aggregate pull request is open,
|
|
229
|
+
does its last write, `completed`, keep just the items it completed, and a parent number only
|
|
230
|
+
when all of its sub-items were; when none is left, a run that completed nothing releases at
|
|
231
|
+
once. Every earlier stop keeps them all and is `blocked`.
|
|
232
|
+
|
|
233
|
+
`$auto refresh` writes none: it acts on a run whose record holds that run's items.
|
|
234
|
+
|
|
225
235
|
Writing this record grants nothing and bypasses nothing. A failure to write it is reported
|
|
226
236
|
and ignored: reporting must never turn into a workflow failure.
|
|
227
237
|
|
package/template/CLAUDE.md
CHANGED
|
@@ -186,6 +186,16 @@ its intended handoff, such as an unattended run waiting for review. Use `blocked
|
|
|
186
186
|
exact recovery command when work can resume. `boundary` is `read-only`, `reviewed`, or
|
|
187
187
|
`local-only`. Everything after `updatedAt` is optional.
|
|
188
188
|
|
|
189
|
+
An `auto` run given explicit items also writes `items`, the plan numbers it holds as strings,
|
|
190
|
+
such as `["6", "7", "8"]`, on every write in the checkout it runs from, starting once preflight
|
|
191
|
+
knows which that is. Other worktrees pass those items over, including the ones the run has not
|
|
192
|
+
reached yet. Only when none of them is left to build, after any aggregate pull request is open,
|
|
193
|
+
does its last write, `completed`, keep just the items it completed, and a parent number only
|
|
194
|
+
when all of its sub-items were; when none is left, a run that completed nothing releases at
|
|
195
|
+
once. Every earlier stop keeps them all and is `blocked`.
|
|
196
|
+
|
|
197
|
+
`/auto refresh` writes none: it acts on a run whose record holds that run's items.
|
|
198
|
+
|
|
189
199
|
Writing this record grants nothing and bypasses nothing. A failure to write it is reported
|
|
190
200
|
and ignored: reporting must never turn into a workflow failure.
|
|
191
201
|
|