create-religion 1.4.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/lib/dashboard.js +4 -3
  3. package/dist/lib/doctor.js +21 -7
  4. package/dist/lib/settings.js +4 -0
  5. package/dist/lib/state.js +143 -5
  6. package/dist/lib/status.js +36 -4
  7. package/package.json +1 -1
  8. package/template/.agents/skills/audit/SKILL.md +13 -7
  9. package/template/.agents/skills/auto/SKILL.md +120 -2
  10. package/template/.agents/skills/complete/SKILL.md +22 -6
  11. package/template/.agents/skills/complete/reference/land-on-top.md +188 -0
  12. package/template/.agents/skills/distill/SKILL.md +2 -1
  13. package/template/.agents/skills/feature/SKILL.md +6 -1
  14. package/template/.agents/skills/fix/SKILL.md +3 -2
  15. package/template/.agents/skills/implement/SKILL.md +18 -1
  16. package/template/.agents/skills/implement/reference/item-worktree.md +133 -0
  17. package/template/.agents/skills/rollback/SKILL.md +3 -1
  18. package/template/.claude/hooks/write-handoff.mjs +92 -3
  19. package/template/.claude/skills/audit/SKILL.md +13 -7
  20. package/template/.claude/skills/auto/SKILL.md +120 -2
  21. package/template/.claude/skills/complete/SKILL.md +22 -6
  22. package/template/.claude/skills/complete/reference/land-on-top.md +188 -0
  23. package/template/.claude/skills/distill/SKILL.md +2 -1
  24. package/template/.claude/skills/feature/SKILL.md +6 -1
  25. package/template/.claude/skills/fix/SKILL.md +3 -2
  26. package/template/.claude/skills/implement/SKILL.md +18 -1
  27. package/template/.claude/skills/implement/reference/item-worktree.md +133 -0
  28. package/template/.claude/skills/rollback/SKILL.md +3 -1
  29. package/template/AGENTS.md +12 -1
  30. package/template/CLAUDE.md +12 -1
  31. package/template/religion/config.json +2 -1
@@ -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
@@ -93,9 +93,9 @@ Read the spec's type.
93
93
 
94
94
  The number lives in the path. It never appears in the commit message.
95
95
 
96
- **Record what landed.** After the work commit exists in Step 3, append a `## Landed`
97
- section to the archive with the exact commits this item produced, oldest first, and the
98
- commit that preceded them:
96
+ **Record what landed.** After the work commit exists in Step 3, and after Step 4 has landed
97
+ it on top of anything that landed first, append a `## Landed` section to the archive with the
98
+ exact commits this item produced, oldest first, and the commit that preceded them:
99
99
 
100
100
  ## Landed
101
101
 
@@ -110,9 +110,10 @@ commits sit among others on the same branch, so this record is the only reliable
110
110
 
111
111
  **Prune the ledger.** Append every `closed`, `accepted`, and `invalid` entry to a
112
112
  `## Findings` section in the archive just written, at its final status, with the reason
113
- preserved for accepted entries. Prefix each identifier with the archive name so it is
114
- globally unique: item 12's `F-03` becomes `12/F-03`, and that prefixed form is the
115
- permanent reference.
113
+ preserved for accepted entries. Every archived identifier carries a prefix, so it is
114
+ globally unique, and that prefixed form is the permanent reference. One raised under the
115
+ item's prefix already has it and moves across unchanged: item 12's `12/F-3` stays `12/F-3`.
116
+ Prefix only a bare one, with the archive name: item 12's `F-03` becomes `12/F-03`.
116
117
 
117
118
  Then remove only those entries from the ledger. `open`, `fixed`, and `unverified` entries
118
119
  stay, with their identifiers, so nothing is silently dropped. A `fixed` P2 does not block
@@ -195,6 +196,15 @@ is refused outright when any of those commits has already been pushed.
195
196
  Under **`git.mode: trunk`** the work is already on the branch you were on. There is nothing
196
197
  to merge. Go to Step 5.
197
198
 
