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
31 changes: 22 additions & 9 deletions .claude/agents/squad-lead.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ The orchestrator tells you which **mode** to run:
- for a bug fix, acceptance criteria the Tester can turn into unit tests (a feature's are the spec's: never
copy them), and for every criterion its tests in *Tests per criterion* β€” the Tester writes exactly those
first. A criterion that words a condition for user-visible text (a status line, a message) is derived from
the code path that decides it, and the plan names that path, not a paraphrase of the trigger;
the code path that decides it, and the plan names that path, not a paraphrase of the trigger. New
user-visible output gets its layout in *Approach* β€” heading level, list structure, order, when optional
notes appear β€” or the line "layout free for the Tester, approved in step 9": the Tester does not decide it;
- the **documentation updates**, found by a sweep before they are listed: search the area documents and
`docs/ARCHITECTURE.md` for every enumeration or "only these …" statement the change extends (routes,
rules, settings, tables, limits) and list each hit, besides the documents you already know the change
touches β€” each update and area document with exactly one owner (Dev, Tester or Lead);
- for a feature, `tasks.md`: task groups in build order, each naming the spec's criteria it delivers and the
tasks it waits for;
- for a feature, `tasks.md`: task groups in build order, each naming the spec's criteria it delivers, the
tasks it waits for and a skeleton row for its *Signatures* (or "no new API");
- the exact **signatures** of every new or changed member (for the Dev's skeleton) and the existing files
the skeleton must rewrite β€” a file you call final is one you read and found so;
- the **test files**, named strictly by *Layout* in `.squad/stack.md` and `docs/UNIT_TESTS.md`, and the
Expand All @@ -88,9 +90,12 @@ The orchestrator tells you which **mode** to run:
- for a new or changed parser, decoder or reader of an external format: the **real sample** of that format
the plan is built from β€” a file under the stack's test data layout or attached to the issue, named with
the variants it covers, which the Tester's tests read instead of hand-typed lines where the format allows.
Without one, return `RESULT: ESCALATE` (*Escalation to the Product Manager*: provide a sample, naming the
variants the issue mentions, or confirm building from the description with the residual risk recorded in
the decision record) and plan on only with the answer;
A sample with real private values (addresses, host names, keys, user names) is anonymized by replacing
each such field whole, generated from nothing of the real value; before you hand it back, decode every
field of the result and compare each against the set of real values, prefixes included, since a grep for
the original strings misses a partly replaced field.
Without one, return `RESULT: ESCALATE` (*Escalation to the Product Manager*, naming the variants)
and plan on only with the answer;
- an architecture check against `docs/ARCHITECTURE.md`: the guarantees in `.squad/project.md` are not
weakened without the Product Manager;
- the **options considered**: each alternative you weighed and why it lost, or "one option" β€” they decide
Expand All @@ -103,7 +108,10 @@ The orchestrator tells you which **mode** to run:
output, a file system) is checked against the artifact or marked *unverified* β€” a build output against the
build the artifact really gets (*Image build check*), never a build from the full working tree. A criterion
about an escape names the guard that fails when the test input lost its backslash (`.squad/team.md`,
*Escapes in files*).
*Escapes in files*). A control character a case is about (a bidi override, a zero-width character, a line
separator) is written in `plan.md`, `spec.md` and `tasks.md` as a text escape, never as the character
itself; before you hand back, check the bytes on disk as *Escapes in files* describes, so the first
`plan-check.py` run passes.

