Skip to content
Open
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
69 changes: 66 additions & 3 deletions .agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ agent back at one file prevents that structurally.
| ------------------------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------- |
| Claude Code | `CLAUDE.md` (first line `@AGENTS.md`) + `.claude/` | `CLAUDE.md`, `.claude/settings.json`, `.claude/{agents,commands,skills}` symlinks |
| OpenCode | `.opencode/opencode.jsonc` `instructions` | `.opencode/opencode.jsonc`, `.opencode/{…}` symlinks |
| GitHub Copilot | native `AGENTS.md` (2026); legacy stub | `.github/copilot-instructions.md` → `AGENTS.md` (opt-in `copilot`) |
| GitHub Copilot | coding agent: native root `AGENTS.md`; code review: `.github/` files only | populated `.github/copilot-instructions.md` + `.github/instructions/` review rules + `.github/skills/code-review/` skill (opt-in `copilot_code_review`; code review does **not** read `AGENTS.md`) |
| OpenAI Codex | native root `AGENTS.md` (32 KiB doc cap) | none needed |
| Google Gemini CLI | `.gemini/settings.json` `context.fileName` | add `.gemini/settings.json` → `AGENTS.md` (see recipe below) |
| Jules, Cursor, Windsurf, Roo Code, Zed, JetBrains Junie, Aider, Amp | native root `AGENTS.md` | none needed |
Expand All @@ -35,15 +35,73 @@ agent back at one file prevents that structurally.
ln -s ../../AGENTS.md .continue/rules/AGENTS.md
```
3. **Needs a config key or a frontmatter'd file?** Add a thin pointer/config stub
that references `AGENTS.md`. Never copy instruction prose into it. Precedents:
that references `AGENTS.md`. Never copy instruction prose into it. Precedent:
```jsonc
// .github/copilot-instructions.md → one line: see ../AGENTS.md
// .gemini/settings.json
{ "context": { "fileName": ["AGENTS.md", "GEMINI.md"] } }
```
4. Put tool-specific guidance (not meant for every agent) in that tool's own file,
not in `AGENTS.md`.

## Layout

Everything here is a single source read by both tools. The symlinks are
created by the post-generation hook; if you copy this layout manually,
recreate them with:

```sh
ln -s ../.agents/subagents .claude/agents && ln -s ../.agents/subagents .opencode/agents
ln -s ../.agents/commands .claude/commands && ln -s ../.agents/commands .opencode/commands
ln -s ../.agents/skills .claude/skills && ln -s ../.agents/skills .opencode/skills
```

### `subagents/`

One Markdown file per role, with YAML frontmatter. Supported keys:

- `name` (required) — invocation name; identity comes from this, not the
filename.
- `description` (required) — used by parent agents to decide when to
delegate; start with "Use proactively when…" for auto-discovery.
- `model` (optional) — `sonnet` / `opus` / `haiku` / `inherit`.
- `tools` (optional) — Claude Code allowlist, comma-separated names
(e.g. `Read, Grep, Glob, Bash`). Claude Code only.
- `permission` (optional) — OpenCode per-action map with keys
`read` / `write` / `edit` / `bash`, each taking `allow` / `ask` / `deny`;
`bash` can also be a per-pattern map (e.g. `"rg *": allow`, `"*": deny`).
OpenCode only — Claude Code ignores this field.
- `mode` (optional, **strongly recommended for subagents**) — OpenCode-only.
Set `mode: subagent` to keep the agent delegation-only; the default
(`all`) would also expose it as a top-level primary OpenCode agent.

A subagent runs in its own context window — use them to keep heavy
exploration or repetitive review out of the main session's context. Claude
Code subagents **cannot spawn other subagents**: when a role needs something
run outside itself (an `explorer` pass, a spike experiment, a user's
answer), it stops and hands back to the main agent, carrying the request
and the resume instruction in its own reply — the role subagents' Handoff
sections define these protocols. The reply must be self-contained because a
subagent can be reached by description match as well as by its slash
command, and in the former case the command's instructions were never
loaded.

### `commands/`

One Markdown file per slash command, with YAML frontmatter (`description`,
optional `argument-hint`). Keep each command short and imperative — the
description is what surfaces in the slash-command picker, and the body is
the prompt the agent will follow.

### `skills/`

One directory per skill, containing a `SKILL.md` (required, with YAML
frontmatter `name` and `description`) and optionally `scripts/`
(deterministic executables), `references/` (docs loaded on demand), and
`assets/`. `design-principles/` is the skill that ships — the shared design
ground rules and red-flag checklist the role subagents read. Write skill
descriptions slightly "pushy" — agents tend to under-trigger skills —
and include synonyms.

## Caveats

- **Copier-managed.** This harness is generated from a Copier template
Expand All @@ -58,3 +116,8 @@ agent back at one file prevents that structurally.
- **Destructive-command deny-list** is canonical in
[`hooks/block-destructive.sh`](hooks/block-destructive.sh); OpenCode's deny globs
are a hand-kept mirror (it cannot call a script).
- **Hook payload parsing** is canonical in
[`hooks/hook-input.sh`](hooks/hook-input.sh): the Claude Code hooks in
`.claude/settings.json` read their JSON input through it (`jq`, with a
`python3` fallback). With neither parser on PATH, the PreToolUse guard fails
closed with an explanatory message and SessionStart prints a warning.
20 changes: 0 additions & 20 deletions .agents/commands/README.md

This file was deleted.

22 changes: 17 additions & 5 deletions .agents/commands/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,22 @@ You are carrying out the implementation phase of a feature.
- Run `make verify` at every phase boundary.
- Stop at the end of each phase and hand off to `/verify`
(Reviewer) before starting the next.
5. When the developer reports a phase complete, **stop** and ask the
user to run `/verify` before proceeding. Do not auto-start the
next phase.
5. If the developer stops mid-phase, that is not a phase boundary. Its
reply names the stop and the servicing instruction; follow it:
- `HANDBACK(explore):` in `scratch.md` — run the `explorer`, append
its answer as a `RESULT(explore):` line (keep the `path:LINE`
citations), then re-invoke the developer. After three explore
hand-backs on the same phase, the phase is scoped too wide — stop
and put it to the user.
- `DECISION-PENDING:` in `report.md` — put the question to the user,
add the register row (`development/adr/README.md`), re-invoke the
developer with the answer.
- `HANDBACK(replan):` in `scratch.md` — hand back to `/plan`
(Architect), then re-run `/build`. After three replan hand-backs
on the same feature, the plan and reality are not converging —
stop and put the mismatch to the user instead of re-planning.
6. When the developer reports a phase complete, **stop** and ask the
user to run `/verify`. Do not auto-start the next phase.

Never silently skip a failing test, edit anything under `*/generated/`,
or run destructive Git. If the plan turns out to be wrong, hand back
to `/plan` (Architect) rather than silently re-planning.
or run destructive Git.
13 changes: 6 additions & 7 deletions .agents/commands/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@ You are expanding a feature spec into an implementation plan.
the architecture-decisions block, the "each phase has tests"
contract, and the "stop and ask before coding" boundary.
4. If the architect hands back a **spike request** — a
`SPIKE-REQUEST:` line in `scratch.md` naming one question (it cannot
run code itself) — run the smallest throwaway experiment that answers
it, in a temp dir or scratch space, never left in the source tree.
Append the result to `scratch.md` on a `SPIKE-FINDING:` line
`HANDBACK(spike):` line in `scratch.md` naming one question (it
cannot run code itself) — run the smallest throwaway experiment that
answers it, in a temp dir or scratch space, never left in the source
tree. Append the result to `scratch.md` on a `RESULT(spike):` line
(question → method → answer → evidence), then re-invoke the
architect; it starts with fresh context and reads `scratch.md` to
pick the answer up. Spike code is disposable; only the findings
survive, in the plan's **Spike findings** section.
Cap this at **three rounds per plan**. A fourth request means the
uncertainty is not a design experiment — stop and put the question
to the user.
After **three spike hand-backs on the same plan**, the uncertainty
is not a design experiment — stop and put the question to the user.

The architect subagent will write `plan.md` and mirror it into
`tasks.md`, then stop for user review. Once the user confirms the
Expand Down
24 changes: 11 additions & 13 deletions .agents/commands/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,20 @@ You are creating a new feature spec.
"stop and ask before planning" boundary.
5. If the product-owner hands back a **clarifying question** instead of
`spec.md`, put it to the user, then re-invoke the product-owner
subagent with the question and the user's answer included in the
prompt — it starts each invocation with fresh context and cannot see
the exchange otherwise. Repeat until it produces the spec. Cap this
at **five rounds**: if the questions have not converged by then, stop
and ask the user to settle the scope directly.
subagent with the question, the user's answer, **and the prior Q&A**
included in the prompt — it starts each invocation with fresh
context and cannot see the exchange otherwise (the carried Q&A is
also how it knows the round count). Repeat until it produces the
spec. Cap this at **five rounds**: if the questions have not
converged by then, stop and ask the user to settle the scope
directly.

The product-owner subagent will write `spec.md` and then stop for user
review. Once the user confirms the spec, the next step is `/plan`
(Architect role). Do not start implementing yet.

If the reviewed spec's **Glossary** section pins down new domain terms,
promote them to `development/glossary.md` as part of the review wrap-up
(the product-owner subagent cannot write outside the feature
directory). The glossary is a **register**: promotion from a reviewed
spec is its one sanctioned mid-feature channel (see Document liveness
in `development/harness-usage.md`), and the Reviewer checks each
promoted entry against the spec's Glossary section — so promote the
reviewed terms verbatim, and don't fold in renames or meaning changes
of existing entries (those are trunk-gated, a dedicated PR).
promote them verbatim to `development/glossary.md` as part of the
review wrap-up (the product-owner subagent cannot write outside the
feature directory; the promotion rule lives in
`development/glossary.md`).
6 changes: 4 additions & 2 deletions .agents/commands/verify.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ Delegate the work to the **reviewer** subagent
(`.agents/subagents/reviewer.md`). It will:

- Read `spec.md`, `plan.md` (including its **Review checklist**
section, if present), `tasks.md`, `report.md` (if the Developer has
started it — expected by the final phase), and the current diff.
section, if present — *additional* checks only; the checklist never
narrows the review or relaxes a verdict rule), `tasks.md`, `report.md`
(if the Developer has started it — expected by the final phase), and
the current diff.
- Run `make verify` itself — it never takes the Developer's
word for the gate.
- Check spec conformance, plan conformance, implementation quality,
Expand Down
9 changes: 9 additions & 0 deletions .agents/hooks/block-destructive.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@
# script does not.
#
# See .agents/README.md for the single-source-of-truth rationale.
#
# DIVERGES FROM THE COPIER TEMPLATE. Upstream matches the raw command text
# with one substring `grep -qE`, which denies any command that merely *quotes*
# a pattern — enough to block a `rg` search of this file or a PR description
# explaining it. Behaviour here is pinned by tests/test_harness_config.py; on
# `copier update`, keep this version and reject the upstream hunk. Like
# upstream, the deny decision stays on POSIX `grep -qE`: `grep -o` is a
# non-POSIX extension that suppresses stdout on binary-classified input, so
# using the extracted match as the decision would fail open.

cmd=$(cat)
[ -n "$cmd" ] || exit 0
Expand Down
68 changes: 68 additions & 0 deletions .agents/hooks/hook-input.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env sh
# hook-input.sh — canonical reader for agent-hook JSON payloads.
#
# Usage: hook-input.sh <dot.path> (stdin: the hook's JSON payload)
# e.g. hook-input.sh .tool_input.command
#
# Prints the field's value on stdout, identically under either backend:
# '' for null/absent (including a path through a non-object), 'true'/'false'
# for booleans, raw text for strings and numbers, JSON for objects/arrays.
#
# Exit codes: 0 read OK; 3 no working JSON parser on PATH; 4 empty or
# unparseable payload. Callers branch on the distinction to pick their own
# failure posture and message.
#
# Parses with jq when available, falling back to python3. The fallback is
# probed by *running* python3, not `command -v` alone — stock macOS ships a
# /usr/bin/python3 stub that passes `command -v` but fails until the Xcode
# Command Line Tools are installed.
#
# Consumers:
# - Claude Code: the PostToolUse, PreToolUse, and Stop hooks in
# .claude/settings.json read their payloads through this script.
#
# See .agents/README.md for the single-source-of-truth rationale.

payload=$(cat)
if [ -z "$payload" ]; then
echo 'hook-input.sh: empty hook payload on stdin.' >&2
exit 4
fi

if command -v jq >/dev/null 2>&1; then
out=$(printf '%s' "$payload" | jq -r --arg p "$1" '
($p | split(".") | map(select(length > 0))) as $parts
| (try getpath($parts) catch null)
| if . == null then "" elif type == "boolean" then tostring else . end
' 2>/dev/null) || {
echo 'hook-input.sh: cannot parse the hook payload as JSON.' >&2
exit 4
}
printf '%s\n' "$out"
exit 0
fi

if python3 -c '' >/dev/null 2>&1; then
printf '%s' "$payload" | python3 -c '
import json, sys
try:
v = json.load(sys.stdin)
except ValueError:
print("hook-input.sh: cannot parse the hook payload as JSON.", file=sys.stderr)
sys.exit(4)
for p in [p for p in sys.argv[1].split(".") if p]:
v = v.get(p) if isinstance(v, dict) else None
if v is None:
print("")
elif v is True or v is False:
print(str(v).lower())
elif isinstance(v, (dict, list)):
print(json.dumps(v))
else:
print(v)
' "$1"
exit $?
fi

echo 'hook-input.sh: no working JSON parser (jq or python3) on PATH; cannot read the hook input. Install jq (apt-get install jq / brew install jq).' >&2
exit 3
17 changes: 0 additions & 17 deletions .agents/skills/README.md

This file was deleted.

Loading