199
+ **Under either branching mode, land on top of what already landed.** Before the squash merge
200
+ or the push, follow `reference/land-on-top.md`. When the branch the item lands in has moved
201
+ since the item began, because another item built in its own worktree landed first, it brings
202
+ that in, takes the landed copy of each of the plan, ledger, journal, overview and handoff that
203
+ the other item changed, and redoes this item's share of them, so those files are never merged
204
+ as text. Any other conflict stops
205
+ for review. When nothing landed in between, it does nothing. Write the `## Landed` record after
206
+ it, as its last section says, so a later rollback reverses this item's work alone.
207
+
198
208
  Under **`branch-per-item`**:
199
209
 
200
210
  1. Squash-merge into the default branch, only with an explicit yes, so the item lands as
@@ -245,6 +255,12 @@ confirm the work commit is actually in it, and only then delete the branch local
245
255
  the remote. A squash-merge leaves no merge parent, so `git branch -d` will refuse; verify
246
256
  the content landed before reaching for `-D`.
247
257
 
258
+ An item built in its own worktree holds its branch there, so the cleanup runs from the main
259
+ checkout: remove the worktree first, then the branch. Removing the worktree is deleting files,
260
+ so it asks, every time, and only once the work commit is confirmed in the default branch. When
261
+ the state directory is ignored, the item's record is in the worktree alone until the routine's
262
+ last section has copied it to the main checkout; say so when asking.
263
+
248
264
  ## Step 5 - say how to try it
249
265
 
250
266
  Always end with the manual walkthrough for what just landed: what to start, where to go,