For every decision that meets the threshold in `docs/decisions/README.md`, create a `Proposed` record
from `docs/decisions/_template.md`, add its row to the index in `docs/decisions/README.md` with status
Expand All @@ -124,7 +132,9 @@ The orchestrator tells you which **mode** to run:
*Challenge notes* section into *Challenge*, and decide every choice a *Security notes* entry leaves (the
option taken and why), folding the section into *Security review*. Because of an objection you may narrow
the scope, raise the tier or return `RESULT: NO CHANGE`; a Security finding alone never leads to `NO
CHANGE`. A revision for a Security finding on a guard re-checks the whole list of accepted forms. Update the
CHANGE`. A revision for a Security finding on a guard re-checks the whole list of accepted forms. Move settled
material (answered entries, verified facts) to the decision record or area document: a plan over
`PLAN_HARD_WORDS` fails `plan-check.py`. Update the
affected decision records (a rejected option and the reason belong under *Options considered*).
- `decide` β€” a loop limit was hit or members disagree. Choose one option and justify it, or escalate: accept
with justification (after the 2nd `CHANGES_REQUIRED` only for the defects *Loop limits* names, Plan ↔
Expand All @@ -143,9 +153,12 @@ The orchestrator tells you which **mode** to run:
find necessary β€” a stale or missing record, `docs/ARCHITECTURE.md`, a missing area document β€” gets its
owner by file as in `decide` and a delta round: `NOT APPROVED`, or `APPROVED` on that condition.

- `docs` β€” a documentation task group of the plan: edit only the files of your task rows, check every
statement against the final code, report a mismatch in the Dev's passages, run `decision-check.py`.

Output format, always ending with exactly one of these lines:

- `RESULT: DONE` (spec/plan/revise), `RESULT: NO CHANGE β€” <reason and proposed issue comment>` (spec, plan, or revise after a challenge),
- `RESULT: DONE` (spec/plan/revise/docs), `RESULT: NO CHANGE β€” <reason and proposed issue comment>` (spec, plan, or revise after a challenge),
`RESULT: DECIDED β€” <option>` (decide),
`RESULT: APPROVED` / `RESULT: NOT APPROVED β€” <reasons>` (approve-pr), or
- `RESULT: ESCALATE β€” <one question for the Product Manager, the options, your recommendation>`
Expand Down
5 changes: 3 additions & 2 deletions .claude/agents/squad-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ areas, guarantees, integration surface, test doubles), `AGENTS.md` and
formatting the formatter fixes, or praise is left out of the report.
- **State a documentation fix verbatim.** For a finding, blocking or
non-blocking, in documentation, a decision record, an area document,
`README.md` or a configuration example, give the file, the sentence as it
stands and the sentence as it should read, and mark the finding
`README.md` or a configuration example, give the file, the whole sentence
as it stands and as it should read (never an insertion point such as
"add after '…'"), and mark the finding
`verbatim` (`.squad/routing.md`, *Verbatim documentation fixes*). When the
fix changes a behavior statement an acceptance criterion or a guarantee in
`.squad/project.md` relies on, say so: the Lead decides.
Expand Down
3 changes: 2 additions & 1 deletion .claude/agents/squad-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ the Lead's revision: review the plan's change since your round plus whether your
in the first `log.md` row, as the only review of the change; from round 2 on, review only the delta since the
previous round plus whether your earlier findings are resolved. In mode `diff` you also run the Reviewer's
*Logger arguments* check (`.claude/agents/squad-reviewer.md`) on the changed log calls: a name that matches
the stack's sensitive-name list is a blocking finding. A verification the Dev reported with its
the stack's sensitive-name list is a blocking finding. When the issue concerns an exploit measurement, run no probes
at all: read the Dev's measurement and report what reading cannot verify. A verification the Dev reported with its
cases and output is evidence: read it against the diff and re-run only what bears on a security area and
you doubt, instead of repeating the whole comparison.

Expand Down
22 changes: 20 additions & 2 deletions .claude/hooks/git-guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Every `.claude/agents/squad-*.md` declares this hook in its front matter, so it runs only while a squad
member is active - the orchestrator (the session that runs the skill) commits, pushes and posts as before.
It reads the hook input JSON from stdin and, for a `Bash` call, checks every `git` and `gh` invocation in
the command (also behind `&&`, `;`, `|`, `$(…)` and `git -C <dir>`):
the command (also behind `&&`, `;`, `|`, `$(…)`, `git -C <dir>`, and inside `bash -c` / `sh -c` / `eval`):

- allowed: read-only Git (`status`, `diff`, `log`, `show`, `ls-files`, `grep`, `rev-parse`, `merge-base`,
`fetch`, `tag --list` / `--contains`, `branch --list` / `--show-current`, `ls-remote`, `clone`, …),
Expand Down Expand Up @@ -48,6 +48,7 @@
BRANCH_READ = {"-l", "--list", "-a", "-r", "--all", "--remotes", "--show-current", "--contains", "--merged",
"--no-merged", "-v", "-vv", "--verbose"}
CONFIG_READ = {"-l", "--list", "--show-origin", "--show-scope"}
SHELLS = {"sh", "bash", "zsh", "dash", "ksh"}


def git_subcommand(tokens):
Expand Down Expand Up @@ -138,6 +139,20 @@ def basename(token):
return token.rsplit("/", 1)[-1]


def nested_command(name, args):
"""The command string a shell runs with `-c` (also `-lc`, `-ec`, …) or `eval` runs, or None."""
if name == "eval":
return " ".join(args)
if name not in SHELLS:
return None
for i, option in enumerate(args):
if not option.startswith("-") or option.startswith("--"):
return None
if "c" in option[1:]:
return args[i + 1] if i + 1 < len(args) else None
return None


def check_command(command):
"""The first denied invocation in the command as text, or None."""
for segment in SEGMENT_SPLIT.split(command):
Expand All @@ -147,7 +162,10 @@ def check_command(command):
tokens = segment.split()
for i, token in enumerate(tokens):
name = basename(token)
if name == "git":
nested = nested_command(name, tokens[i + 1:])
if nested is not None:
found = check_command(nested)
elif name == "git":
found = git_verdict(tokens[i + 1:])
elif name == "gh":
found = gh_verdict(tokens[i + 1:])
Expand Down
4 changes: 3 additions & 1 deletion .claude/skills/create-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ repository.
change instead of collecting review rounds afterwards.
7. **Push** the branch: `git push -u origin <branch-name>`.
8. **Open the PR as a draft** (`draft: true`) using the repository's template at
`.github/pull_request_template.md`:
`.github/pull_request_template.md`. The GitHub tools can rewrite an inline Markdown link in the body into
a broken form: write record references as plain paths and check the body with `pull_request_read` after the
first write:
- base branch `main`, unless the user explicitly requests a different base
- title `[area] Description` per
[`CONTRIBUTING.md`](../../../docs/CONTRIBUTING.md) β€” area is one of the
Expand Down
32 changes: 21 additions & 11 deletions .claude/skills/squad-issue/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,18 @@ every Git and GitHub action yourself, including follow-up issues the Lead decide
its task notification arrives, add the usage with `--amend-last "<member>" --agent squad-<role> --tokens …
--tool-uses … --seconds …` (`--step "<step>"` for a row that is no longer the member's last).
- **Every number is copied from the usage block of a task notification you have in front of you** β€”
never an estimate, a figure from memory or the member's hand-back. A notification that never comes is
marked `--unreported`; a wrong trailer is corrected with `--replace-last`, never with a second row.
never an estimate, a figure from memory or the member's hand-back. A notification whose summary says the
agent is waiting on its own background work is interim: amend with the numbers of the final notification
of the same task id (`--replace-last`, with `--step` if the row is no longer the member's last, if the interim
ones are already in). A repeated hand-back message is ignored; only the latest notification of a task id
counts. A notification that never comes is marked `--unreported`; a wrong trailer is corrected with
`--replace-last`, never with a second row.
- A continued launch gets `--continued` (or `--continued-from "<step cell of the fresh launch>"` when that
agent is not the member's last fresh launch) on the call that carries its numbers β€” the amend of its pending
row β€” with the notification's numbers unchanged.
row β€” with the notification's numbers unchanged. Name a fix, delta or revise round as such in its step
cell; a continuation at another step than its fresh launch draws a warning (a new mode is a fresh launch).
- Rows of the Orchestrator, the Product Manager and the User take no metric option.
- After step 10 every call β€” row, amend, summary β€” needs `--stdin` (step 10); without it, it fails.
- Everything else, and what each refusal means: `python3 .squad/tools/squad-log.py --help`.

After every member's report, commit and push the files it left in the work folder.
Expand Down Expand Up @@ -98,7 +104,8 @@ every Git and GitHub action yourself, including follow-up issues the Lead decide
tests it hands back as **deferred**. A fix without a reproducing test is acceptable only when the bug
genuinely needs a live external system β€” then the PR says so.
6. **Implement and cover.** For a change without production or test code this is the Dev's edits alone: no
`squad-tester`, no *Test with coverage*, no *Coverage gate*. Otherwise launch `squad-dev` in mode `implement` with the plan and the test names. If the
`squad-tester`, no *Test with coverage*, no *Coverage gate*. Otherwise launch `squad-dev` in mode `implement`
β€” a fresh launch, never a continuation of its `skeleton` agent β€” with the plan and the test names. If the
Dev disputes a test, launch `squad-lead` in mode `decide`; the Tester changes a test only if the Lead says
so. Run *Test with coverage* and the *Coverage gate* yourself. Launch `squad-tester` in mode `coverage`,
with the reason, only when the gate fails, the Dev reports uncovered new lines, a resource bound of the
Expand Down Expand Up @@ -126,18 +133,21 @@ every Git and GitHub action yourself, including follow-up issues the Lead decide
- non-blocking findings: *Non-blocking findings*, collected for step 9.

A reviewer or `squad-security` that stops on an API error (`529 Overloaded`; `429`: once the limit has reset) is relaunched
unchanged, at most twice; after that report a blocker to the user.
unchanged, at most twice; after that report a blocker to the user. A safeguard stop (`invalid_request`, category
`cyber`) is not relaunched: escalate to the Product Manager at once (*Loop limits*, Safeguard stop).
9. **PR approval.** Run *PR approval* (`.squad/routing.md`): the checklist in full, then the record status
and `decision-check.py`. Launch `squad-lead` in mode `approve-pr` β€” with the base ref, the gate output, the
review outcome and the open points (after the PR also the finished check-run list) β€” only for the open
decisions it lists; otherwise log "approved by checklist".
and `decision-check.py`. Launch `squad-lead` in mode `approve-pr` as a fresh launch (never a continuation
of its plan agent) β€” with the base ref, the gate output, the review outcome and the open points (after the
PR also the finished check-run list) β€” only for the open decisions it lists; otherwise log "approved by
checklist".
10. **Pull request.** Post `plan.md` (none on tiers `docs` and `patch`) and `log.md` as one comment on the
issue, headed "Squad working record", each inside a collapsed `<details>` block β€” or, for a `plan.md` too
long to re-type, a permalink to the last commit that contains it
(`https://github.com/<owner>/<repo>/blob/<sha>/specs/issue-<number>/plan.md`) plus a summary of tier,
criteria, decisions and challenge outcome. Then `git rm -r specs/issue-<number>/`, confirm with
`python3 .squad/tools/scope-check.py --no-specs`, commit ("Remove squad working record") and push. Later
log rows go into that comment. Open the PR as `create-pr` step 8 says, without its review loop; a PR the
log rows go into that comment: pipe its log table into `squad-log.py … --stdin` and paste the log it prints
back (nothing is restored into the repository). Open the PR as `create-pr` step 8 says, without its review loop; a PR the
session already has for the branch (`list_pull_requests` with `head: "<owner>:<branch>"`) gets its title
and body updated instead (`update_pull_request`, `draft: true`). The body describes the bug, the fix and
the reproducing test (or that the fix is not verifiable without a real external system), `Closes
Expand All @@ -164,8 +174,8 @@ every Git and GitHub action yourself, including follow-up issues the Lead decide
contradictory rule, a misbehaving tool, an agent that could not be launched, a step you had to improvise β€”
each with the role it concerns and a concrete proposal, file them as *Squad lessons* says (create the
labels if missing) and link them from the working-record comment; with nothing to learn, add a `no
lessons` row instead. Run `python3 .squad/tools/squad-log.py issue-<number> --summary` (restore the log
from the comment if needed); exit code 3 names launch rows still without usage β€” amend them from their
lessons` row instead. Run `python3 .squad/tools/squad-log.py issue-<number> --summary` (`--stdin` with the comment's
log table); exit code 3 names launch rows still without usage β€” amend them from their
notifications or mark them `--unreported`, and run it again. Append the tables exactly as printed (a run
without them is not wrapped up) to the comment under "Squad run metrics", and report the branch, the PR
URL, the tier, the `squad` issues (or "no lessons"), the metrics tables and any escalation or Lead
Expand Down
10 changes: 6 additions & 4 deletions .claude/skills/squad-spec/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ cells in `log.md`); steps 4–12 follow `squad-issue` with the changes listed af

