create-religion 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/package.json +1 -1
- package/template/.agents/skills/implement/SKILL.md +21 -7
- package/template/.agents/skills/implement/reference/worktrees.md +175 -0
- package/template/.claude/skills/implement/SKILL.md +21 -7
- package/template/.claude/skills/implement/reference/worktrees.md +175 -0
- package/template/AGENTS.md +7 -2
- package/template/CLAUDE.md +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# create-religion
|
|
2
2
|
|
|
3
|
+
## 1.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a15784b: With `workflow.parallelSteps` on, `implement` builds each wave step in its own detached git
|
|
8
|
+
worktree outside the project and joins the steps' patches with one all-or-nothing `git apply`,
|
|
9
|
+
stopping for review with the project unchanged when they do not apply. The commands live in the
|
|
10
|
+
implement skill's `reference/worktrees.md`. The authority rules let a run remove the worktrees it
|
|
11
|
+
created for a wave once that wave has joined; removing anything else still asks.
|
|
12
|
+
|
|
3
13
|
## 1.3.0
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-religion",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.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",
|
|
@@ -154,16 +154,30 @@ Two waves of one, then a wave of two, then a wave of one.
|
|
|
154
154
|
**Refuse a wave rather than guess at one.** Run the wave sequentially, and say why, when:
|
|
155
155
|
|
|
156
156
|
- a marker names a later step, itself, or a step that does not exist
|
|
157
|
-
- two steps in the wave would change the same file, which is a conflict the spec
|
|
158
|
-
intend and the author is better placed to resolve than a merge
|
|
157
|
+
- two steps in the wave would change the same authored file, which is a conflict the spec
|
|
158
|
+
did not intend and the author is better placed to resolve than a merge. Output the
|
|
159
|
+
project's build generates from those files does not count; it is rendered once after
|
|
160
|
+
the join
|
|
159
161
|
- the wave is larger than three steps, which usually means the spec is describing one step
|
|
160
162
|
badly rather than three independent ones
|
|
161
163
|
|
|
162
|
-
**Building a wave.** Each step
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
**Building a wave.** Each step builds in its own detached git worktree outside the project,
|
|
165
|
+
set up and torn down by `reference/worktrees.md`, so a step cannot see or change another
|
|
166
|
+
step's files. Each runs in its own subagent with the step, the spec, the standards, the path
|
|
167
|
+
of its worktree, and nothing else. They build there and **return** their evidence; they do
|
|
168
|
+
not tick, do not write the spec, and do not touch the ledger. The parent extracts each
|
|
169
|
+
step's patch and performs every write. This is the same rule audit's lenses follow, for the
|
|
170
|
+
same reason: concurrent writers to one file is the one thing waves must never introduce.
|
|
171
|
+
|
|
172
|
+
**Joining a wave.** The parent concatenates the patches in step order and applies them with
|
|
173
|
+
one `git apply`, which lands all of them or none. Then it renders generated output once,
|
|
174
|
+
when any was left out of the patches, and runs each step's gate on the joined tree. A step
|
|
175
|
+
that fails its gate there leaves its patch applied for review, as a failed step always does.
|
|
176
|
+
|
|
177
|
+
**A join that does not apply stops the wave** with the project exactly as it was. Report the
|
|
178
|
+
paths `git apply` named and where each worktree is, keep the worktrees, tick nothing, and
|
|
179
|
+
leave the conflict for review. Never retry with a three-way apply, merge, or edit to make it
|
|
180
|
+
fit.
|
|
167
181
|
|
|
168
182
|
**A failure stops the wave.** Report which steps passed and which did not, tick only what
|
|
169
183
|
passed, and stop. Do not retry the wave; the next run resumes at the first unticked step,
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Building a wave in worktrees
|
|
2
|
+
|
|
3
|
+
One detached git worktree per wave step, outside the project, so a step can neither see nor
|
|
4
|
+
change another step's files. The parent runs every command here; a step's subagent only
|
|
5
|
+
works inside the worktree it is given.
|
|
6
|
+
|
|
7
|
+
Run the phases in order. A command that fails stops the wave: report it and stop, as for any
|
|
8
|
+
failed step.
|
|
9
|
+
|
|
10
|
+
## 1. Base
|
|
11
|
+
|
|
12
|
+
Once per wave, after every earlier step has landed, take the base, make the wave's directory,
|
|
13
|
+
and save the values every later phase needs. Run these lines as **one** command: the save
|
|
14
|
+
needs the values the first lines produce, and a fresh shell would have none of them.
|
|
15
|
+
|
|
16
|
+
(
|
|
17
|
+
root=$(git rev-parse --show-toplevel) || exit 1
|
|
18
|
+
case "$root" in */step-*) [ ! -f "${root%/*}/base" ] || exit 1;; esac
|
|
19
|
+
[ ! -e "$root/religion/.state/wave" ] || { cat "$root/religion/.state/wave"; exit 1; }
|
|
20
|
+
base=$(git -C "$root" stash create) || exit 1
|
|
21
|
+
[ -n "$base" ] || base=$(git -C "$root" rev-parse HEAD) || exit 1
|
|
22
|
+
wave=$(mktemp -d) && wave=$(cd -- "$wave" >/dev/null && pwd -P) || exit 1
|
|
23
|
+
printf '%s\n' "$base" > "$wave/base" || exit 1
|
|
24
|
+
mkdir -p "$root/religion/.state" && printf '%s\n' "$wave" > "$root/religion/.state/wave" || exit 1
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
Each line that can fail stops the block, rather than `set -e`, which would stay on in a tool
|
|
28
|
+
that keeps one shell between commands. The block runs in a subshell, so its exits end only
|
|
29
|
+
the subshell and never that shell; it hands nothing back, since every later phase reads the
|
|
30
|
+
saved values. The `cd` sends its output away so a shell hook that prints on a directory change
|
|
31
|
+
cannot end up in the saved path. Without the stops, a failed `mktemp` leaves `wave`
|
|
32
|
+
empty, `cd ""` stays in the project, and the block saves the project itself as the wave; a
|
|
33
|
+
failed `git stash create` falls back to `HEAD` and drops uncommitted earlier steps.
|
|
34
|
+
|
|
35
|
+
A saved wave that already exists means an earlier one was not torn down, which is what a
|
|
36
|
+
failed join leaves. The block prints the directory it names and stops, so the record of where
|
|
37
|
+
those worktrees are is never overwritten; deal with them first.
|
|
38
|
+
|
|
39
|
+
Every command that touches the project runs against `$root`, never the current directory.
|
|
40
|
+
From a subdirectory, `git apply` skips every path outside it and still exits 0, and
|
|
41
|
+
`git ls-files` lists only what is below it, so a wave started there would join nothing and
|
|
42
|
+
then tear down the work as if it had landed.
|
|
43
|
+
|
|
44
|
+
`git stash create` makes a commit of the working tree without touching the stash list, any
|
|
45
|
+
ref, or the working tree, so uncommitted earlier steps are part of the base. An empty result
|
|
46
|
+
means the tree is clean, and `HEAD` is the base.
|
|
47
|
+
|
|
48
|
+
Never `git stash push`, and never `git add -N` in the project: the user's index and stash
|
|
49
|
+
are not the wave's to change.
|
|
50
|
+
|
|
51
|
+
## 2. Create
|
|
52
|
+
|
|
53
|
+
`pwd -P` saves the resolved path, the form `git rev-parse` reports, so the check below can
|
|
54
|
+
compare the two. A shell that does not keep variables between commands starts each later
|
|
55
|
+
phase by reading the values back, and stops if the current directory has drifted into one of
|
|
56
|
+
the wave's own worktrees:
|
|
57
|
+
|
|
58
|
+
root=$(git rev-parse --show-toplevel)
|
|
59
|
+
case "$root" in */step-*) [ ! -f "${root%/*}/base" ] || exit 1;; esac
|
|
60
|
+
wave=$(cat "$root/religion/.state/wave")
|
|
61
|
+
base=$(cat "$wave/base")
|
|
62
|
+
case "$root/" in "$wave"/*) exit 1;; esac
|
|
63
|
+
|
|
64
|
+
From inside a worktree, `root` would be that worktree, and the join would land there and
|
|
65
|
+
report success while the project stayed unchanged. The first `case` stops in a step worktree
|
|
66
|
+
of any wave, including one kept by an earlier failed join, recognised by the `base` file in
|
|
67
|
+
the wave directory beside it; the second stops in one of this wave's own. A linked worktree
|
|
68
|
+
the user works from is neither, so it is not refused.
|
|
69
|
+
|
|
70
|
+
Then, once per step, named for the step's number:
|
|
71
|
+
|
|
72
|
+
git -C "$root" worktree add --detach "$wave/step-<n>" "$base"
|
|
73
|
+
|
|
74
|
+
`--detach` creates no branch, whatever `git.mode` says. Keeping the worktrees outside the
|
|
75
|
+
project keeps a second copy of the source out of the project's own tests, linters and file
|
|
76
|
+
watchers.
|
|
77
|
+
|
|
78
|
+
The stash commit leaves untracked files out, so copy them in, all but the workflow's own
|
|
79
|
+
state, which would carry the saved wave path into the worktree:
|
|
80
|
+
|
|
81
|
+
git -C "$root" ls-files --others --exclude-standard -z |
|
|
82
|
+
while IFS= read -r -d '' f; do
|
|
83
|
+
case "$f" in religion/.state/*) continue;; esac
|
|
84
|
+
mkdir -p "$wave/step-<n>/$(dirname "$f")" && cp -RPp "$root/$f" "$wave/step-<n>/$f"
|
|
85
|
+
done
|
|
86
|
+
|
|
87
|
+
`-RP` copies an untracked link as a link. Plain `cp` refuses a link to a directory, and
|
|
88
|
+
following it would copy whatever it points at.
|
|
89
|
+
|
|
90
|
+
Then make the copies part of the step's base, so they are not handed back as new files:
|
|
91
|
+
|
|
92
|
+
git -C "$wave/step-<n>" add -A
|
|
93
|
+
git -C "$wave/step-<n>" write-tree > "$wave/step-<n>.base"
|
|
94
|
+
|
|
95
|
+
`write-tree` saves a tree object and nothing else: no commit, no ref. The saved file is the
|
|
96
|
+
base phase 5 diffs against, and it survives a shell that does not keep variables between
|
|
97
|
+
commands.
|
|
98
|
+
|
|
99
|
+
## 3. Link
|
|
100
|
+
|
|
101
|
+
A fresh worktree has none of the project's installed dependencies. Symlink each installed
|
|
102
|
+
dependency directory at the project root that the gate needs, such as `node_modules`:
|
|
103
|
+
|
|
104
|
+
[ -e "$wave/step-<n>/node_modules" ] || [ -L "$wave/step-<n>/node_modules" ] ||
|
|
105
|
+
ln -s "$root/node_modules" "$wave/step-<n>/node_modules"
|
|
106
|
+
|
|
107
|
+
Skip the link when anything already sits at that path, such as an untracked link copied in
|
|
108
|
+
phase 2. `ln -s` onto an existing link to a directory does not replace it: it creates a new
|
|
109
|
+
link inside the directory it points at, which here is the project's real dependencies.
|
|
110
|
+
|
|
111
|
+
Never install into a worktree. That is a dependency install, which the Authority section
|
|
112
|
+
does not grant a wave. On Windows the link would be a junction, which this routine has not
|
|
113
|
+
been run against.
|
|
114
|
+
|
|
115
|
+
## 4. Build
|
|
116
|
+
|
|
117
|
+
When the project's gate reads build output that is not committed, run the project's build in
|
|
118
|
+
the worktree before handing it to the subagent, so the step's checks can run there at all.
|
|
119
|
+
Run it in a subshell, `(cd "$wave/step-<n>" && <build>)`, so the current directory never
|
|
120
|
+
moves into the worktree.
|
|
121
|
+
|
|
122
|
+
## 5. Extract
|
|
123
|
+
|
|
124
|
+
When the subagent returns, take its patch:
|
|
125
|
+
|
|
126
|
+
git -C "$wave/step-<n>" add -A
|
|
127
|
+
git -C "$wave/step-<n>" diff --cached --binary "$(cat "$wave/step-<n>.base")" -- . <exclusions> > "$wave/step-<n>.patch"
|
|
128
|
+
|
|
129
|
+
`add -A` writes to the worktree's own index, never the project's, and brings new files into
|
|
130
|
+
the patch. `<exclusions>` is one `':!<path>'` for each link made in phase 3, which an ignore
|
|
131
|
+
rule with a trailing slash such as `node_modules/` does not cover, because a link is not a
|
|
132
|
+
directory. Then one more for each path the project's instructions declare as generated, such
|
|
133
|
+
as rendered trees, so two steps that both regenerate a shared file do not
|
|
134
|
+
collide over output the build reproduces. A project that declares none keeps every path, and
|
|
135
|
+
a generated file that both steps touched then fails the join and stops it.
|
|
136
|
+
|
|
137
|
+
## 6. Join
|
|
138
|
+
|
|
139
|
+
All patches, in step order, through **one** `git apply` of one file:
|
|
140
|
+
|
|
141
|
+
for p in "$wave"/step-<n>.patch "$wave"/step-<m>.patch; do [ -f "$p" ] || exit 1; done
|
|
142
|
+
cat "$wave"/step-<n>.patch "$wave"/step-<m>.patch > "$wave/join.patch"
|
|
143
|
+
git -C "$root" apply "$wave/join.patch"
|
|
144
|
+
|
|
145
|
+
Check that every step's patch exists first, and never pipe into `git apply`: without
|
|
146
|
+
`pipefail` a pipe reports `git apply`'s status alone, so a missing patch would land the
|
|
147
|
+
others as a success and teardown would delete the step that never applied.
|
|
148
|
+
|
|
149
|
+
One concatenated input applies all or nothing, including two patches to the same file.
|
|
150
|
+
Separate patch files given to one `git apply` do not: the first lands even when a later one
|
|
151
|
+
fails. Never `--3way`, never a merge, and never resolve a conflict by editing.
|
|
152
|
+
|
|
153
|
+
On failure the project is exactly as it was. Stop the wave, keep the worktrees, and report
|
|
154
|
+
the paths `git apply` named and where each worktree is.
|
|
155
|
+
|
|
156
|
+
On success, run the project's build once when any path was excluded, so generated output is
|
|
157
|
+
rendered from the joined sources, then run each step's gate on the joined tree.
|
|
158
|
+
|
|
159
|
+
## 7. Teardown
|
|
160
|
+
|
|
161
|
+
Only after a successful join, and only for worktrees this run created for this wave. Once per
|
|
162
|
+
step:
|
|
163
|
+
|
|
164
|
+
rm "$wave/step-<n>/node_modules"
|
|
165
|
+
git worktree remove --force "$wave/step-<n>"
|
|
166
|
+
rm "$wave/step-<n>.patch" "$wave/step-<n>.base"
|
|
167
|
+
|
|
168
|
+
Then once for the wave:
|
|
169
|
+
|
|
170
|
+
rm "$wave/join.patch" "$wave/base"
|
|
171
|
+
rmdir "$wave"
|
|
172
|
+
rm "$root/religion/.state/wave"
|
|
173
|
+
|
|
174
|
+
Remove each link first, so nothing follows it into the project's real dependencies. Removing
|
|
175
|
+
any other worktree, or these after a failed join, still asks.
|
|
@@ -154,16 +154,30 @@ Two waves of one, then a wave of two, then a wave of one.
|
|
|
154
154
|
**Refuse a wave rather than guess at one.** Run the wave sequentially, and say why, when:
|
|
155
155
|
|
|
156
156
|
- a marker names a later step, itself, or a step that does not exist
|
|
157
|
-
- two steps in the wave would change the same file, which is a conflict the spec
|
|
158
|
-
intend and the author is better placed to resolve than a merge
|
|
157
|
+
- two steps in the wave would change the same authored file, which is a conflict the spec
|
|
158
|
+
did not intend and the author is better placed to resolve than a merge. Output the
|
|
159
|
+
project's build generates from those files does not count; it is rendered once after
|
|
160
|
+
the join
|
|
159
161
|
- the wave is larger than three steps, which usually means the spec is describing one step
|
|
160
162
|
badly rather than three independent ones
|
|
161
163
|
|
|
162
|
-
**Building a wave.** Each step
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
**Building a wave.** Each step builds in its own detached git worktree outside the project,
|
|
165
|
+
set up and torn down by `reference/worktrees.md`, so a step cannot see or change another
|
|
166
|
+
step's files. Each runs in its own subagent with the step, the spec, the standards, the path
|
|
167
|
+
of its worktree, and nothing else. They build there and **return** their evidence; they do
|
|
168
|
+
not tick, do not write the spec, and do not touch the ledger. The parent extracts each
|
|
169
|
+
step's patch and performs every write. This is the same rule audit's lenses follow, for the
|
|
170
|
+
same reason: concurrent writers to one file is the one thing waves must never introduce.
|
|
171
|
+
|
|
172
|
+
**Joining a wave.** The parent concatenates the patches in step order and applies them with
|
|
173
|
+
one `git apply`, which lands all of them or none. Then it renders generated output once,
|
|
174
|
+
when any was left out of the patches, and runs each step's gate on the joined tree. A step
|
|
175
|
+
that fails its gate there leaves its patch applied for review, as a failed step always does.
|
|
176
|
+
|
|
177
|
+
**A join that does not apply stops the wave** with the project exactly as it was. Report the
|
|
178
|
+
paths `git apply` named and where each worktree is, keep the worktrees, tick nothing, and
|
|
179
|
+
leave the conflict for review. Never retry with a three-way apply, merge, or edit to make it
|
|
180
|
+
fit.
|
|
167
181
|
|
|
168
182
|
**A failure stops the wave.** Report which steps passed and which did not, tick only what
|
|
169
183
|
passed, and stop. Do not retry the wave; the next run resumes at the first unticked step,
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Building a wave in worktrees
|
|
2
|
+
|
|
3
|
+
One detached git worktree per wave step, outside the project, so a step can neither see nor
|
|
4
|
+
change another step's files. The parent runs every command here; a step's subagent only
|
|
5
|
+
works inside the worktree it is given.
|
|
6
|
+
|
|
7
|
+
Run the phases in order. A command that fails stops the wave: report it and stop, as for any
|
|
8
|
+
failed step.
|
|
9
|
+
|
|
10
|
+
## 1. Base
|
|
11
|
+
|
|
12
|
+
Once per wave, after every earlier step has landed, take the base, make the wave's directory,
|
|
13
|
+
and save the values every later phase needs. Run these lines as **one** command: the save
|
|
14
|
+
needs the values the first lines produce, and a fresh shell would have none of them.
|
|
15
|
+
|
|
16
|
+
(
|
|
17
|
+
root=$(git rev-parse --show-toplevel) || exit 1
|
|
18
|
+
case "$root" in */step-*) [ ! -f "${root%/*}/base" ] || exit 1;; esac
|
|
19
|
+
[ ! -e "$root/religion/.state/wave" ] || { cat "$root/religion/.state/wave"; exit 1; }
|
|
20
|
+
base=$(git -C "$root" stash create) || exit 1
|
|
21
|
+
[ -n "$base" ] || base=$(git -C "$root" rev-parse HEAD) || exit 1
|
|
22
|
+
wave=$(mktemp -d) && wave=$(cd -- "$wave" >/dev/null && pwd -P) || exit 1
|
|
23
|
+
printf '%s\n' "$base" > "$wave/base" || exit 1
|
|
24
|
+
mkdir -p "$root/religion/.state" && printf '%s\n' "$wave" > "$root/religion/.state/wave" || exit 1
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
Each line that can fail stops the block, rather than `set -e`, which would stay on in a tool
|
|
28
|
+
that keeps one shell between commands. The block runs in a subshell, so its exits end only
|
|
29
|
+
the subshell and never that shell; it hands nothing back, since every later phase reads the
|
|
30
|
+
saved values. The `cd` sends its output away so a shell hook that prints on a directory change
|
|
31
|
+
cannot end up in the saved path. Without the stops, a failed `mktemp` leaves `wave`
|
|
32
|
+
empty, `cd ""` stays in the project, and the block saves the project itself as the wave; a
|
|
33
|
+
failed `git stash create` falls back to `HEAD` and drops uncommitted earlier steps.
|
|
34
|
+
|
|
35
|
+
A saved wave that already exists means an earlier one was not torn down, which is what a
|
|
36
|
+
failed join leaves. The block prints the directory it names and stops, so the record of where
|
|
37
|
+
those worktrees are is never overwritten; deal with them first.
|
|
38
|
+
|
|
39
|
+
Every command that touches the project runs against `$root`, never the current directory.
|
|
40
|
+
From a subdirectory, `git apply` skips every path outside it and still exits 0, and
|
|
41
|
+
`git ls-files` lists only what is below it, so a wave started there would join nothing and
|
|
42
|
+
then tear down the work as if it had landed.
|
|
43
|
+
|
|
44
|
+
`git stash create` makes a commit of the working tree without touching the stash list, any
|
|
45
|
+
ref, or the working tree, so uncommitted earlier steps are part of the base. An empty result
|
|
46
|
+
means the tree is clean, and `HEAD` is the base.
|
|
47
|
+
|
|
48
|
+
Never `git stash push`, and never `git add -N` in the project: the user's index and stash
|
|
49
|
+
are not the wave's to change.
|
|
50
|
+
|
|
51
|
+
## 2. Create
|
|
52
|
+
|
|
53
|
+
`pwd -P` saves the resolved path, the form `git rev-parse` reports, so the check below can
|
|
54
|
+
compare the two. A shell that does not keep variables between commands starts each later
|
|
55
|
+
phase by reading the values back, and stops if the current directory has drifted into one of
|
|
56
|
+
the wave's own worktrees:
|
|
57
|
+
|
|
58
|
+
root=$(git rev-parse --show-toplevel)
|
|
59
|
+
case "$root" in */step-*) [ ! -f "${root%/*}/base" ] || exit 1;; esac
|
|
60
|
+
wave=$(cat "$root/religion/.state/wave")
|
|
61
|
+
base=$(cat "$wave/base")
|
|
62
|
+
case "$root/" in "$wave"/*) exit 1;; esac
|
|
63
|
+
|
|
64
|
+
From inside a worktree, `root` would be that worktree, and the join would land there and
|
|
65
|
+
report success while the project stayed unchanged. The first `case` stops in a step worktree
|
|
66
|
+
of any wave, including one kept by an earlier failed join, recognised by the `base` file in
|
|
67
|
+
the wave directory beside it; the second stops in one of this wave's own. A linked worktree
|
|
68
|
+
the user works from is neither, so it is not refused.
|
|
69
|
+
|
|
70
|
+
Then, once per step, named for the step's number:
|
|
71
|
+
|
|
72
|
+
git -C "$root" worktree add --detach "$wave/step-<n>" "$base"
|
|
73
|
+
|
|
74
|
+
`--detach` creates no branch, whatever `git.mode` says. Keeping the worktrees outside the
|
|
75
|
+
project keeps a second copy of the source out of the project's own tests, linters and file
|
|
76
|
+
watchers.
|
|
77
|
+
|
|
78
|
+
The stash commit leaves untracked files out, so copy them in, all but the workflow's own
|
|
79
|
+
state, which would carry the saved wave path into the worktree:
|
|
80
|
+
|
|
81
|
+
git -C "$root" ls-files --others --exclude-standard -z |
|
|
82
|
+
while IFS= read -r -d '' f; do
|
|
83
|
+
case "$f" in religion/.state/*) continue;; esac
|
|
84
|
+
mkdir -p "$wave/step-<n>/$(dirname "$f")" && cp -RPp "$root/$f" "$wave/step-<n>/$f"
|
|
85
|
+
done
|
|
86
|
+
|
|
87
|
+
`-RP` copies an untracked link as a link. Plain `cp` refuses a link to a directory, and
|
|
88
|
+
following it would copy whatever it points at.
|
|
89
|
+
|
|
90
|
+
Then make the copies part of the step's base, so they are not handed back as new files:
|
|
91
|
+
|
|
92
|
+
git -C "$wave/step-<n>" add -A
|
|
93
|
+
git -C "$wave/step-<n>" write-tree > "$wave/step-<n>.base"
|
|
94
|
+
|
|
95
|
+
`write-tree` saves a tree object and nothing else: no commit, no ref. The saved file is the
|
|
96
|
+
base phase 5 diffs against, and it survives a shell that does not keep variables between
|
|
97
|
+
commands.
|
|
98
|
+
|
|
99
|
+
## 3. Link
|
|
100
|
+
|
|
101
|
+
A fresh worktree has none of the project's installed dependencies. Symlink each installed
|
|
102
|
+
dependency directory at the project root that the gate needs, such as `node_modules`:
|
|
103
|
+
|
|
104
|
+
[ -e "$wave/step-<n>/node_modules" ] || [ -L "$wave/step-<n>/node_modules" ] ||
|
|
105
|
+
ln -s "$root/node_modules" "$wave/step-<n>/node_modules"
|
|
106
|
+
|
|
107
|
+
Skip the link when anything already sits at that path, such as an untracked link copied in
|
|
108
|
+
phase 2. `ln -s` onto an existing link to a directory does not replace it: it creates a new
|
|
109
|
+
link inside the directory it points at, which here is the project's real dependencies.
|
|
110
|
+
|
|
111
|
+
Never install into a worktree. That is a dependency install, which the Authority section
|
|
112
|
+
does not grant a wave. On Windows the link would be a junction, which this routine has not
|
|
113
|
+
been run against.
|
|
114
|
+
|
|
115
|
+
## 4. Build
|
|
116
|
+
|
|
117
|
+
When the project's gate reads build output that is not committed, run the project's build in
|
|
118
|
+
the worktree before handing it to the subagent, so the step's checks can run there at all.
|
|
119
|
+
Run it in a subshell, `(cd "$wave/step-<n>" && <build>)`, so the current directory never
|
|
120
|
+
moves into the worktree.
|
|
121
|
+
|
|
122
|
+
## 5. Extract
|
|
123
|
+
|
|
124
|
+
When the subagent returns, take its patch:
|
|
125
|
+
|
|
126
|
+
git -C "$wave/step-<n>" add -A
|
|
127
|
+
git -C "$wave/step-<n>" diff --cached --binary "$(cat "$wave/step-<n>.base")" -- . <exclusions> > "$wave/step-<n>.patch"
|
|
128
|
+
|
|
129
|
+
`add -A` writes to the worktree's own index, never the project's, and brings new files into
|
|
130
|
+
the patch. `<exclusions>` is one `':!<path>'` for each link made in phase 3, which an ignore
|
|
131
|
+
rule with a trailing slash such as `node_modules/` does not cover, because a link is not a
|
|
132
|
+
directory. Then one more for each path the project's instructions declare as generated, such
|
|
133
|
+
as rendered trees, so two steps that both regenerate a shared file do not
|
|
134
|
+
collide over output the build reproduces. A project that declares none keeps every path, and
|
|
135
|
+
a generated file that both steps touched then fails the join and stops it.
|
|
136
|
+
|
|
137
|
+
## 6. Join
|
|
138
|
+
|
|
139
|
+
All patches, in step order, through **one** `git apply` of one file:
|
|
140
|
+
|
|
141
|
+
for p in "$wave"/step-<n>.patch "$wave"/step-<m>.patch; do [ -f "$p" ] || exit 1; done
|
|
142
|
+
cat "$wave"/step-<n>.patch "$wave"/step-<m>.patch > "$wave/join.patch"
|
|
143
|
+
git -C "$root" apply "$wave/join.patch"
|
|
144
|
+
|
|
145
|
+
Check that every step's patch exists first, and never pipe into `git apply`: without
|
|
146
|
+
`pipefail` a pipe reports `git apply`'s status alone, so a missing patch would land the
|
|
147
|
+
others as a success and teardown would delete the step that never applied.
|
|
148
|
+
|
|
149
|
+
One concatenated input applies all or nothing, including two patches to the same file.
|
|
150
|
+
Separate patch files given to one `git apply` do not: the first lands even when a later one
|
|
151
|
+
fails. Never `--3way`, never a merge, and never resolve a conflict by editing.
|
|
152
|
+
|
|
153
|
+
On failure the project is exactly as it was. Stop the wave, keep the worktrees, and report
|
|
154
|
+
the paths `git apply` named and where each worktree is.
|
|
155
|
+
|
|
156
|
+
On success, run the project's build once when any path was excluded, so generated output is
|
|
157
|
+
rendered from the joined sources, then run each step's gate on the joined tree.
|
|
158
|
+
|
|
159
|
+
## 7. Teardown
|
|
160
|
+
|
|
161
|
+
Only after a successful join, and only for worktrees this run created for this wave. Once per
|
|
162
|
+
step:
|
|
163
|
+
|
|
164
|
+
rm "$wave/step-<n>/node_modules"
|
|
165
|
+
git worktree remove --force "$wave/step-<n>"
|
|
166
|
+
rm "$wave/step-<n>.patch" "$wave/step-<n>.base"
|
|
167
|
+
|
|
168
|
+
Then once for the wave:
|
|
169
|
+
|
|
170
|
+
rm "$wave/join.patch" "$wave/base"
|
|
171
|
+
rmdir "$wave"
|
|
172
|
+
rm "$root/religion/.state/wave"
|
|
173
|
+
|
|
174
|
+
Remove each link first, so nothing follows it into the project's real dependencies. Removing
|
|
175
|
+
any other worktree, or these after a failed join, still asks.
|
package/template/AGENTS.md
CHANGED
|
@@ -104,6 +104,10 @@ carry to a later instance.
|
|
|
104
104
|
- deleting files or data, and any rewrite of existing history
|
|
105
105
|
- changing a remote service, its configuration, or its secrets
|
|
106
106
|
|
|
107
|
+
Removing a detached worktree that the current run created for a wave, outside the project,
|
|
108
|
+
once that wave's join has succeeded, is not deleting files or data. Removing any other
|
|
109
|
+
worktree still asks.
|
|
110
|
+
|
|
107
111
|
Pushing a branch that a run created itself is the single action that may be granted ahead
|
|
108
112
|
of time, and only in the narrow way set out below. Every other push is first tier.
|
|
109
113
|
|
|
@@ -184,8 +188,9 @@ needed, a conflict appears, the work drifts outside its spec, or something in th
|
|
|
184
188
|
tier of the authority rules comes up.
|
|
185
189
|
|
|
186
190
|
`workflow.parallelSteps` ships `false`. Turned on, steps a spec marks as independent are
|
|
187
|
-
built together in one wave
|
|
188
|
-
|
|
191
|
+
built together in one wave, each in its own git worktree, then joined and reviewed as one
|
|
192
|
+
packet, so nothing lands unreviewed either way. A join that does not apply cleanly stops for
|
|
193
|
+
review. While it is off, those markers are ignored and every spec builds in order.
|
|
189
194
|
## Activity
|
|
190
195
|
|
|
191
196
|
Any skill that changes something writes an activity record to
|
package/template/CLAUDE.md
CHANGED
|
@@ -68,6 +68,10 @@ carry to a later instance.
|
|
|
68
68
|
- deleting files or data, and any rewrite of existing history
|
|
69
69
|
- changing a remote service, its configuration, or its secrets
|
|
70
70
|
|
|
71
|
+
Removing a detached worktree that the current run created for a wave, outside the project,
|
|
72
|
+
once that wave's join has succeeded, is not deleting files or data. Removing any other
|
|
73
|
+
worktree still asks.
|
|
74
|
+
|
|
71
75
|
Pushing a branch that a run created itself is the single action that may be granted ahead
|
|
72
76
|
of time, and only in the narrow way set out below. Every other push is first tier.
|
|
73
77
|
|
|
@@ -148,8 +152,9 @@ needed, a conflict appears, the work drifts outside its spec, or something in th
|
|
|
148
152
|
tier of the authority rules comes up.
|
|
149
153
|
|
|
150
154
|
`workflow.parallelSteps` ships `false`. Turned on, steps a spec marks as independent are
|
|
151
|
-
built together in one wave
|
|
152
|
-
|
|
155
|
+
built together in one wave, each in its own git worktree, then joined and reviewed as one
|
|
156
|
+
packet, so nothing lands unreviewed either way. A join that does not apply cleanly stops for
|
|
157
|
+
review. While it is off, those markers are ignored and every spec builds in order.
|
|
153
158
|
## Activity
|
|
154
159
|
|
|
155
160
|
Any skill that changes something writes an activity record to
|