@@ -0,0 +1,188 @@
1
+ # Landing on top of what already landed
2
+
3
+ Used before an item's branch is squash-merged or pushed, when the branch it lands in has moved
4
+ since the item began: another item, built in its own worktree, landed first. Both items wrote
5
+ the same state files, and a text merge of those conflicts every time, because each holds
6
+ bookkeeping derived from its own branch. So the landed copy of each is taken, and this item's
7
+ share of it is done again on top.
8
+
9
+ The **target** is the branch the item lands in: the default branch, or an automated run's
10
+ integration branch. Under `pull-request`, fetch it first, which is a network read and asks
11
+ outside an automated run, and use the remote-tracking name, such as `origin/main`.
12
+
13
+ The merge this makes is a commit on the item's own branch, like the work commit, and is covered
14
+ by the same yes; it never writes the target.
15
+
16
+ Run each block as **one** command, from the item's checkout, after the work commit. Each runs in
17
+ a subshell and sets what it needs. A block that stops ends the landing: report what it printed
18
+ and stop. A stop after block 1 leaves the merge open: either put right what the block named and
19
+ run it again, or abandon the merge with the block in "Abandoning the merge" below.
20
+
21
+ ## 1. Bring in what landed
22
+
23
+ (
24
+ root=$(git rev-parse --show-toplevel) || exit 1
25
+ target='<the target>'
26
+ [ -z "$(git -C "$root" status --porcelain -- . ':!religion/context/handoff.md')" ] ||
27
+ { echo "uncommitted changes; make the work commit first"; exit 1; }
28
+ git -C "$root" checkout -q -- religion/context/handoff.md 2>/dev/null
29
+ git -C "$root" rev-parse -q --verify "$target^{commit}" >/dev/null || { echo "no such target: $target"; exit 1; }
30
+ if git -C "$root" merge-base --is-ancestor "$target" HEAD; then echo "nothing landed since"; exit 0; fi
31
+ git -C "$root" merge --no-commit --no-ff "$target"
32
+ git -C "$root" rev-parse -q --verify MERGE_HEAD >/dev/null || { echo "the merge did not start"; exit 1; }
33
+ base=$(git -C "$root" merge-base HEAD MERGE_HEAD)
34
+ for f in build-plan.md context/findings.md learning/journal.md context/project-overview.md context/handoff.md; do
35
+ p="religion/$f"
36
+ git -C "$root" diff --quiet "$base" MERGE_HEAD -- "$p" && continue
37
+ if git -C "$root" cat-file -e "MERGE_HEAD:$p" 2>/dev/null; then
38
+ git -C "$root" checkout MERGE_HEAD -- "$p" || exit 1
39
+ echo "taken as landed: $p"
40
+ else
41
+ git -C "$root" rm -q -f --ignore-unmatch -- "$p" || exit 1
42
+ echo "no longer tracked by the target: $p"
43
+ fi
44
+ done
45
+ left=$(git -C "$root" diff --name-only --diff-filter=U)
46
+ if [ -n "$left" ]; then
47
+ echo "conflicts outside the shared state files:"; echo "$left"
48
+ git -C "$root" merge --abort; exit 1
49
+ fi
50
+ echo "merged; redo this item's share of each file taken as landed"
51
+ )
52
+
53
+ `nothing landed since` means the target is already part of the branch: skip the rest. Only a
54
+ shared file the target changed since the item began is taken from it; one it left alone merges
55
+ as usual, carrying this item's changes with nothing to redo. A shared file the target stopped
56
+ tracking is removed, as the target has it; this item's copy is still in `HEAD`. The handoff is generated and rewritten at the end of every turn, so discarding it loses nothing.
57
+
58
+ Taking the target's copy of a shared file resolves that file, conflicted or not. A conflict in
59
+ any other file, product code included, is not this routine's to settle: the merge is abandoned,
60
+ the branch is exactly as it was, and the paths are reported for review. Never resolve them by
61
+ taking either side.
62
+
63
+ While the merge is open, `HEAD` is this item's own work and `MERGE_HEAD` is what landed.
64
+
65
+ ## 2. Redo this item's share
66
+
67
+ For each file block 1 took as landed, edit it as it now stands, taking this item's part from
68
+ `HEAD`'s copy, as `git show HEAD:religion/<file>` prints it:
69
+
70
+ | File | This item's share |
71
+ | --- | --- |
72
+ | `build-plan.md` | a feature ticks its line, and its parent once every sub-item is ticked; a rollback unticks its target and adds its note; a fix or refactor campaign has none |
73
+ | `context/findings.md` | append each entry `HEAD`'s copy holds that the merge base's did not, still `open`, `fixed` or `unverified`, unchanged; one whose identifier the landed copy already holds, two fixes' bare `F-1` say, takes its permanent prefixed form, the archive's name before it as completion would archive it, and the rename is reported; when the landed copy is the stub, replace its placeholder line |
74
+ | `learning/journal.md` | append its entry, the one whose `**Archive:**` line names this item's archive |
75
+ | `context/project-overview.md` | none: left as landed, and stale until $overview runs, as after any completion |
76
+ | `context/handoff.md` | none |
77
+
78
+ Then compare `HEAD`'s ledger with the merge base's,
79
+ `git diff $(git merge-base HEAD MERGE_HEAD) HEAD -- religion/context/findings.md`. A change this
80
+ item made to an entry it did not add is not carried, since the landed copy is the one kept; name
81
+ each such entry in the report so it can be made again by hand.
82
+
83
+ Everything else, this item's archive included, merged as git merges it.
84
+
85
+ ## 3. Check, then commit the merge
86
+
87
+ Stage the redone files for the merge commit, then check them:
88
+
89
+ (
90
+ root=$(git rev-parse --show-toplevel) || exit 1
91
+ for f in build-plan.md context/findings.md learning/journal.md context/project-overview.md context/handoff.md; do
92
+ [ -e "$root/religion/$f" ] && git -C "$root" ls-files --error-unmatch -- "religion/$f" >/dev/null 2>&1 &&
93
+ { git -C "$root" add -- "religion/$f" || exit 1; }
94
+ done
95
+ for f in build-plan.md context/findings.md learning/journal.md; do
96
+ ! grep -n -e '^<<<<<<< ' -e '^>>>>>>> ' -- "$root/religion/$f" 2>/dev/null || { echo "conflict markers in religion/$f"; exit 1; }
97
+ done
98
+ [ -z "$(git -C "$root" diff --name-only --diff-filter=U)" ] || { echo "unresolved paths remain"; exit 1; }
99
+ echo "ready for Verify"
100
+ )
101
+
102
+ Only the files edited by hand are searched: git leaves no markers in a file it merged cleanly,
103
+ and a document elsewhere may show one on purpose.
104
+
105
+ Run the project's full `Verify` on the result: it is new code, made of both items, and neither
106
+ was checked against the other. A turn that ends before the merge is committed has its handoff
107
+ rewritten by the hook; run block 3 again before committing, which stages it only while the index
108
+ still tracks it.
109
+
110
+ When `Verify` passes, commit the merge with a message that says what was brought in, such as
111
+ `merge: bring in what landed on main`.
112
+
113
+ When it fails, abandon the merge with the block below, which returns the branch to its work
114
+ commit, and stop with the failure. The item is complete on its branch but cannot land as it
115
+ stands: the way forward is $fix here, describing the failure, built on this branch.
116
+ Completing that fix lands both through this routine: block 2 then redoes the share of both, the
117
+ item's and the fix's, every row, each taken from `HEAD`, and the `## Landed` record goes into
118
+ both archives with the one range they share, each saying it is shared.
119
+
120
+ ## Abandoning the merge
121
+
122
+ (
123
+ set -o pipefail
124
+ root=$(git rev-parse --show-toplevel) || exit 1
125
+ git -C "$root" rev-parse -q --verify MERGE_HEAD >/dev/null || { echo "no merge open"; exit 0; }
126
+ g="$(git -C "$root" rev-parse --absolute-git-dir)/abandon"
127
+ list() { git -C "$root" -c core.quotePath=false "$@"; }
128
+ list ls-files -o | LC_ALL=C sort >| "$g.before" || exit 1
129
+ { list diff --name-only --no-renames HEAD && list diff --cached --name-only --no-renames HEAD; } >| "$g.tracked" || exit 1
130
+ git -C "$root" reset -q --hard HEAD; rc=$?
131
+ list ls-files -o | LC_ALL=C sort >| "$g.after" || exit 1
132
+ { cat "$g.tracked"; LC_ALL=C comm -23 "$g.before" "$g.after"; } | LC_ALL=C sort -u | sed 's/^/discarded: /'
133
+ rm -f "$g.before" "$g.after" "$g.tracked"
134
+ [ "$rc" -eq 0 ] || { echo "the reset failed: the merge is still open, and a path named above may not be set back"; exit 1; }
135
+ list ls-files -o --exclude-standard --directory | sed 's/^/left untracked: /'
136
+ echo "back at the work commit"
137
+ )
138
+
139
+ Block 1 started from a clean tree, so the work commit is the whole of this item's work, and
140
+ resetting to it ends the merge and returns the branch exactly as it was, whatever the merge left:
141
+ redone shares, a handoff the hook rewrote, output `Verify` regenerated, paths still in conflict.
142
+ Each path the reset set back or removed is named once: every tracked path whose working or staged copy
143
+ differed from the work commit, an edit made by hand while the merge was open among them, and every untracked or
144
+ ignored file in the way of a path the work commit has, such as one inside a directory the target
145
+ put where a file was, found by listing them with git before the reset and after it. Other
146
+ untracked files are left where they are and named, a whole directory as one line. Names are
147
+ printed as git prints them, quoted where they hold unusual characters. So nothing is lost without
148
+ a word. A reset that fails, such as on a directory it cannot write, still names what it removed,
149
+ then says the merge is still open: put right what git named and run the block again. Never
150
+ abandon with `git merge --abort` alone, which refuses while a file the merge touched has unstaged
151
+ changes. With no merge open, the block does nothing.
152
+
153
+ ## 4. Record what landed
154
+
155
+ When a merge was made, the item's `## Landed` record names the last such merge's second parent,
156
+ the target it brought in, as **Base**, and that merge commit and anything after it as **Commits**:
157
+ `git diff` from that base to the last commit is then this item's work alone, which is what a
158
+ rollback reverses. Without a merge, the record is as `$complete` describes. Commit the
159
+ record: in a worktree, an uncommitted file goes when the worktree is removed.
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
+
175
+ ## When the state directory is ignored
176
+
177
+ Then none of these files are in git, and the merge carries none of them: this item's archive,
178
+ plan tick, journal entry and ledger exist only in its worktree's copy of `religion/`. Before
179
+ the worktree can be removed, redo this item's share, as the table in block 2 lists it, in the
180
+ main checkout's own `religion/` files, and copy this item's archive there. When this landing
181
+ follows a failed `Verify`, that is the share and the archive of both, the item's and the fix's. With no `HEAD` copy
182
+ or merge base in git, the worktree's own files are this item's part, and the ledger saved when
183
+ the worktree was made, `religion/.state/findings-at-start.md` in it, is the base: an entry the
184
+ worktree's ledger holds that the saved one did not is this item's. Comparing the two also shows
185
+ each entry this item changed or removed, a repair it closed and pruned among them: make the
186
+ same change in the main checkout's ledger, or name the entry in the report, so a finding this
187
+ item closed does not stay open there. Removing the worktree deletes ignored files with it, so
188
+ until then they are the only record.
@@ -56,7 +56,8 @@ finding, already repaired, is an incident.
56
56
 