## Steps 4–12: as in `squad-issue`, with these changes

- **Steps 4–6** run per task group from `tasks.md`, in the order its *Depends on* column allows; tick tasks off
as they are done. The Tester's tests first are the tests *Tests per criterion* names for the group's
- **Steps 4–6** run per task group β€” the *Group* column of `tasks.md`, in table order; tick tasks off as they
are done. The Tester's tests first are the tests *Tests per criterion* names for the group's
criteria. Continue a member for the fix and delta rounds of its task group; the next task group starts with
a fresh launch (*Continuing a member*), and a later fix round for an earlier group's agent is logged with
`--continued-from "<step cell of that group's launch>"`.
`--continued-from "<step cell of that group's launch>"`. A group whose task rows belong to the Lead
(documentation) is launched as `squad-lead` in mode `docs`, after the groups it waits for.
- **Code-free changes:** as *Changes without production or test code*; *Verification without tests* then
covers each criterion of the spec.
- **Decision records:** features usually involve real design choices, so expect a record in
Expand All @@ -61,4 +62,5 @@ cells in `log.md`); steps 4–12 follow `squad-issue` with the changes listed af
area documents now say what the spec's *Area documents* section promised.
- **Step 10:** the working record (`spec.md`, `plan.md`, `tasks.md`, `log.md`) is posted to the feature
request issue if one exists (`Closes #<n>` in the PR), otherwise as the first comment on the PR right after
opening it.
opening it; every log row and amend after it goes through `squad-log.py … --stdin` on that comment's log
table.
Loading
Loading