Skip to content
22 changes: 14 additions & 8 deletions .agents/tasks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,21 @@ the file, it is meaningful from the start and never needs renaming.
Durable status, hand-off notes, and resumable plans belong **in this
file** — never appended to the random-named plan-mode file.

Keep the two apart by routing plan mode's throwaway files to a gitignored
scratch dir via `.claude/settings.json`:
Plan-mode files land in this directory too — `.claude/settings.json` sets:

{
"plansDirectory": ".claude/plans"
"plansDirectory": ".agents/tasks"
}

`.claude/plans/` is gitignored: plan-mode files stay in the working tree
(inspectable, resumable within the session) but out of version control
and out of `.agents/tasks/`, which then holds only meaningful, committed
task files.
so approval artifacts are visible beside the durable notes they precede
instead of hiding under `.claude/`. The two kinds stay apart by
lifecycle, not location:

- A **task file** is committed, carries frontmatter, and has a
meaningful slug an agent chose.
- A **plan-mode file** is untracked, frontmatter-less, and random-named.
Never stage, commit, or rename one — it may be deleted freely once its
session is over.

## File format

Expand Down Expand Up @@ -91,7 +95,9 @@ complete unchecked items by ticking them and adding a `Log` line.

1. **Discover** — at task start, scan `.agents/tasks/` for `in-progress`
or `blocked` plans **whose `branch` frontmatter equals the current git
branch**, and resume rather than restart. Ignore plans tagged to other
branch**, and resume rather than restart. A file without `status`
frontmatter (e.g. an untracked plan-mode artifact) is not a task
file — skip it. Ignore plans tagged to other
branches — they are someone else's in-flight work, not yours to pick up,
so a plan that lands on `master` (or any other branch) is never
resurfaced outside its own branch.
Expand Down
7 changes: 1 addition & 6 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
{
"permissions": {
"ask": [
"Bash(git commit:*)"
]
},
"plansDirectory": ".claude/plans"
"plansDirectory": ".agents/tasks"
Comment thread
alexander-yevsyukov marked this conversation as resolved.
}
17 changes: 11 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,22 @@ production.**
`.agents/...` references resolve while you edit (the repo dogfoods itself).
- Durable task and hand-off notes go in `.agents/tasks/<meaningful-slug>.md` that
**you author** with the Write tool — a meaningful name from creation, never
renamed. Claude Code's plan-mode file keeps a harness-assigned random name and is
only a throwaway approval artifact; never put durable content in it. Details:
renamed. Claude Code's plan-mode files land in the same directory (via
`plansDirectory`) under harness-assigned random names; they are throwaway
approval artifacts — never commit one or put durable content in it. Details:
[`.agents/tasks/README.md`](.agents/tasks/README.md).

## Commit and history safety

**Do not commit, push, tag, rebase, merge, cherry-pick, or otherwise write to git
history** unless the user's *current* prompt explicitly requests it. Authorization
does not carry over between turns. When in doubt: stage changes, show the diff, and
stop — let the maintainer open the pull request. See
`.agents/guidelines/safety-rules.md`.
history** without explicit authorization: a per-prompt request ("commit this"), a
session grant ("you may commit for the rest of this session") that holds on the
granted branch until revoked or the session ends, or an active skill's
`## Commit authorization` section. A grant covers only the operations it names;
history rewrites and release operations (`git push --force`, `git rebase`,
`git tag`, `gh release create`, `gh pr merge`) always stay per-action. When in
doubt: stage changes, show the diff, and stop — let the maintainer open the pull
request. See `.agents/guidelines/safety-rules.md`.

## Other safety rules