57
57
  For each group that earns it, propose the coding-standard rule that would have prevented every
58
58
  finding in it, in the standards' own voice, as an instruction for work not yet started, with the
59
- findings cited by identifier. A rule that only restates one finding is not a standard. Skip a
59
+ findings cited by their archived identifier, prefix included, such as `39a/F-2` or `27/F-161`.
60
+ A rule that only restates one finding is not a standard. Skip a
60
61
  group already recorded in `religion/learning/journal-archive.md` unless it has grown since.
61
62
 
62
63
  ## Step 3 - route by kind
@@ -65,7 +65,12 @@ plan top to bottom and take the first unchecked leaf that waits on nothing: an i
65
65
  those is unticked, or says `spike first` and no archive under `religion/history/spikes/` records
66
66
  `**For:** item N` with `**Settled:** yes`. Wording inside a code span does not count.
67
67
  Name each item passed over and what it waits on, and point a spike-first one at
68
- $spike. State which item you are spec'ing before going further.
68
+ $spike. An item another worktree is already building is passed over too: one that
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. 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.
73
+ State which item you are spec'ing before going further.
69
74
 
70
75
  Asked by number for an item that waits, say what it waits on and stop: speccing it now
71
76
  builds on an answer nobody has yet. A reference to an item the plan does not have, or items
