Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.6.9
1.6.10
47 changes: 42 additions & 5 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#
# Run it from a clone of this repo (or an unzipped copy that holds
# parallel-lanes/). Copies the skill to ~/.claude/skills/parallel-lanes
# (without .git), copies its parallel-lanes-worker agent type to
# (from a git checkout its tracked files only, else everything but .git),
# staged and checked first so a failed copy keeps the old install; copies its
# parallel-lanes-worker agent type to
# ~/.claude/agents/parallel-lanes-worker.md, and registers its two hooks in
# ~/.claude/settings.json (backed up first):
# SessionStart (startup|clear|compact) -> hooks/session-start.sh
Expand Down Expand Up @@ -123,13 +125,48 @@ mkdir -p "$claude_dir/skills"
if [ "$src" = "$(cd "$dest" 2>/dev/null && pwd)" ]; then
echo "Skill already at $dest; installing the agent type and registering hooks only"
else
# Stage the new copy, check it, then swap it in, so a failed copy never
# leaves the skill missing (1.6.10). The staged and old copies sit outside
# skills/, where Claude Code would see a second SKILL.md. From a git
# checkout only tracked files are copied (untracked notes and logs stay
# out); from an unzipped copy, everything but .git and tests/tmp.
stage="$claude_dir/.parallel-lanes-install.$$"
old="$claude_dir/.parallel-lanes-old.$$"
rm -rf "$stage" "$old"
mkdir -p "$stage"
copied=1
if git -C "$src" ls-files --error-unmatch SKILL.md >/dev/null 2>&1; then
list="$(mktemp)"
git -C "$src" ls-files -z | while IFS= read -r -d '' f; do
# A tracked file deleted in the work tree is not copied.
[ -e "$src/$f" ] && printf '%s\0' "$f"
done > "$list"
tar -C "$src" --null -T "$list" -cf - | tar -C "$stage" -xf - || copied=0
rm -f "$list"
else
tar -C "$src" --exclude=.git --exclude=tests/tmp -cf - . | tar -C "$stage" -xf - || copied=0
fi
for f in SKILL.md VERSION run.workflow.js agents/parallel-lanes-worker.md; do
[ "$copied" = 1 ] && cmp -s "$src/$f" "$stage/$f" && continue
rm -rf "$stage"
die "the new copy is incomplete ($f did not copy); the installed skill is unchanged"
done
chmod +x "$stage"/hooks/*.sh "$stage"/scripts/*
if [ -e "$dest" ]; then
echo "Updating existing $dest"
rm -rf "$dest"
if ! mv "$dest" "$old"; then
rm -rf "$stage"
die "cannot move the installed skill aside (is a file in it open?); it is unchanged"
fi
if ! mv "$stage" "$dest"; then
mv "$old" "$dest"
rm -rf "$stage"
die "cannot put the new copy in place; the installed skill is unchanged"
fi
rm -rf "$old"
else
mv "$stage" "$dest"
fi
mkdir -p "$dest"
tar -C "$src" --exclude=.git --exclude=tests/tmp -cf - . | tar -C "$dest" -xf -
chmod +x "$dest"/hooks/*.sh "$dest"/scripts/*
echo "Installed skill to $dest"
fi

Expand Down
12 changes: 8 additions & 4 deletions reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ PATH, else `C:/Program Files/Git`), never the WSL `bash.exe` in System32.
| `repo.ledger_dir` | `<run_dir>/<plan-name>.<run_id>.ledger`, where `<run_dir>` is the plan's directory when the plan is outside the project and its repo, else `<config dir>/parallel-lanes/runs/<run_id>/`, by default `~/.claude/parallel-lanes/runs/<run_id>/` (plan inside the project, e.g. `docs/superpowers/plans/`). It holds briefs, reports, and review packages too, so it must never be inside the project; the manifest goes in the same `<run_dir>`. |
| `commands` | `setup`, `test`, `lint`, `build`: lists of shell commands run from a checkout. Take them from the plan's conventions, then the project (package.json scripts, pyproject, Makefile). Use `[]` for a group the project lacks. |
| `lane_commands` | Optional `{<lane id>: {setup?, test?, lint?, build?}}` overrides for lanes that need only a subset (e.g. backend lanes skip the frontend suite). They are the lane's own checks: integration and verify run every distinct one, so a narrower run of a project check belongs in `task_checks`, not here. |
| `task_checks` | Optional `{<task id>: {test, lint?, build?}}`: the targeted checks a task runs once before committing (see Check ownership). `test` is required and names at least one command; an absent `lint` or `build` inherits the lane's. A batch uses its last task's entry. Every command must be a narrower run of a check the delivery inventory already runs, in the same checkout and environment; must run correctly at the task that uses it (list the lane's test files created or changed by that task and the lane's earlier tasks, cumulatively, so no entry names a file a later task creates); must fail when it selects no tests (name existing test files; a filter that passes on zero matches needs a wrapper that fails); and keeps an explicit worker limit like the project's test command (for example `node --test --test-concurrency=4 ...`). Derive the entries from the briefs' test files and commands; a task you cannot map gets no entry and runs its lane's checks. Never part of the delivery inventory. |
| `task_checks` | Optional `{<task id>: {test, lint?, build?}}`: the targeted checks a task runs once before committing (see Check ownership). `test` is required and names at least one command; an absent `lint` or `build` inherits the lane's. A batch uses its last task's entry. Every command must be a narrower run of a check the delivery inventory already runs, in the same checkout and environment; must run correctly at the task that uses it (list the lane's test files created or changed by that task and the lane's earlier tasks, cumulatively, so no entry names a file a later task creates); must fail when it selects no tests (name existing test files; a filter that passes on zero matches needs a wrapper that fails); and keeps an explicit worker limit like the project's test command (for example `node --test --test-concurrency=4 ...`). Derive the entries from the briefs' test files and commands; a task you cannot map gets no entry and runs its lane's checks. Never part of the delivery inventory. An entry for a task whose lane (or, for a prelude or join task, the project) has no test, lint or build command is a validation error from 1.6.10: the run builds no task-checks command there, so the entry would never run. |
| `widen_paths` | Optional list of project-relative paths (a trailing `/` marks a directory; an existing directory named without it counts as one too) of shared infrastructure: test harness and helpers, package and lock files, build and CI config, modules many lanes import. A task whose change touches one runs project-wide checks instead of its targeted ones. Name the files every check runs through (wrappers, shared fixtures, settings), not a folder that also holds tools and tests; name a folder only when everything under it is shared (`Packages/`, `ProjectSettings/`). For example, run f2a listed `Tools/Unity/` for the Unity batch wrappers in it: units RP2 and RB4 declared other files under it (`Tools/Unity/tests/__init__.py`) and widened to every Unity suite, 72 minutes on the critical path (32% of the run) that found nothing; the wrapper files themselves were the entries it needed. The dry run lists every unit that will widen (`checks.widening`, see Confirmation header). Take them from the plan's shared files; no absolute path or `..`. |
| `repairs_spent` | Optional integer, default 0: repairs after a failing verify that earlier launches of this run made (the sum of `final.repairs` lengths in their reports; a report without `final` made none). Set it on a transient relaunch or a resume, like the budget carry-over; a run allows 2 in all (see Check ownership). |
| `prelude` | Tasks run first on the feature branch, before lanes. |
| `lanes` | `[{id, name, setup_note?, tasks}]`. `id` matches `^[A-Za-z0-9_][A-Za-z0-9._-]*$` (it names the ledger file); `name` is the progress phase label. Ids `prelude` and `join` are reserved. No file may appear in two lanes unless an `overlaps` entry records it; files are compared normalized (`./a`, `a//b`, `a/../b`) and case-insensitively. |
| `lanes` | `[{id, name, setup_note?, tasks}]`. `id` matches `^[A-Za-z0-9_][A-Za-z0-9._-]*$` (it names the ledger file); `name` is the progress phase label. Ids `prelude` and `join` are reserved. No file may appear in two lanes unless an `overlaps` entry records it; files are compared normalized (`./a`, `a//b`, `a/../b`) and case-insensitively. A folder entry (`src/`, or `src` holding a path another task lists) covers the paths under it, as task-checks reads it, so a folder in one lane and a path under it in another are a claim by both lanes too (from 1.6.10); an `overlaps` entry on the file, listing both tasks, records it. Join waves use the same rule: a unit that declares a folder never shares a wave with a unit declaring a path under it. |
| `join` | Tasks run in order on the merged branch after integration. |
| task | `{id, title, files, tier, security, batch?, depends_on?}`. `id` exactly as in the plan heading (`T13a`, `7`) and a safe file name (`^[A-Za-z0-9_][A-Za-z0-9._-]*$`: it names brief, report, and review files); `title` and `files` from derive-lanes (project-relative: no absolute path, nothing that leaves the project; each review is shown the files its range changes outside `files`, from git, and judges each); `tier` `standard`, `sonnet`, or `light` (see Tiers); a sonnet or light task cannot have `security: true`; `batch` only on light tasks (see Batching); `depends_on` `[{id, kind: "code" or "contract"}]` (see Building lanes). |
| `overlaps` | Optional `[{file, tasks, reason, merge_owner, validation?}]`: a file tasks in different lanes both change on purpose. Every listed task lists the file; `merge_owner` is one of them (its version wins where both changes cannot stand); `validation` says how the merged file is checked (a command, or what to look at). The integration agent is told about each and runs the validation after the merge. |
Expand Down Expand Up @@ -733,7 +733,11 @@ raised it).
since the final re-review must follow it), or the agents unavailable, the run makes no call
and records `final.start_check` as `unavailable` with `why: 'budget'`, and nothing else
changes; `repairReserve` is unchanged, since the start check runs before repair
admission and its agent is already counted.
admission and its agent is already counted. From 1.6.10 it is an advisory call: it runs once,
with no retry and no availability probe, and when no agent is free it is not run and sets no
refusal latch. A start check that returns nothing is `unavailable` (`why: 'no result'`), and
the run goes on to its acceptance; before 1.6.10 its refused retry could stop the run on
`budget`.
- Agents unavailable (see "Usage limit") uses the same refusal path: once the availability
probe found no agent answering, every later spawn is refused, `verify` included (a refused
verify is never stored as evidence), and the run stops with reason `agents_unavailable`
Expand Down Expand Up @@ -2071,7 +2075,7 @@ Hard failures:
|---|---|
| `git_identity` | `git var GIT_COMMITTER_IDENT` or (from 1.6.7) `git var GIT_AUTHOR_IDENT` fails, after any `-c user.name=... -c user.email=...` pair `commit_rules` passes |
| `base_not_current` | the local `base_ref` does not contain its upstream tip (details below) |
| `python` | the manifest's `python` does not run Python 3.8 or newer |
| `python` | the manifest's `python` does not run Python 3.8 or newer, run as one path the way the run uses it (a path with spaces is fine), or it is wrapped in quotes (from 1.6.10; doctor used to split it into shell words) |
| `dirty_checkout` | git mode: the checkout has uncommitted changes or untracked, non-ignored files; its fix says to commit any file a check needs and gitignore only known generated output, and never to ignore source a check needs |
| `shadow_repo` | shadow mode: the shadow repo is missing or broken |

Expand Down
Loading
Loading