Expand Down
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@
- To create or edit a skill, use the `author-skill` skill; the full conventions
are in [`docs/authoring-skills.md`](docs/authoring-skills.md).
- `master` floats to every Spine repository, so land changes through a reviewed
pull request — do not commit or push unless explicitly asked.
pull request — do not commit or push without explicit authorization: a
per-prompt request, a session grant, or a skill's `## Commit authorization`
section, per `.agents/guidelines/safety-rules.md`.
- Per-developer memory lives in the built-in auto-memory dir.
4 changes: 2 additions & 2 deletions claude/commands/version-bumped.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ Follow the `version-bumped` skill exactly:
Do not bump.
- Exit 1 → run the `bump-version` skill to recover (it owns the policy),
then re-run the check once to confirm. Do not loop.
- Recovery may commit the bump — that is the `bump-version` skill's
documented policy; the repo's permission settings still gate `git commit`.
- Recovery may commit the bump — the `bump-version` skill's `SKILL.md`
declares it in its `## Commit authorization` section.
4 changes: 2 additions & 2 deletions guidelines/quick-reference-card.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
explicit authorization. See
[`safety-rules.md`](safety-rules.md) → *Commits and history-writing*.
Authorization comes only from a skill's `## Commit authorization`
section or from the user's current prompt — never from prior turns or
memory.
section, the user's current prompt, or an unrevoked session grant —
never from `CLAUDE.md` or memory.

🔑 **Never commit secrets** (private keys, service-account JSON, tokens).
Decrypted `*.gpg` twins like `spine-dev.json` must stay gitignored; stage
Expand Down
43 changes: 36 additions & 7 deletions guidelines/safety-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,46 @@ of the following is true *right now*:
2. **User-instructed.** The user's *current* prompt explicitly tells
the agent to perform the operation. Examples that qualify:
"commit this", "make a commit with subject X", "push the branch",
"tag this release". Authorization from previous turns, from
`CLAUDE.md`, or from any memory file does **not** carry over.
"tag this release". This form covers the named operation in the
current turn only.
3. **Session-granted.** The user's prompt explicitly grants standing
authorization for the rest of the session — e.g.
"you may commit throughout this session" or
"commit and push as needed until CI is green".
Unlike a user-instructed request, a session grant persists
across turns. Its limits:
- It covers only the operations it names: `git commit` does not
imply `git push`, and `git push` does not imply
`git push --force`.
- It is scoped to the repository and branch it was given on;
switching either suspends it until the user confirms it again.
- It never extends to history-rewriting or publishing operations:
`git push --force`, `git rebase`, `git tag`, `gh release create`,
and `gh pr merge` stay per-action even under a grant.
- It ends when the session ends or the user revokes it, and it
never carries into a new session.
- It suspends on surprise: if the secret-scan gate fires, the tree
contains changes the agent did not make, or the scope of the
next commit is unclear — stop and ask despite the grant.

If neither holds, the agent:
Neither `CLAUDE.md` nor a memory file can create authorization of any
form. In a long-running loop, put the session grant into the loop
prompt itself, so every iteration restates the grant to the agent.

If none of the three holds, the agent:

1. Stages relevant changes with `git add` (only if helpful for review).
2. Prints the proposed commit subject (if any) and `git diff --staged`.
3. **Stops.** The user runs the commit themselves, or replies with
explicit authorization in the next prompt.

The project's `.claude/settings.json` keeps `Bash(git commit:*)` in
`permissions.ask` as defense-in-depth, but the primary enforcement is
this rule — agents must not propose commit attempts that rely on the
user clicking the prompt.
Do not add `Bash(git commit:*)` to `permissions.ask` in a repo's
checked-in `.claude/settings.json`. In Claude Code, an `ask` rule
outranks every `allow` rule from every settings file, so such an entry
forces a confirmation dialog on each commit — even under a valid
session grant — and makes autonomous sessions impossible. The primary
enforcement is this rule plus the secret-scan gate; leave harness
prompting at Claude Code's defaults, so each developer decides once
per repository whether `git commit` may run without a dialog. Agents
still must not attempt a commit that relies on the user clicking a
permission prompt.