@@ -14,8 +14,9 @@ numbered, it ticks nothing, and it archives on its own.
14
14
  - **A description** - `$fix "password reset email never sends"`. A problem stated in the
15
15
  conversation already counts; this skill does not scan the project hoping to find
16
16
  something wrong.
17
- - **Finding identifiers** - `$fix F-03` specs the repair of that ledger entry, and
18
- `$fix F-03 F-07` repairs several together. Group findings only when they share a file
17
+ - **Finding identifiers** - `$fix 39a/F-2` specs the repair of that ledger entry, and
18
+ `$fix 39a/F-2 F-07` repairs several together. An identifier is used exactly as the
19
+ ledger heads it, with an item prefix when it has one, bare when it does not. Group findings only when they share a file
19
20
  and a severity, as $auto's fix mode does: one fix then stays one reviewable diff.
20
21
 
21
22
  - **An issue** - `$fix #42` or its link. Read it through the host's own command-line tool, for example `gh issue view 42 --json title,body,url` on
@@ -24,6 +24,10 @@ guess past broken configuration.
24
24
  **Read the spec.** If `religion/context/current-work.md` is still the stub, or its status
25
25
  is already complete, stop and say to run $feature, $fix, or
26
26
  $rollback first.
27
+ When it is the stub and `religion status --json` lists items under `inFlight` with `here`
28
+ false, or the handoff lists items in flight in other worktrees, name each with its worktree
29
+ and branch, and say that resuming one means working from that worktree. Never start one of
30
+ them again here.
27
31
 
28
32
  **Take the context.** Conventions from `religion/context/coding-standards.md`, the data
29
33
  model from the project overview. Code that ignores them is rework, not progress.
@@ -56,6 +60,18 @@ They differ only in how the work leaves the branch at completion, which is not t
56
60
  concern. On a resume the branch exists already, so check it out rather than creating a
57
61
  second one.
58
62
 
63
+ **With `workflow.parallelItems` on**, under either of those modes, a feature from the build
64
+ plan starts in its own worktree instead: follow `reference/item-worktree.md`, which creates
65
+ it on that branch beside the project and moves the spec there. Creating it is part of
66
+ positioning the work, as creating the branch is; removing it is deleting files, and asks.
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.
70
+ Move the session into it where the tool can; otherwise address every file and
71
+ command to the worktree's path, and tell the user to open later sessions there, since hooks
72
+ keep running where a session started. A resume inside the worktree is the ordinary resume
73
+ below. Fixes, rollbacks and refactor campaigns keep the branch above.
74
+
59
75
  Either way, if the project is not a git repository, say so and ask the user to initialize
60
76
  one. The loop needs history to be reviewable.
61
77
 
@@ -194,7 +210,8 @@ block completion, so close the loop now rather than discovering it later.
194
210
 
195
211
  For each `open` P0 or P1:
196
212
 
197
- 1. **Append it to the spec's build steps** first (`- [ ] Repair F-03 - <title>`), so the
213
+ 1. **Append it to the spec's build steps** first (`- [ ] Repair 39a/F-2 - <title>`, using
214
+ the identifier exactly as the ledger heads it), so the
198
215
  repair is on the record and survives a cleared context. That changes what the spec asks
199
216
  for, so record it as an amendment naming the finding, and renew the stamp.
200
217
  2. Build it through the same loop as Step 2: smallest change, diff, explanation, evidence.
@@ -0,0 +1,133 @@
1
+ # Starting an item in its own worktree
2
+
3
+ Used when `workflow.parallelItems` is `true`, `git.mode` is `branch-per-item` or
4
+ `pull-request`, the spec is a feature from the build plan, and no automated run is underway.
5
+ The item gets its own git worktree beside the project, on the branch Step 1 would otherwise
6
+ create, and its spec moves there. The main checkout is then idle, and `$feature` can spec
7
+ the next item from it. An automated run makes its own worktree with the same blocks; see
8
+ the last section.
9
+
10
+ Run each block as **one** command. Each runs in a subshell, so a stop ends that block and never
11
+ the shell a tool keeps between commands, and nothing set in one block survives into the next:
12
+ each sets what it needs. A block that stops ends the start: report what it printed and stop.
13
+
14
+ A branch whose name holds a `'` is refused before anything runs, since the blocks quote it.
15
+
16
+ ## 1. Check, then create
17
+
18
+ (
19
+ root=$(git rev-parse --show-toplevel) || exit 1
20
+ [ "$(git -C "$root" rev-parse --absolute-git-dir)" = "$(git -C "$root" rev-parse --path-format=absolute --git-common-dir)" ] ||
21
+ { echo "not the main checkout: $root"; exit 1; }
22
+ [ -z "$(git -C "$root" status --porcelain -- . ':!religion/context/current-work.md' ':!religion/context/handoff.md')" ] ||
23
+ { echo "uncommitted changes beyond the spec"; exit 1; }
24
+ branch='<the branch Step 1 names>'
25
+ tree="${root%/*}/${root##*/}.$(printf '%s' "$branch" | tr / -)"
26
+ [ ! -e "$tree" ] && [ ! -L "$tree" ] || { echo "already exists: $tree"; exit 1; }
27
+ ! git -C "$root" show-ref --verify --quiet "refs/heads/$branch" || { echo "branch exists: $branch"; exit 1; }
28
+ git -C "$root" worktree add -q -b "$branch" "$tree" HEAD || exit 1
29
+ echo "$tree"
30
+ )
31
+
32
+ Started from inside a linked worktree, the first check stops: that worktree is an item already,
33
+ and a second one made from it would land on top of it. Anything uncommitted but the spec and
34
+ the generated handoff stops it too, settings and plan edits included, since the worktree is
35
+ made from `HEAD` and would run without them. Commit them first.
36
+
37
+ The path is the project's directory name, a dot, and the branch with each `/` as `-`, in the
38
+ project's parent directory: `religion` building `feature/export-reports` gets
39
+ `religion.feature-export-reports`. Landing the item later looks for it there, so never choose
40
+ another. A path or branch that already exists is another item's, or a start that stopped after
41
+ this block: when the path is a worktree on this item's branch and the main checkout still holds
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.
44
+
45
+ ## 2. Copy what git does not carry, then move the spec
46
+
47
+ Safe to run again until block 3 has run, and it refuses after that. Use the same branch as
48
+ block 1:
49
+
50
+ (
51
+ root=$(git rev-parse --show-toplevel) || exit 1
52
+ branch='<the branch Step 1 names>'
53
+ move='yes'
54
+ tree="${root%/*}/${root##*/}.$(printf '%s' "$branch" | tr / -)"
55
+ [ "$(git -C "$tree" branch --show-current 2>/dev/null)" = "$branch" ] || { echo "not this item's worktree: $tree"; exit 1; }
56
+ spec="religion/context/current-work.md"
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
62
+ fi
63
+ for f in .claude/settings.json .claude/settings.local.json .claude/hooks; do
64
+ if [ -e "$root/$f" ] && git -C "$root" check-ignore -q "$f"; then
65
+ mkdir -p "$tree/$(dirname "$f")" && cp -RPp "$root/$f" "$tree/$(dirname "$f")/" || exit 1
66
+ fi
67
+ done
68
+ if git -C "$root" check-ignore -q religion; then
69
+ (cd "$root/religion" && find . -path ./.state -prune -o -type f -print) > "$tree.files" || exit 1
70
+ while IFS= read -r f; do
71
+ mkdir -p "$tree/religion/$(dirname "$f")" && cp -Pp "$root/religion/$f" "$tree/religion/$f" || exit 1
72
+ done < "$tree.files"
73
+ rm -f "$tree.files"
74
+ fi
75
+ for d in node_modules; do
76
+ [ -d "$root/$d" ] || continue
77
+ [ -e "$tree/$d" ] || [ -L "$tree/$d" ] || ln -s "$root/$d" "$tree/$d" || exit 1
78
+ git -C "$tree" check-ignore -q "$d" || printf '/%s\n' "$d" >> "$(git -C "$root" rev-parse --path-format=absolute --git-path info/exclude)" || exit 1
79
+ done
80
+ if git -C "$root" check-ignore -q religion && [ -f "$root/religion/context/findings.md" ]; then
81
+ mkdir -p "$tree/religion/.state" && cp -p "$root/religion/context/findings.md" "$tree/religion/.state/findings-at-start.md" || exit 1
82
+ fi
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
86
+ echo "copied"
87
+ )
88
+
89
+ The installer's output that git ignores is copied only where git ignores it; a tracked copy is
90
+ already in the worktree. When `religion/` itself is ignored, which `doctor` reports as "state is
91
+ local only", every state file but `religion/.state/` is copied, since each worktree keeps its own
92
+ activity record; the ledger as it stood is saved in the worktree's `religion/.state/` as
93
+ `findings-at-start.md`, the base landing later tells this item's findings apart by. The list of files is written beside the worktree, not piped, so a failed copy
94
+ stops the block in every shell; a block that stopped there leaves that list, `<worktree>.files`,
95
+ and the rerun removes it.
96
+
97
+ The checks at the top are what make a rerun safe. Once block 3 has reset the main checkout's
98
+ spec, there is nothing to move, and copying would put the stub over the only copy of the spec.
99
+ A worktree that already holds a different spec belongs to work that has started, and copying the
100
+ main checkout's over it would replace that work.
101
+
102
+ Each installed dependency directory at the project root that the gate needs is linked, never
103
+ installed: an install needs its own yes. Name each one in the `for d` list; `node_modules` is the
104
+ usual one. A pattern such as `node_modules/` matches a directory but not a link to one, so a link
105
+ git would not ignore is added to the repository's own exclude file, which is never committed;
106
+ without that, the link would be committed with the item's work.
107
+
108
+ ## 3. Reset the main checkout's spec
109
+
110
+ Only after block 2 printed `copied`: reset the main checkout's
111
+ `religion/context/current-work.md` to its stub, exactly as shipped, as `$complete` does.
112
+ The spec now lives only in the worktree. That is a move, not a deletion: if the copy did not
113
+ match, nothing is reset, and the spec is still where it was.
114
+
115
+ ## 4. Confirm
116
+
117
+ From the main checkout, `religion status` lists the item under `inFlight` with its branch and
118
+ `here` false, and reports nothing active. From the worktree, it reports the item as active, and
119
+ `religion doctor` there names the main checkout in its git check. Without the `religion`
120
+ command, the handoff and the two specs say the same.
121
+
122
+ From here on, every file and command of this item is the worktree's. Removing the worktree is
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.
@@ -67,7 +67,9 @@ Stop on any failure. Do not repair a mismatch by choosing a nearby commit.
67
67
  1. Every recorded identifier matches `^[0-9a-f]{40}$`. Reject abbreviated or uppercase
68
68
  values.
69
69
  2. Every recorded commit resolves in this repository and is an ancestor of `HEAD`.
70
- 3. The recorded base is the parent of the earliest recorded commit.
70
+ 3. The recorded base is the parent of the earliest recorded commit. For an item landed on top
71
+ of another, that commit is the merge that brought the other in, and the base must be its
72
+ second parent, what it brought in: the first parent would reverse the other item's work.
71
73
  4. The working tree is clean apart from the rollback spec being written.
72
74
 
73
75
  ## Step 3 - preview the product diff