diff --git a/.agents/skills/delegate/SKILL.md b/.agents/skills/delegate/SKILL.md
new file mode 100644
index 0000000..01e0251
--- /dev/null
+++ b/.agents/skills/delegate/SKILL.md
@@ -0,0 +1,102 @@
+---
+name: delegate
+description: Code Cannon: Generate a scoped prompt for delegating work to another agent
+---
+
+> **Codex CLI:** This skill is triggered by description matching. State any arguments in your message. Sub-agent spawning is not supported — perform any review steps inline.
+
+---
+
+## What `/delegate` does
+
+`/delegate` generates a ready-to-paste prompt that gives another AI agent everything it needs to contribute code on the current branch. Run it during the planning gate of `/start` (Step 2), before saying `go`, to carve off part of the work for another agent.
+
+Each `/delegate` invocation also tells the lead agent to skip that work when it proceeds.
+
+---
+
+## Prerequisites
+
+`/delegate` relies on context already loaded by `/start`. If you are not in the middle of a `/start` planning gate (Case A Step 2 or Case B Step 2), say:
+
+> "`/delegate` is meant to be run during the planning step of `/start`, before you type `go`. Run `/start` first to load the issue context, then use `/delegate` to carve off tasks."
+
+Stop.
+
+If `$ARGUMENTS` is empty, ask: **"What task should the prompt describe?"** and wait for a response.
+
+---
+
+## Step 1 — Gather branch and diff context
+
+```bash
+git branch --show-current
+```
+
+```bash
+git diff --name-only dev...HEAD 2>/dev/null || git diff --name-only main...HEAD
+```
+
+---
+
+## Step 2 — Build the prompt
+
+Compose the following as a fenced markdown block (` ```markdown `) so the operator can copy it.
+
+Use the issue title, body, and agent implementation notes from the current `/start` session — do not re-fetch them.
+
+```markdown
+## Your task
+
+<$ARGUMENTS — the operator's description of the delegated work>
+
+## Branch
+
+You are on branch ``. Stay on this branch.
+
+## Context
+
+**Issue #: **
+
+
+
+
+
+## What's already been done on this branch
+
+
+
+## Rules
+
+- **Do not commit.** Leave all changes unstaged. The lead developer will handle commits.
+- **Be surgical.** Only modify what is necessary for your task. Do not refactor adjacent code, reorganize imports, or "improve" files outside the scope of your task.
+- **Do not start until the branch exists on the remote.** The lead developer will create and push the branch. Wait until `git checkout && git pull origin ` succeeds before beginning work.
+```
+
+---
+
+## Step 3 — Present to operator and update lead scope
+
+Print the generated prompt preceded by:
+
+> Here's a delegation prompt for: **$ARGUMENTS**
+>
+> Review and edit as needed, then paste into your agent of choice after the lead agent has created and pushed the branch.
+
+Then print the fenced prompt block.
+
+After presenting, record internally that `$ARGUMENTS` has been delegated. When the operator says `go` and the lead agent proceeds to write code, it must skip the delegated work. If multiple `/delegate` invocations occur before `go`, accumulate all of them.
+
+After presenting, say:
+
+> Noted — the lead agent will skip this part when you say `go`. Run `/delegate ` to delegate more, or type `go` to proceed.
+
+---
+
+## Hard rules
+
+- Never commit, push, or modify any files.
+- Never post to GitHub (no comments, labels, or issue updates).
+- Output only — the skill produces text for the operator to copy.
+- Do not suggest or name a specific agent to paste into — that is the operator's choice.
+
diff --git a/.agents/skills/deploy/SKILL.md b/.agents/skills/deploy/SKILL.md
index 48dadbe..4523b10 100644
--- a/.agents/skills/deploy/SKILL.md
+++ b/.agents/skills/deploy/SKILL.md
@@ -117,7 +117,19 @@ If the user chose a bump level, map their response to a command:
| "major" | `make bump-major` |
| A specific version e.g. "2.4.5" | `make set-version V= 2.4.5` |
-These commands update the version manifest, create a git commit, and create a git tag. Do not create commits or tags manually.
+These commands update the version manifest, create a git commit, and create a git tag.
+
+After the bump command runs, verify the tag was actually created:
+
+```bash
+git tag -l "v"
+```
+
+If the tag is missing (some git configs like `tag.forceSignAnnotated = true` reject lightweight tags silently), create an annotated tag as a fallback:
+
+```bash
+git tag -a "v" -m "v"
+```
Push the version bump:
```bash
@@ -253,4 +265,4 @@ After the command runs, note the release URL from the output.
Tell the user:
> "Released vX.Y.Z. Issues #N, #M closed automatically. GitHub Release vX.Y.Z created at ``. Run `make deploy-prod` to ship to production."
-
+
diff --git a/.agents/skills/setup/SKILL.md b/.agents/skills/setup/SKILL.md
index f86ffc7..936f3d2 100644
--- a/.agents/skills/setup/SKILL.md
+++ b/.agents/skills/setup/SKILL.md
@@ -258,7 +258,104 @@ A value counts as "set" if it is present, uncommented, and non-empty in `.codeca
---
-### Phase 2 — Label population
+### Phase 2 — Permission audit
+
+Check whether the agent's permission configuration covers the shell commands Code Cannon skills use. Read `CodeCannon/permissions.yaml` to get the list of required command prefixes.
+
+**Claude Code:** Read `.claude/settings.local.json` (if it exists) and `.claude/settings.json` (if it exists). Collect all `Bash(...)` entries from the `permissions.allow` arrays in both files. For each command prefix in `permissions.yaml`, check whether an allow rule covers it (e.g. `Bash(git:*)` or `Bash(git *)` covers the `git` prefix).
+
+If all prefixes are covered, display `Agent permissions: all skill commands pre-approved` and continue to Phase 3.
+
+If any prefixes are missing, show:
+
+```
+Agent permissions: some skill commands may prompt for approval.
+
+ Missing allow rules:
+ - Bash(cd:*)
+ - Bash(make:*)
+ ...
+
+ To pre-approve these, add them to .claude/settings.local.json (git-ignored)
+ or .claude/settings.json (shared with team). See docs/index.md for a full example.
+
+ This is optional — you can approve commands individually when prompted instead.
+```
+
+Do not modify any settings file. This is advisory only.
+
+**Other agents (Cursor, Codex, Gemini):** Skip this phase silently — Cursor doesn't prompt, and Codex/Gemini permission systems vary. The docs cover these agents separately.
+
+---
+
+### Phase 3 — Commit signing
+
+Check whether commit signing is already configured at any level (local or global):
+
+```bash
+git config --get commit.gpgsign
+```
+
+If the output is `true`, display `Commit signing: enabled` in the health summary area and skip to Phase 4.
+
+If not `true`, ask: **"Does this project require signed commits? (yes/no)"**
+
+Wait for response.
+
+- **no / skip** → continue to Phase 4.
+- **yes** → proceed with signing setup.
+
+**Verify a signing key exists:**
+
+```bash
+git config --get user.signingkey
+```
+
+**If a signing key is found**, show the proposed change and confirm:
+
+```
+I'll enable commit and tag signing for this repo:
+
+ git config commit.gpgsign true
+ git config tag.gpgsign true
+
+ Signing key:
+
+Proceed? (yes/no)
+```
+
+Wait for confirmation. Write only on yes. If no, skip to Phase 4.
+
+Continue to Phase 4.
+
+**If no signing key is found**, detect the signing format:
+
+```bash
+git config --get gpg.format
+```
+
+- If `ssh` → suggest: `git config user.signingkey ~/.ssh/id_ed25519.pub` (adjust path to the user's key). Ask the user for their SSH public key path.
+- If `gpg` or unset → suggest: run `gpg --list-secret-keys --keyid-format=long` to find a key ID. Ask the user for their GPG key ID.
+
+Once the user provides a key value, show the proposed changes and confirm:
+
+```
+I'll configure signing for this repo:
+
+ git config user.signingkey
+ git config commit.gpgsign true
+ git config tag.gpgsign true
+
+Proceed? (yes/no)
+```
+
+Wait for confirmation. Write only on yes. If no, skip to Phase 4.
+
+If the user has no signing key and doesn't know how to create one, point them to GitHub's signing key documentation and stop: "Set up a signing key first, then run `/setup` again to enable commit signing."
+
+---
+
+### Phase 4 — Label population
Run:
@@ -290,7 +387,7 @@ Wait for response.
After this step (or if labels were non-zero initially), run `gh label list --limit 100 --json name,color,description` again.
-If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 3; otherwise leave it unset so `/start` auto-detects."
+If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 5; otherwise leave it unset so `/start` auto-detects."
Display the results as a numbered list:
@@ -308,7 +405,7 @@ Wait for the user's response.
- **yes** → use all labels
- **numbers** (e.g. `1,3,5`) → use only those labels
-- **no / skip / anything else** → skip this phase, continue to Phase 3
+- **no / skip / anything else** → skip this phase, continue to Phase 5
Show the exact change before writing:
@@ -324,7 +421,7 @@ Wait for confirmation. Write only on yes.
---
-### Phase 3 — Optional config walkthrough (profile-aware)
+### Phase 5 — Optional config walkthrough (profile-aware)
First, infer the current profile using the same rules as Phase 1.
@@ -346,7 +443,7 @@ The walkthrough adapts based on profile. Walk through each applicable unset valu
---
-### Phase 4 — Team sharing
+### Phase 6 — Team sharing
After completing or skipping the config walkthrough, say:
@@ -364,11 +461,11 @@ Add a note: `/start` can be used to create well-formed GitHub issues without wri
## Hard rules
-- Only modify `.codecannon.yaml`. Do not touch any other file (except running `CodeCannon/sync.py`, which modifies `.claude/commands/` — permitted only with explicit user approval).
+- Only modify `.codecannon.yaml` and local git config (Phase 3 signing setup). Do not touch any other file (except running `CodeCannon/sync.py`, which modifies `.claude/commands/` — permitted only with explicit user approval).
- Do not run `sync.py` without explicit user permission.
- Do not create `.codecannon.yaml` without explicit user permission.
- Do not report a configuration problem unless confident the condition is genuinely broken. Prefer false negatives over false positives on all diagnostic checks.
- Never fetch more than 100 labels in a single command. `gh label list --limit 100` is the ceiling.
-- Do not skip any human gate in Phase 2 or Phase 3 — each write requires confirmation.
+- Do not skip any human gate in Phase 3, Phase 4, or Phase 5 — each write requires confirmation.
- If the user skips a config value, do not ask again. Move on.
-
+
diff --git a/.agents/skills/start/SKILL.md b/.agents/skills/start/SKILL.md
index c8f0433..8389046 100644
--- a/.agents/skills/start/SKILL.md
+++ b/.agents/skills/start/SKILL.md
@@ -25,6 +25,8 @@ Otherwise → go to **Case A: New work**.
> Skip this entirely if `$ARGUMENTS` triggered Case B.
+> **Execution order:** Resolve labels and milestones **now**, before entering Case A Step 1. If milestone auto-detection requires a user prompt (2+ open milestones), that prompt happens here — not later during issue creation. By the time you reach Step 2's human gate, all metadata must already be resolved so that Step 3 can proceed without re-prompting.
+
The argument string may contain optional inline flags after the description. Parse as follows:
1. **Identify flags** — scan for the first token that starts with `--label`, `-l`, `--milestone`, or `-m`. Everything before it is the **description**. Everything from the first flag onward is **flags**.
@@ -107,18 +109,13 @@ Read the relevant code. Propose a concrete implementation approach. Be specific
Say exactly:
-> **"Does this approach sound right? Type `go` to create a GitHub issue and branch, or share any questions/adjustments first."**
+> **"Does this approach sound right? Type `go` to create a GitHub issue and branch, or share any questions/adjustments first. To delegate part of the work to another agent, run `/delegate ` before typing `go`."**
Stop. Wait for the user to respond.
The friendly text question is required regardless of harness mode. If your harness is currently in a preview / plan / dry-run mode where you cannot passively stop and wait (and must instead invoke the harness's own approval mechanism), still include the text question in your response. The harness's approval UI mediates the wait, but it is not a substitute for the question itself. Users expect to see the consistent text language across all modes; do not silently swap it for the harness's UI.
-**Intent classification (not keyword matching):**
-
-- **Affirmative with no conditions** ("go", "yes", "looks good", "let's do it") → continue to Step 3.
-- **Affirmative with conditions** ("go, but first change X", "yes but can we also...") → treat as discussion. Address the conditions, revise the approach if needed, then re-ask.
-- **Questions or pushback** ("what about...", "I'm not sure about...", any adjustment) → discuss, revise approach, re-ask.
-- **Abandonment** ("never mind", "not now", "stop") → stop. Nothing to clean up.
+Proceed only on unconditional approval. If the user's response includes conditions, questions, or adjustments, treat it as discussion — address their input and re-ask. If the user abandons ("never mind", "stop"), stop — nothing to clean up.
### Step 3 — Create GitHub Issue
@@ -147,9 +144,9 @@ gh issue create \
> **IMPORTANT — never pass body content inline in the `gh` command.** Do not use `--body`, `--body-file -`, heredocs (`<` — is the only approach that works without prompts across Claude Code, Gemini CLI, Cursor, and Codex.
-Resolve labels and milestone using the resolution steps in the Parsing section above:
-- **Labels**: use the value from three-tier label resolution. If non-empty, add `--label ""` to the command. If empty (no flag, empty pool, creation not allowed), omit `--label` entirely.
-- **Milestone**: use the value from three-tier milestone resolution. If non-empty, add `--milestone ""` to the command. If empty (no flag, no config default, no open milestones), omit `--milestone` entirely.
+Use the labels and milestone you already resolved in the Parsing section (before Step 1). Do **not** re-run label or milestone resolution here — the values are final:
+- **Labels**: if non-empty, add `--label ""` to the command. If empty, omit `--label` entirely.
+- **Milestone**: if non-empty, add `--milestone ""` to the command. If empty, omit `--milestone` entirely.
**Body structure (required sections, in this order):**
@@ -174,12 +171,6 @@ Resolve labels and milestone using the resolution steps in the Parsing section a
All five sections are required. Write for a non-developer audience — no code, no file paths. Acceptance Criteria must be concrete and verifiable (not vague goals).
-**Complexity scale guidance (for agent use only — do not include the scale definition in the issue body):**
-- **trivial** — single obvious check (e.g. color change, label text, toggle visibility)
-- **moderate** — a few scenarios to verify, minor setup needed (e.g. form validation, a new UI component with a couple of states)
-- **significant** — many scenarios, data setup, or cross-feature impact (e.g. multi-step workflow, permission changes across roles)
-- **extensive** — complex data flows, integration testing, or edge cases spanning multiple areas (e.g. data import/export mapping, API contract changes consumed by multiple clients)
-
**Title rules:**
- ✅ `Fix 'Contact Us' footer link pointing to 404 instead of /contact-us`
- ❌ `Fix broken link`
@@ -257,17 +248,43 @@ Tell the user:
- What was previously done (from agent notes if present)
- What appears to remain
-Ask: **"Does this match your understanding? Type `go` to start coding, or share any questions/adjustments first."**
+Ask: **"Does this match your understanding? Type `go` to start coding, or share any questions/adjustments first. To delegate part of the work to another agent, run `/delegate ` before typing `go`."**
+
+Proceed only on unconditional approval. If the user's response includes conditions, questions, or adjustments, treat it as discussion — address their input and re-ask. If the user wants a fresh start, restart as Case A. If the user abandons, stop — nothing to clean up.
+
+### Step 3 — Investigation findings (conditional)
+
+If the investigation in Steps 1–2 revealed anything that isn't already stated or implied by the issue body — a root cause correction, a related side-effect, a project-wide gotcha — present the findings. If the investigation simply confirmed the ticket, skip this step silently and proceed to Step 4.
+
+Create a temp directory for this invocation:
+
+```bash
+mkdir -p /tmp/CodeCannon && mktemp -d /tmp/CodeCannon/XXXXXX
+```
-**Intent classification (not keyword matching):**
+Present numbered findings:
-- **Affirmative with no conditions** ("go", "yes", "continue", "looks right") → proceed to Step 3.
-- **Affirmative with conditions** ("go, but first...", "yes but let's also...") → treat as discussion. Address the conditions, then re-ask.
-- **Questions or discussion** ("what about...", "can we change...", any adjustment) → discuss, then re-ask.
-- **Fresh start** ("open a new one", "start fresh", "new ticket") → restart as Case A with a new description.
-- **Abandonment** ("never mind", "not now", "stop") → stop. Nothing to clean up.
+> The investigation surfaced the following:
+>
+> 1.
+> 2.
+>
+> **post** as a comment to the ticket, or **skip** to continue.
-### Step 3 — Check out branch
+- `post` → use your file-writing tool (not Bash) to create `/investigation_comment.md`:
+ ```markdown
+ ## Investigation Findings
+
+ -
+ -
+ ```
+ Then post it:
+ ```bash
+ gh issue comment --body-file /investigation_comment.md
+ ```
+- `skip` → proceed silently.
+
+### Step 4 — Check out branch
Ensure the base branch is up-to-date before branching:
@@ -289,13 +306,7 @@ Verify:
git branch --show-current
```
-Post a resumption comment:
-
-```bash
-gh issue comment --body "Resuming work. "
-```
-
-### Step 4 — Write the code
+### Step 5 — Write the code
Continue from where work left off. Do NOT commit.
@@ -313,4 +324,4 @@ When done, say: **"The code is ready for review. Please run `make dev` and test
- The issue is assigned to `@me` at creation. If you are creating a ticket on someone else's behalf, remove the assignee after creation with `gh issue edit --remove-assignee @me`.
- Apply resolved labels and milestone to every new issue. Label resolution order: per-invocation flag → pool selection from `bug, documentation, enhancement, chore` → omit `--label` entirely. Never apply a label outside `bug, documentation, enhancement, chore`.
- Milestone resolution order: per-invocation flag → auto-detected from GitHub open milestones. Never prompt for a milestone more than once per invocation.
-
+
diff --git a/.agents/skills/submit-for-review/SKILL.md b/.agents/skills/submit-for-review/SKILL.md
index 31d60a8..392fa58 100644
--- a/.agents/skills/submit-for-review/SKILL.md
+++ b/.agents/skills/submit-for-review/SKILL.md
@@ -32,22 +32,16 @@ If the current branch matches any of the above, **abort immediately** and say:
## Step 2 — Type-check gate
-First, move to the repository root so the command resolves against the correct Makefile / project config:
+First, find the repository root:
```
git rev-parse --show-toplevel
```
-Then `cd` into the path returned above:
+Then `cd` to the returned path and verify the make target exists. Extract the target name from `make check` (e.g. `make check` → `check`) and run:
```
-cd
-```
-
-Then verify the make target exists before running it. Extract the target name from `make check` (e.g. `make check` → `check`) and run:
-
-```
-make -n 2>/dev/null
+cd && make -n 2>/dev/null
```
If `make -n` exits non-zero, **stop** and say:
@@ -194,13 +188,7 @@ Wait for the review to complete and report its verdict.
## Step 8 — Act on verdict
-Before merging, verify the merge target exists. Move to the repo root, extract the target name from `make merge` (e.g. `make merge` → `merge`), and run:
-
-```
-git rev-parse --show-toplevel
-```
-
-Then `cd` into the path returned above and check the target:
+Before merging, verify the merge target exists. Find the repo root with `git rev-parse --show-toplevel`, then extract the target name from `make merge` (e.g. `make merge` → `merge`) and run:
```
cd && make -n 2>/dev/null
@@ -291,11 +279,7 @@ Then post it (do NOT use `--body` or heredocs):
gh issue comment --body-file /resolution_comment.md
```
-**Resolution writing rules:**
-- Write for PMs and BAs, not developers. Describe the *outcome*, not the implementation.
-- Reference the original problem from the issue body so the resolution reads as a direct answer to it.
-- Keep it to 1-3 sentences. If one sentence covers it, don't pad.
-- Use the unqualified `#N` form for the PR reference.
+Use the unqualified `#N` form for the PR reference (not `owner/repo#N`).
Report success based on mode:
"PR merged. Issues stay open until testing confirms the fix. Run `make deploy-preview` when ready to deploy to preview."
@@ -366,4 +350,4 @@ If a single `gh issue create` call fails, report the failure for that finding an
- `/submit-for-review` merges only to `dev` — never directly to `main`.
- If `make merge` fails for any reason, report it and stop — do not attempt workarounds.
- The follow-up issue offer in Step 9 runs only after a successful merge and only when the review produced non-blocking findings. Never prompt the user for follow-ups when the review blocked the merge — those findings should be fixed, not ticketed.
-
+
diff --git a/.claude/commands/delegate.md b/.claude/commands/delegate.md
new file mode 100644
index 0000000..1bfe65d
--- /dev/null
+++ b/.claude/commands/delegate.md
@@ -0,0 +1,97 @@
+Code Cannon: Generate a scoped prompt for delegating work to another agent
+
+---
+
+## What `/delegate` does
+
+`/delegate` generates a ready-to-paste prompt that gives another AI agent everything it needs to contribute code on the current branch. Run it during the planning gate of `/start` (Step 2), before saying `go`, to carve off part of the work for another agent.
+
+Each `/delegate` invocation also tells the lead agent to skip that work when it proceeds.
+
+---
+
+## Prerequisites
+
+`/delegate` relies on context already loaded by `/start`. If you are not in the middle of a `/start` planning gate (Case A Step 2 or Case B Step 2), say:
+
+> "`/delegate` is meant to be run during the planning step of `/start`, before you type `go`. Run `/start` first to load the issue context, then use `/delegate` to carve off tasks."
+
+Stop.
+
+If `$ARGUMENTS` is empty, ask: **"What task should the prompt describe?"** and wait for a response.
+
+---
+
+## Step 1 — Gather branch and diff context
+
+```bash
+git branch --show-current
+```
+
+```bash
+git diff --name-only dev...HEAD 2>/dev/null || git diff --name-only main...HEAD
+```
+
+---
+
+## Step 2 — Build the prompt
+
+Compose the following as a fenced markdown block (` ```markdown `) so the operator can copy it.
+
+Use the issue title, body, and agent implementation notes from the current `/start` session — do not re-fetch them.
+
+```markdown
+## Your task
+
+<$ARGUMENTS — the operator's description of the delegated work>
+
+## Branch
+
+You are on branch ``. Stay on this branch.
+
+## Context
+
+**Issue #: **
+
+
+
+
+
+## What's already been done on this branch
+
+
+
+## Rules
+
+- **Do not commit.** Leave all changes unstaged. The lead developer will handle commits.
+- **Be surgical.** Only modify what is necessary for your task. Do not refactor adjacent code, reorganize imports, or "improve" files outside the scope of your task.
+- **Do not start until the branch exists on the remote.** The lead developer will create and push the branch. Wait until `git checkout && git pull origin ` succeeds before beginning work.
+```
+
+---
+
+## Step 3 — Present to operator and update lead scope
+
+Print the generated prompt preceded by:
+
+> Here's a delegation prompt for: **$ARGUMENTS**
+>
+> Review and edit as needed, then paste into your agent of choice after the lead agent has created and pushed the branch.
+
+Then print the fenced prompt block.
+
+After presenting, record internally that `$ARGUMENTS` has been delegated. When the operator says `go` and the lead agent proceeds to write code, it must skip the delegated work. If multiple `/delegate` invocations occur before `go`, accumulate all of them.
+
+After presenting, say:
+
+> Noted — the lead agent will skip this part when you say `go`. Run `/delegate ` to delegate more, or type `go` to proceed.
+
+---
+
+## Hard rules
+
+- Never commit, push, or modify any files.
+- Never post to GitHub (no comments, labels, or issue updates).
+- Output only — the skill produces text for the operator to copy.
+- Do not suggest or name a specific agent to paste into — that is the operator's choice.
+
diff --git a/.claude/commands/deploy.md b/.claude/commands/deploy.md
index 54a296d..2217a29 100644
--- a/.claude/commands/deploy.md
+++ b/.claude/commands/deploy.md
@@ -112,7 +112,19 @@ If the user chose a bump level, map their response to a command:
| "major" | `make bump-major` |
| A specific version e.g. "2.4.5" | `make set-version V= 2.4.5` |
-These commands update the version manifest, create a git commit, and create a git tag. Do not create commits or tags manually.
+These commands update the version manifest, create a git commit, and create a git tag.
+
+After the bump command runs, verify the tag was actually created:
+
+```bash
+git tag -l "v"
+```
+
+If the tag is missing (some git configs like `tag.forceSignAnnotated = true` reject lightweight tags silently), create an annotated tag as a fallback:
+
+```bash
+git tag -a "v" -m "v"
+```
Push the version bump:
```bash
@@ -248,4 +260,4 @@ After the command runs, note the release URL from the output.
Tell the user:
> "Released vX.Y.Z. Issues #N, #M closed automatically. GitHub Release vX.Y.Z created at ``. Run `make deploy-prod` to ship to production."
-
+
diff --git a/.claude/commands/setup.md b/.claude/commands/setup.md
index 15d78d5..0adb239 100644
--- a/.claude/commands/setup.md
+++ b/.claude/commands/setup.md
@@ -253,7 +253,104 @@ A value counts as "set" if it is present, uncommented, and non-empty in `.codeca
---
-### Phase 2 — Label population
+### Phase 2 — Permission audit
+
+Check whether the agent's permission configuration covers the shell commands Code Cannon skills use. Read `CodeCannon/permissions.yaml` to get the list of required command prefixes.
+
+**Claude Code:** Read `.claude/settings.local.json` (if it exists) and `.claude/settings.json` (if it exists). Collect all `Bash(...)` entries from the `permissions.allow` arrays in both files. For each command prefix in `permissions.yaml`, check whether an allow rule covers it (e.g. `Bash(git:*)` or `Bash(git *)` covers the `git` prefix).
+
+If all prefixes are covered, display `Agent permissions: all skill commands pre-approved` and continue to Phase 3.
+
+If any prefixes are missing, show:
+
+```
+Agent permissions: some skill commands may prompt for approval.
+
+ Missing allow rules:
+ - Bash(cd:*)
+ - Bash(make:*)
+ ...
+
+ To pre-approve these, add them to .claude/settings.local.json (git-ignored)
+ or .claude/settings.json (shared with team). See docs/index.md for a full example.
+
+ This is optional — you can approve commands individually when prompted instead.
+```
+
+Do not modify any settings file. This is advisory only.
+
+**Other agents (Cursor, Codex, Gemini):** Skip this phase silently — Cursor doesn't prompt, and Codex/Gemini permission systems vary. The docs cover these agents separately.
+
+---
+
+### Phase 3 — Commit signing
+
+Check whether commit signing is already configured at any level (local or global):
+
+```bash
+git config --get commit.gpgsign
+```
+
+If the output is `true`, display `Commit signing: enabled` in the health summary area and skip to Phase 4.
+
+If not `true`, ask: **"Does this project require signed commits? (yes/no)"**
+
+Wait for response.
+
+- **no / skip** → continue to Phase 4.
+- **yes** → proceed with signing setup.
+
+**Verify a signing key exists:**
+
+```bash
+git config --get user.signingkey
+```
+
+**If a signing key is found**, show the proposed change and confirm:
+
+```
+I'll enable commit and tag signing for this repo:
+
+ git config commit.gpgsign true
+ git config tag.gpgsign true
+
+ Signing key:
+
+Proceed? (yes/no)
+```
+
+Wait for confirmation. Write only on yes. If no, skip to Phase 4.
+
+Continue to Phase 4.
+
+**If no signing key is found**, detect the signing format:
+
+```bash
+git config --get gpg.format
+```
+
+- If `ssh` → suggest: `git config user.signingkey ~/.ssh/id_ed25519.pub` (adjust path to the user's key). Ask the user for their SSH public key path.
+- If `gpg` or unset → suggest: run `gpg --list-secret-keys --keyid-format=long` to find a key ID. Ask the user for their GPG key ID.
+
+Once the user provides a key value, show the proposed changes and confirm:
+
+```
+I'll configure signing for this repo:
+
+ git config user.signingkey
+ git config commit.gpgsign true
+ git config tag.gpgsign true
+
+Proceed? (yes/no)
+```
+
+Wait for confirmation. Write only on yes. If no, skip to Phase 4.
+
+If the user has no signing key and doesn't know how to create one, point them to GitHub's signing key documentation and stop: "Set up a signing key first, then run `/setup` again to enable commit signing."
+
+---
+
+### Phase 4 — Label population
Run:
@@ -285,7 +382,7 @@ Wait for response.
After this step (or if labels were non-zero initially), run `gh label list --limit 100 --json name,color,description` again.
-If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 3; otherwise leave it unset so `/start` auto-detects."
+If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 5; otherwise leave it unset so `/start` auto-detects."
Display the results as a numbered list:
@@ -303,7 +400,7 @@ Wait for the user's response.
- **yes** → use all labels
- **numbers** (e.g. `1,3,5`) → use only those labels
-- **no / skip / anything else** → skip this phase, continue to Phase 3
+- **no / skip / anything else** → skip this phase, continue to Phase 5
Show the exact change before writing:
@@ -319,7 +416,7 @@ Wait for confirmation. Write only on yes.
---
-### Phase 3 — Optional config walkthrough (profile-aware)
+### Phase 5 — Optional config walkthrough (profile-aware)
First, infer the current profile using the same rules as Phase 1.
@@ -341,7 +438,7 @@ The walkthrough adapts based on profile. Walk through each applicable unset valu
---
-### Phase 4 — Team sharing
+### Phase 6 — Team sharing
After completing or skipping the config walkthrough, say:
@@ -359,11 +456,11 @@ Add a note: `/start` can be used to create well-formed GitHub issues without wri
## Hard rules
-- Only modify `.codecannon.yaml`. Do not touch any other file (except running `CodeCannon/sync.py`, which modifies `.claude/commands/` — permitted only with explicit user approval).
+- Only modify `.codecannon.yaml` and local git config (Phase 3 signing setup). Do not touch any other file (except running `CodeCannon/sync.py`, which modifies `.claude/commands/` — permitted only with explicit user approval).
- Do not run `sync.py` without explicit user permission.
- Do not create `.codecannon.yaml` without explicit user permission.
- Do not report a configuration problem unless confident the condition is genuinely broken. Prefer false negatives over false positives on all diagnostic checks.
- Never fetch more than 100 labels in a single command. `gh label list --limit 100` is the ceiling.
-- Do not skip any human gate in Phase 2 or Phase 3 — each write requires confirmation.
+- Do not skip any human gate in Phase 3, Phase 4, or Phase 5 — each write requires confirmation.
- If the user skips a config value, do not ask again. Move on.
-
+
diff --git a/.claude/commands/start.md b/.claude/commands/start.md
index ca98ac5..e538771 100644
--- a/.claude/commands/start.md
+++ b/.claude/commands/start.md
@@ -20,6 +20,8 @@ Otherwise → go to **Case A: New work**.
> Skip this entirely if `$ARGUMENTS` triggered Case B.
+> **Execution order:** Resolve labels and milestones **now**, before entering Case A Step 1. If milestone auto-detection requires a user prompt (2+ open milestones), that prompt happens here — not later during issue creation. By the time you reach Step 2's human gate, all metadata must already be resolved so that Step 3 can proceed without re-prompting.
+
The argument string may contain optional inline flags after the description. Parse as follows:
1. **Identify flags** — scan for the first token that starts with `--label`, `-l`, `--milestone`, or `-m`. Everything before it is the **description**. Everything from the first flag onward is **flags**.
@@ -102,18 +104,13 @@ Read the relevant code. Propose a concrete implementation approach. Be specific
Say exactly:
-> **"Does this approach sound right? Type `go` to create a GitHub issue and branch, or share any questions/adjustments first."**
+> **"Does this approach sound right? Type `go` to create a GitHub issue and branch, or share any questions/adjustments first. To delegate part of the work to another agent, run `/delegate ` before typing `go`."**
Stop. Wait for the user to respond.
The friendly text question is required regardless of harness mode. If your harness is currently in a preview / plan / dry-run mode where you cannot passively stop and wait (and must instead invoke the harness's own approval mechanism), still include the text question in your response. The harness's approval UI mediates the wait, but it is not a substitute for the question itself. Users expect to see the consistent text language across all modes; do not silently swap it for the harness's UI.
-**Intent classification (not keyword matching):**
-
-- **Affirmative with no conditions** ("go", "yes", "looks good", "let's do it") → continue to Step 3.
-- **Affirmative with conditions** ("go, but first change X", "yes but can we also...") → treat as discussion. Address the conditions, revise the approach if needed, then re-ask.
-- **Questions or pushback** ("what about...", "I'm not sure about...", any adjustment) → discuss, revise approach, re-ask.
-- **Abandonment** ("never mind", "not now", "stop") → stop. Nothing to clean up.
+Proceed only on unconditional approval. If the user's response includes conditions, questions, or adjustments, treat it as discussion — address their input and re-ask. If the user abandons ("never mind", "stop"), stop — nothing to clean up.
### Step 3 — Create GitHub Issue
@@ -142,9 +139,9 @@ gh issue create \
> **IMPORTANT — never pass body content inline in the `gh` command.** Do not use `--body`, `--body-file -`, heredocs (`<` — is the only approach that works without prompts across Claude Code, Gemini CLI, Cursor, and Codex.
-Resolve labels and milestone using the resolution steps in the Parsing section above:
-- **Labels**: use the value from three-tier label resolution. If non-empty, add `--label ""` to the command. If empty (no flag, empty pool, creation not allowed), omit `--label` entirely.
-- **Milestone**: use the value from three-tier milestone resolution. If non-empty, add `--milestone ""` to the command. If empty (no flag, no config default, no open milestones), omit `--milestone` entirely.
+Use the labels and milestone you already resolved in the Parsing section (before Step 1). Do **not** re-run label or milestone resolution here — the values are final:
+- **Labels**: if non-empty, add `--label ""` to the command. If empty, omit `--label` entirely.
+- **Milestone**: if non-empty, add `--milestone ""` to the command. If empty, omit `--milestone` entirely.
**Body structure (required sections, in this order):**
@@ -169,12 +166,6 @@ Resolve labels and milestone using the resolution steps in the Parsing section a
All five sections are required. Write for a non-developer audience — no code, no file paths. Acceptance Criteria must be concrete and verifiable (not vague goals).
-**Complexity scale guidance (for agent use only — do not include the scale definition in the issue body):**
-- **trivial** — single obvious check (e.g. color change, label text, toggle visibility)
-- **moderate** — a few scenarios to verify, minor setup needed (e.g. form validation, a new UI component with a couple of states)
-- **significant** — many scenarios, data setup, or cross-feature impact (e.g. multi-step workflow, permission changes across roles)
-- **extensive** — complex data flows, integration testing, or edge cases spanning multiple areas (e.g. data import/export mapping, API contract changes consumed by multiple clients)
-
**Title rules:**
- ✅ `Fix 'Contact Us' footer link pointing to 404 instead of /contact-us`
- ❌ `Fix broken link`
@@ -252,17 +243,43 @@ Tell the user:
- What was previously done (from agent notes if present)
- What appears to remain
-Ask: **"Does this match your understanding? Type `go` to start coding, or share any questions/adjustments first."**
+Ask: **"Does this match your understanding? Type `go` to start coding, or share any questions/adjustments first. To delegate part of the work to another agent, run `/delegate ` before typing `go`."**
+
+Proceed only on unconditional approval. If the user's response includes conditions, questions, or adjustments, treat it as discussion — address their input and re-ask. If the user wants a fresh start, restart as Case A. If the user abandons, stop — nothing to clean up.
+
+### Step 3 — Investigation findings (conditional)
+
+If the investigation in Steps 1–2 revealed anything that isn't already stated or implied by the issue body — a root cause correction, a related side-effect, a project-wide gotcha — present the findings. If the investigation simply confirmed the ticket, skip this step silently and proceed to Step 4.
+
+Create a temp directory for this invocation:
+
+```bash
+mkdir -p /tmp/CodeCannon && mktemp -d /tmp/CodeCannon/XXXXXX
+```
-**Intent classification (not keyword matching):**
+Present numbered findings:
-- **Affirmative with no conditions** ("go", "yes", "continue", "looks right") → proceed to Step 3.
-- **Affirmative with conditions** ("go, but first...", "yes but let's also...") → treat as discussion. Address the conditions, then re-ask.
-- **Questions or discussion** ("what about...", "can we change...", any adjustment) → discuss, then re-ask.
-- **Fresh start** ("open a new one", "start fresh", "new ticket") → restart as Case A with a new description.
-- **Abandonment** ("never mind", "not now", "stop") → stop. Nothing to clean up.
+> The investigation surfaced the following:
+>
+> 1.
+> 2.
+>
+> **post** as a comment to the ticket, or **skip** to continue.
-### Step 3 — Check out branch
+- `post` → use your file-writing tool (not Bash) to create `/investigation_comment.md`:
+ ```markdown
+ ## Investigation Findings
+
+ -
+ -
+ ```
+ Then post it:
+ ```bash
+ gh issue comment --body-file /investigation_comment.md
+ ```
+- `skip` → proceed silently.
+
+### Step 4 — Check out branch
Ensure the base branch is up-to-date before branching:
@@ -284,13 +301,7 @@ Verify:
git branch --show-current
```
-Post a resumption comment:
-
-```bash
-gh issue comment --body "Resuming work. "
-```
-
-### Step 4 — Write the code
+### Step 5 — Write the code
Continue from where work left off. Do NOT commit.
@@ -308,4 +319,4 @@ When done, say: **"The code is ready for review. Please run `make dev` and test
- The issue is assigned to `@me` at creation. If you are creating a ticket on someone else's behalf, remove the assignee after creation with `gh issue edit --remove-assignee @me`.
- Apply resolved labels and milestone to every new issue. Label resolution order: per-invocation flag → pool selection from `bug, documentation, enhancement, chore` → omit `--label` entirely. Never apply a label outside `bug, documentation, enhancement, chore`.
- Milestone resolution order: per-invocation flag → auto-detected from GitHub open milestones. Never prompt for a milestone more than once per invocation.
-
+
diff --git a/.claude/commands/submit-for-review.md b/.claude/commands/submit-for-review.md
index 0cfa625..d21f08e 100644
--- a/.claude/commands/submit-for-review.md
+++ b/.claude/commands/submit-for-review.md
@@ -27,22 +27,16 @@ If the current branch matches any of the above, **abort immediately** and say:
## Step 2 — Type-check gate
-First, move to the repository root so the command resolves against the correct Makefile / project config:
+First, find the repository root:
```
git rev-parse --show-toplevel
```
-Then `cd` into the path returned above:
+Then `cd` to the returned path and verify the make target exists. Extract the target name from `make check` (e.g. `make check` → `check`) and run:
```
-cd
-```
-
-Then verify the make target exists before running it. Extract the target name from `make check` (e.g. `make check` → `check`) and run:
-
-```
-make -n 2>/dev/null
+cd && make -n 2>/dev/null
```
If `make -n` exits non-zero, **stop** and say:
@@ -189,13 +183,7 @@ Wait for the review to complete and report its verdict.
## Step 8 — Act on verdict
-Before merging, verify the merge target exists. Move to the repo root, extract the target name from `make merge` (e.g. `make merge` → `merge`), and run:
-
-```
-git rev-parse --show-toplevel
-```
-
-Then `cd` into the path returned above and check the target:
+Before merging, verify the merge target exists. Find the repo root with `git rev-parse --show-toplevel`, then extract the target name from `make merge` (e.g. `make merge` → `merge`) and run:
```
cd && make -n 2>/dev/null
@@ -286,11 +274,7 @@ Then post it (do NOT use `--body` or heredocs):
gh issue comment --body-file /resolution_comment.md
```
-**Resolution writing rules:**
-- Write for PMs and BAs, not developers. Describe the *outcome*, not the implementation.
-- Reference the original problem from the issue body so the resolution reads as a direct answer to it.
-- Keep it to 1-3 sentences. If one sentence covers it, don't pad.
-- Use the unqualified `#N` form for the PR reference.
+Use the unqualified `#N` form for the PR reference (not `owner/repo#N`).
Report success based on mode:
"PR merged. Issues stay open until testing confirms the fix. Run `make deploy-preview` when ready to deploy to preview."
@@ -361,4 +345,4 @@ If a single `gh issue create` call fails, report the failure for that finding an
- `/submit-for-review` merges only to `dev` — never directly to `main`.
- If `make merge` fails for any reason, report it and stop — do not attempt workarounds.
- The follow-up issue offer in Step 9 runs only after a successful merge and only when the review produced non-blocking findings. Never prompt the user for follow-ups when the review blocked the merge — those findings should be fixed, not ticketed.
-
+
diff --git a/.claude/settings.local.json.example b/.claude/settings.local.json.example
index 4e6cacb..c1ca260 100644
--- a/.claude/settings.local.json.example
+++ b/.claude/settings.local.json.example
@@ -2,6 +2,7 @@
"permissions": {
"allow": [
"Bash(cat:*)",
+ "Bash(cd:*)",
"Bash(cp:*)",
"Bash(gh:*)",
"Bash(git:*)",
diff --git a/.cursor/rules/delegate.mdc b/.cursor/rules/delegate.mdc
new file mode 100644
index 0000000..3dfde80
--- /dev/null
+++ b/.cursor/rules/delegate.mdc
@@ -0,0 +1,103 @@
+---
+description: Code Cannon: Generate a scoped prompt for delegating work to another agent
+globs:
+alwaysApply: false
+---
+
+> **Cursor:** Trigger this skill via `@delegate` in Agent mode. State any arguments in your message. Sub-agent spawning is not supported — the automated review step in `/submit-for-review` must be done manually using the review-agent prompt.
+
+---
+
+## What `/delegate` does
+
+`/delegate` generates a ready-to-paste prompt that gives another AI agent everything it needs to contribute code on the current branch. Run it during the planning gate of `/start` (Step 2), before saying `go`, to carve off part of the work for another agent.
+
+Each `/delegate` invocation also tells the lead agent to skip that work when it proceeds.
+
+---
+
+## Prerequisites
+
+`/delegate` relies on context already loaded by `/start`. If you are not in the middle of a `/start` planning gate (Case A Step 2 or Case B Step 2), say:
+
+> "`/delegate` is meant to be run during the planning step of `/start`, before you type `go`. Run `/start` first to load the issue context, then use `/delegate` to carve off tasks."
+
+Stop.
+
+If `$ARGUMENTS` is empty, ask: **"What task should the prompt describe?"** and wait for a response.
+
+---
+
+## Step 1 — Gather branch and diff context
+
+```bash
+git branch --show-current
+```
+
+```bash
+git diff --name-only dev...HEAD 2>/dev/null || git diff --name-only main...HEAD
+```
+
+---
+
+## Step 2 — Build the prompt
+
+Compose the following as a fenced markdown block (` ```markdown `) so the operator can copy it.
+
+Use the issue title, body, and agent implementation notes from the current `/start` session — do not re-fetch them.
+
+```markdown
+## Your task
+
+<$ARGUMENTS — the operator's description of the delegated work>
+
+## Branch
+
+You are on branch ``. Stay on this branch.
+
+## Context
+
+**Issue #: **
+
+
+
+
+
+## What's already been done on this branch
+
+
+
+## Rules
+
+- **Do not commit.** Leave all changes unstaged. The lead developer will handle commits.
+- **Be surgical.** Only modify what is necessary for your task. Do not refactor adjacent code, reorganize imports, or "improve" files outside the scope of your task.
+- **Do not start until the branch exists on the remote.** The lead developer will create and push the branch. Wait until `git checkout && git pull origin ` succeeds before beginning work.
+```
+
+---
+
+## Step 3 — Present to operator and update lead scope
+
+Print the generated prompt preceded by:
+
+> Here's a delegation prompt for: **$ARGUMENTS**
+>
+> Review and edit as needed, then paste into your agent of choice after the lead agent has created and pushed the branch.
+
+Then print the fenced prompt block.
+
+After presenting, record internally that `$ARGUMENTS` has been delegated. When the operator says `go` and the lead agent proceeds to write code, it must skip the delegated work. If multiple `/delegate` invocations occur before `go`, accumulate all of them.
+
+After presenting, say:
+
+> Noted — the lead agent will skip this part when you say `go`. Run `/delegate ` to delegate more, or type `go` to proceed.
+
+---
+
+## Hard rules
+
+- Never commit, push, or modify any files.
+- Never post to GitHub (no comments, labels, or issue updates).
+- Output only — the skill produces text for the operator to copy.
+- Do not suggest or name a specific agent to paste into — that is the operator's choice.
+
diff --git a/.cursor/rules/deploy.mdc b/.cursor/rules/deploy.mdc
index 4bfab50..a55d216 100644
--- a/.cursor/rules/deploy.mdc
+++ b/.cursor/rules/deploy.mdc
@@ -118,7 +118,19 @@ If the user chose a bump level, map their response to a command:
| "major" | `make bump-major` |
| A specific version e.g. "2.4.5" | `make set-version V= 2.4.5` |
-These commands update the version manifest, create a git commit, and create a git tag. Do not create commits or tags manually.
+These commands update the version manifest, create a git commit, and create a git tag.
+
+After the bump command runs, verify the tag was actually created:
+
+```bash
+git tag -l "v"
+```
+
+If the tag is missing (some git configs like `tag.forceSignAnnotated = true` reject lightweight tags silently), create an annotated tag as a fallback:
+
+```bash
+git tag -a "v" -m "v"
+```
Push the version bump:
```bash
@@ -254,4 +266,4 @@ After the command runs, note the release URL from the output.
Tell the user:
> "Released vX.Y.Z. Issues #N, #M closed automatically. GitHub Release vX.Y.Z created at ``. Run `make deploy-prod` to ship to production."
-
+
diff --git a/.cursor/rules/setup.mdc b/.cursor/rules/setup.mdc
index 689ece5..8977e7d 100644
--- a/.cursor/rules/setup.mdc
+++ b/.cursor/rules/setup.mdc
@@ -259,7 +259,104 @@ A value counts as "set" if it is present, uncommented, and non-empty in `.codeca
---
-### Phase 2 — Label population
+### Phase 2 — Permission audit
+
+Check whether the agent's permission configuration covers the shell commands Code Cannon skills use. Read `CodeCannon/permissions.yaml` to get the list of required command prefixes.
+
+**Claude Code:** Read `.claude/settings.local.json` (if it exists) and `.claude/settings.json` (if it exists). Collect all `Bash(...)` entries from the `permissions.allow` arrays in both files. For each command prefix in `permissions.yaml`, check whether an allow rule covers it (e.g. `Bash(git:*)` or `Bash(git *)` covers the `git` prefix).
+
+If all prefixes are covered, display `Agent permissions: all skill commands pre-approved` and continue to Phase 3.
+
+If any prefixes are missing, show:
+
+```
+Agent permissions: some skill commands may prompt for approval.
+
+ Missing allow rules:
+ - Bash(cd:*)
+ - Bash(make:*)
+ ...
+
+ To pre-approve these, add them to .claude/settings.local.json (git-ignored)
+ or .claude/settings.json (shared with team). See docs/index.md for a full example.
+
+ This is optional — you can approve commands individually when prompted instead.
+```
+
+Do not modify any settings file. This is advisory only.
+
+**Other agents (Cursor, Codex, Gemini):** Skip this phase silently — Cursor doesn't prompt, and Codex/Gemini permission systems vary. The docs cover these agents separately.
+
+---
+
+### Phase 3 — Commit signing
+
+Check whether commit signing is already configured at any level (local or global):
+
+```bash
+git config --get commit.gpgsign
+```
+
+If the output is `true`, display `Commit signing: enabled` in the health summary area and skip to Phase 4.
+
+If not `true`, ask: **"Does this project require signed commits? (yes/no)"**
+
+Wait for response.
+
+- **no / skip** → continue to Phase 4.
+- **yes** → proceed with signing setup.
+
+**Verify a signing key exists:**
+
+```bash
+git config --get user.signingkey
+```
+
+**If a signing key is found**, show the proposed change and confirm:
+
+```
+I'll enable commit and tag signing for this repo:
+
+ git config commit.gpgsign true
+ git config tag.gpgsign true
+
+ Signing key:
+
+Proceed? (yes/no)
+```
+
+Wait for confirmation. Write only on yes. If no, skip to Phase 4.
+
+Continue to Phase 4.
+
+**If no signing key is found**, detect the signing format:
+
+```bash
+git config --get gpg.format
+```
+
+- If `ssh` → suggest: `git config user.signingkey ~/.ssh/id_ed25519.pub` (adjust path to the user's key). Ask the user for their SSH public key path.
+- If `gpg` or unset → suggest: run `gpg --list-secret-keys --keyid-format=long` to find a key ID. Ask the user for their GPG key ID.
+
+Once the user provides a key value, show the proposed changes and confirm:
+
+```
+I'll configure signing for this repo:
+
+ git config user.signingkey
+ git config commit.gpgsign true
+ git config tag.gpgsign true
+
+Proceed? (yes/no)
+```
+
+Wait for confirmation. Write only on yes. If no, skip to Phase 4.
+
+If the user has no signing key and doesn't know how to create one, point them to GitHub's signing key documentation and stop: "Set up a signing key first, then run `/setup` again to enable commit signing."
+
+---
+
+### Phase 4 — Label population
Run:
@@ -291,7 +388,7 @@ Wait for response.
After this step (or if labels were non-zero initially), run `gh label list --limit 100 --json name,color,description` again.
-If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 3; otherwise leave it unset so `/start` auto-detects."
+If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 5; otherwise leave it unset so `/start` auto-detects."
Display the results as a numbered list:
@@ -309,7 +406,7 @@ Wait for the user's response.
- **yes** → use all labels
- **numbers** (e.g. `1,3,5`) → use only those labels
-- **no / skip / anything else** → skip this phase, continue to Phase 3
+- **no / skip / anything else** → skip this phase, continue to Phase 5
Show the exact change before writing:
@@ -325,7 +422,7 @@ Wait for confirmation. Write only on yes.
---
-### Phase 3 — Optional config walkthrough (profile-aware)
+### Phase 5 — Optional config walkthrough (profile-aware)
First, infer the current profile using the same rules as Phase 1.
@@ -347,7 +444,7 @@ The walkthrough adapts based on profile. Walk through each applicable unset valu
---
-### Phase 4 — Team sharing
+### Phase 6 — Team sharing
After completing or skipping the config walkthrough, say:
@@ -365,11 +462,11 @@ Add a note: `/start` can be used to create well-formed GitHub issues without wri
## Hard rules
-- Only modify `.codecannon.yaml`. Do not touch any other file (except running `CodeCannon/sync.py`, which modifies `.claude/commands/` — permitted only with explicit user approval).
+- Only modify `.codecannon.yaml` and local git config (Phase 3 signing setup). Do not touch any other file (except running `CodeCannon/sync.py`, which modifies `.claude/commands/` — permitted only with explicit user approval).
- Do not run `sync.py` without explicit user permission.
- Do not create `.codecannon.yaml` without explicit user permission.
- Do not report a configuration problem unless confident the condition is genuinely broken. Prefer false negatives over false positives on all diagnostic checks.
- Never fetch more than 100 labels in a single command. `gh label list --limit 100` is the ceiling.
-- Do not skip any human gate in Phase 2 or Phase 3 — each write requires confirmation.
+- Do not skip any human gate in Phase 3, Phase 4, or Phase 5 — each write requires confirmation.
- If the user skips a config value, do not ask again. Move on.
-
+
diff --git a/.cursor/rules/start.mdc b/.cursor/rules/start.mdc
index 858828e..afde6cf 100644
--- a/.cursor/rules/start.mdc
+++ b/.cursor/rules/start.mdc
@@ -26,6 +26,8 @@ Otherwise → go to **Case A: New work**.
> Skip this entirely if `$ARGUMENTS` triggered Case B.
+> **Execution order:** Resolve labels and milestones **now**, before entering Case A Step 1. If milestone auto-detection requires a user prompt (2+ open milestones), that prompt happens here — not later during issue creation. By the time you reach Step 2's human gate, all metadata must already be resolved so that Step 3 can proceed without re-prompting.
+
The argument string may contain optional inline flags after the description. Parse as follows:
1. **Identify flags** — scan for the first token that starts with `--label`, `-l`, `--milestone`, or `-m`. Everything before it is the **description**. Everything from the first flag onward is **flags**.
@@ -108,18 +110,13 @@ Read the relevant code. Propose a concrete implementation approach. Be specific
Say exactly:
-> **"Does this approach sound right? Type `go` to create a GitHub issue and branch, or share any questions/adjustments first."**
+> **"Does this approach sound right? Type `go` to create a GitHub issue and branch, or share any questions/adjustments first. To delegate part of the work to another agent, run `/delegate ` before typing `go`."**
Stop. Wait for the user to respond.
The friendly text question is required regardless of harness mode. If your harness is currently in a preview / plan / dry-run mode where you cannot passively stop and wait (and must instead invoke the harness's own approval mechanism), still include the text question in your response. The harness's approval UI mediates the wait, but it is not a substitute for the question itself. Users expect to see the consistent text language across all modes; do not silently swap it for the harness's UI.
-**Intent classification (not keyword matching):**
-
-- **Affirmative with no conditions** ("go", "yes", "looks good", "let's do it") → continue to Step 3.
-- **Affirmative with conditions** ("go, but first change X", "yes but can we also...") → treat as discussion. Address the conditions, revise the approach if needed, then re-ask.
-- **Questions or pushback** ("what about...", "I'm not sure about...", any adjustment) → discuss, revise approach, re-ask.
-- **Abandonment** ("never mind", "not now", "stop") → stop. Nothing to clean up.
+Proceed only on unconditional approval. If the user's response includes conditions, questions, or adjustments, treat it as discussion — address their input and re-ask. If the user abandons ("never mind", "stop"), stop — nothing to clean up.
### Step 3 — Create GitHub Issue
@@ -148,9 +145,9 @@ gh issue create \
> **IMPORTANT — never pass body content inline in the `gh` command.** Do not use `--body`, `--body-file -`, heredocs (`<` — is the only approach that works without prompts across Claude Code, Gemini CLI, Cursor, and Codex.
-Resolve labels and milestone using the resolution steps in the Parsing section above:
-- **Labels**: use the value from three-tier label resolution. If non-empty, add `--label ""` to the command. If empty (no flag, empty pool, creation not allowed), omit `--label` entirely.
-- **Milestone**: use the value from three-tier milestone resolution. If non-empty, add `--milestone ""` to the command. If empty (no flag, no config default, no open milestones), omit `--milestone` entirely.
+Use the labels and milestone you already resolved in the Parsing section (before Step 1). Do **not** re-run label or milestone resolution here — the values are final:
+- **Labels**: if non-empty, add `--label ""` to the command. If empty, omit `--label` entirely.
+- **Milestone**: if non-empty, add `--milestone ""` to the command. If empty, omit `--milestone` entirely.
**Body structure (required sections, in this order):**
@@ -175,12 +172,6 @@ Resolve labels and milestone using the resolution steps in the Parsing section a
All five sections are required. Write for a non-developer audience — no code, no file paths. Acceptance Criteria must be concrete and verifiable (not vague goals).
-**Complexity scale guidance (for agent use only — do not include the scale definition in the issue body):**
-- **trivial** — single obvious check (e.g. color change, label text, toggle visibility)
-- **moderate** — a few scenarios to verify, minor setup needed (e.g. form validation, a new UI component with a couple of states)
-- **significant** — many scenarios, data setup, or cross-feature impact (e.g. multi-step workflow, permission changes across roles)
-- **extensive** — complex data flows, integration testing, or edge cases spanning multiple areas (e.g. data import/export mapping, API contract changes consumed by multiple clients)
-
**Title rules:**
- ✅ `Fix 'Contact Us' footer link pointing to 404 instead of /contact-us`
- ❌ `Fix broken link`
@@ -258,17 +249,43 @@ Tell the user:
- What was previously done (from agent notes if present)
- What appears to remain
-Ask: **"Does this match your understanding? Type `go` to start coding, or share any questions/adjustments first."**
+Ask: **"Does this match your understanding? Type `go` to start coding, or share any questions/adjustments first. To delegate part of the work to another agent, run `/delegate ` before typing `go`."**
+
+Proceed only on unconditional approval. If the user's response includes conditions, questions, or adjustments, treat it as discussion — address their input and re-ask. If the user wants a fresh start, restart as Case A. If the user abandons, stop — nothing to clean up.
+
+### Step 3 — Investigation findings (conditional)
+
+If the investigation in Steps 1–2 revealed anything that isn't already stated or implied by the issue body — a root cause correction, a related side-effect, a project-wide gotcha — present the findings. If the investigation simply confirmed the ticket, skip this step silently and proceed to Step 4.
+
+Create a temp directory for this invocation:
+
+```bash
+mkdir -p /tmp/CodeCannon && mktemp -d /tmp/CodeCannon/XXXXXX
+```
-**Intent classification (not keyword matching):**
+Present numbered findings:
-- **Affirmative with no conditions** ("go", "yes", "continue", "looks right") → proceed to Step 3.
-- **Affirmative with conditions** ("go, but first...", "yes but let's also...") → treat as discussion. Address the conditions, then re-ask.
-- **Questions or discussion** ("what about...", "can we change...", any adjustment) → discuss, then re-ask.
-- **Fresh start** ("open a new one", "start fresh", "new ticket") → restart as Case A with a new description.
-- **Abandonment** ("never mind", "not now", "stop") → stop. Nothing to clean up.
+> The investigation surfaced the following:
+>
+> 1.
+> 2.
+>
+> **post** as a comment to the ticket, or **skip** to continue.
-### Step 3 — Check out branch
+- `post` → use your file-writing tool (not Bash) to create `/investigation_comment.md`:
+ ```markdown
+ ## Investigation Findings
+
+ -
+ -
+ ```
+ Then post it:
+ ```bash
+ gh issue comment --body-file /investigation_comment.md
+ ```
+- `skip` → proceed silently.
+
+### Step 4 — Check out branch
Ensure the base branch is up-to-date before branching:
@@ -290,13 +307,7 @@ Verify:
git branch --show-current
```
-Post a resumption comment:
-
-```bash
-gh issue comment --body "Resuming work. "
-```
-
-### Step 4 — Write the code
+### Step 5 — Write the code
Continue from where work left off. Do NOT commit.
@@ -314,4 +325,4 @@ When done, say: **"The code is ready for review. Please run `make dev` and test
- The issue is assigned to `@me` at creation. If you are creating a ticket on someone else's behalf, remove the assignee after creation with `gh issue edit --remove-assignee @me`.
- Apply resolved labels and milestone to every new issue. Label resolution order: per-invocation flag → pool selection from `bug, documentation, enhancement, chore` → omit `--label` entirely. Never apply a label outside `bug, documentation, enhancement, chore`.
- Milestone resolution order: per-invocation flag → auto-detected from GitHub open milestones. Never prompt for a milestone more than once per invocation.
-
+
diff --git a/.cursor/rules/submit-for-review.mdc b/.cursor/rules/submit-for-review.mdc
index 0e647df..be6468f 100644
--- a/.cursor/rules/submit-for-review.mdc
+++ b/.cursor/rules/submit-for-review.mdc
@@ -33,22 +33,16 @@ If the current branch matches any of the above, **abort immediately** and say:
## Step 2 — Type-check gate
-First, move to the repository root so the command resolves against the correct Makefile / project config:
+First, find the repository root:
```
git rev-parse --show-toplevel
```
-Then `cd` into the path returned above:
+Then `cd` to the returned path and verify the make target exists. Extract the target name from `make check` (e.g. `make check` → `check`) and run:
```
-cd
-```
-
-Then verify the make target exists before running it. Extract the target name from `make check` (e.g. `make check` → `check`) and run:
-
-```
-make -n 2>/dev/null
+cd && make -n 2>/dev/null
```
If `make -n` exits non-zero, **stop** and say:
@@ -195,13 +189,7 @@ Wait for the review to complete and report its verdict.
## Step 8 — Act on verdict
-Before merging, verify the merge target exists. Move to the repo root, extract the target name from `make merge` (e.g. `make merge` → `merge`), and run:
-
-```
-git rev-parse --show-toplevel
-```
-
-Then `cd` into the path returned above and check the target:
+Before merging, verify the merge target exists. Find the repo root with `git rev-parse --show-toplevel`, then extract the target name from `make merge` (e.g. `make merge` → `merge`) and run:
```
cd && make -n 2>/dev/null
@@ -292,11 +280,7 @@ Then post it (do NOT use `--body` or heredocs):
gh issue comment --body-file /resolution_comment.md
```
-**Resolution writing rules:**
-- Write for PMs and BAs, not developers. Describe the *outcome*, not the implementation.
-- Reference the original problem from the issue body so the resolution reads as a direct answer to it.
-- Keep it to 1-3 sentences. If one sentence covers it, don't pad.
-- Use the unqualified `#N` form for the PR reference.
+Use the unqualified `#N` form for the PR reference (not `owner/repo#N`).
Report success based on mode:
"PR merged. Issues stay open until testing confirms the fix. Run `make deploy-preview` when ready to deploy to preview."
@@ -367,4 +351,4 @@ If a single `gh issue create` call fails, report the failure for that finding an
- `/submit-for-review` merges only to `dev` — never directly to `main`.
- If `make merge` fails for any reason, report it and stop — do not attempt workarounds.
- The follow-up issue offer in Step 9 runs only after a successful merge and only when the review produced non-blocking findings. Never prompt the user for follow-ups when the review blocked the merge — those findings should be fixed, not ticketed.
-
+
diff --git a/.gemini/skills/delegate/SKILL.md b/.gemini/skills/delegate/SKILL.md
new file mode 100644
index 0000000..b0ca160
--- /dev/null
+++ b/.gemini/skills/delegate/SKILL.md
@@ -0,0 +1,102 @@
+---
+name: delegate
+description: Code Cannon: Generate a scoped prompt for delegating work to another agent
+---
+
+> **Gemini CLI:** This skill is triggered by description matching. State any arguments in your message. Sub-agent spawning is not supported — the automated review step in `/submit-for-review` must be done manually using the review-agent prompt in a separate session.
+
+---
+
+## What `/delegate` does
+
+`/delegate` generates a ready-to-paste prompt that gives another AI agent everything it needs to contribute code on the current branch. Run it during the planning gate of `/start` (Step 2), before saying `go`, to carve off part of the work for another agent.
+
+Each `/delegate` invocation also tells the lead agent to skip that work when it proceeds.
+
+---
+
+## Prerequisites
+
+`/delegate` relies on context already loaded by `/start`. If you are not in the middle of a `/start` planning gate (Case A Step 2 or Case B Step 2), say:
+
+> "`/delegate` is meant to be run during the planning step of `/start`, before you type `go`. Run `/start` first to load the issue context, then use `/delegate` to carve off tasks."
+
+Stop.
+
+If `$ARGUMENTS` is empty, ask: **"What task should the prompt describe?"** and wait for a response.
+
+---
+
+## Step 1 — Gather branch and diff context
+
+```bash
+git branch --show-current
+```
+
+```bash
+git diff --name-only dev...HEAD 2>/dev/null || git diff --name-only main...HEAD
+```
+
+---
+
+## Step 2 — Build the prompt
+
+Compose the following as a fenced markdown block (` ```markdown `) so the operator can copy it.
+
+Use the issue title, body, and agent implementation notes from the current `/start` session — do not re-fetch them.
+
+```markdown
+## Your task
+
+<$ARGUMENTS — the operator's description of the delegated work>
+
+## Branch
+
+You are on branch ``. Stay on this branch.
+
+## Context
+
+**Issue #: **
+
+
+
+
+
+## What's already been done on this branch
+
+
+
+## Rules
+
+- **Do not commit.** Leave all changes unstaged. The lead developer will handle commits.
+- **Be surgical.** Only modify what is necessary for your task. Do not refactor adjacent code, reorganize imports, or "improve" files outside the scope of your task.
+- **Do not start until the branch exists on the remote.** The lead developer will create and push the branch. Wait until `git checkout && git pull origin ` succeeds before beginning work.
+```
+
+---
+
+## Step 3 — Present to operator and update lead scope
+
+Print the generated prompt preceded by:
+
+> Here's a delegation prompt for: **$ARGUMENTS**
+>
+> Review and edit as needed, then paste into your agent of choice after the lead agent has created and pushed the branch.
+
+Then print the fenced prompt block.
+
+After presenting, record internally that `$ARGUMENTS` has been delegated. When the operator says `go` and the lead agent proceeds to write code, it must skip the delegated work. If multiple `/delegate` invocations occur before `go`, accumulate all of them.
+
+After presenting, say:
+
+> Noted — the lead agent will skip this part when you say `go`. Run `/delegate ` to delegate more, or type `go` to proceed.
+
+---
+
+## Hard rules
+
+- Never commit, push, or modify any files.
+- Never post to GitHub (no comments, labels, or issue updates).
+- Output only — the skill produces text for the operator to copy.
+- Do not suggest or name a specific agent to paste into — that is the operator's choice.
+
diff --git a/.gemini/skills/deploy/SKILL.md b/.gemini/skills/deploy/SKILL.md
index 0671f7f..b24e427 100644
--- a/.gemini/skills/deploy/SKILL.md
+++ b/.gemini/skills/deploy/SKILL.md
@@ -117,7 +117,19 @@ If the user chose a bump level, map their response to a command:
| "major" | `make bump-major` |
| A specific version e.g. "2.4.5" | `make set-version V= 2.4.5` |
-These commands update the version manifest, create a git commit, and create a git tag. Do not create commits or tags manually.
+These commands update the version manifest, create a git commit, and create a git tag.
+
+After the bump command runs, verify the tag was actually created:
+
+```bash
+git tag -l "v"
+```
+
+If the tag is missing (some git configs like `tag.forceSignAnnotated = true` reject lightweight tags silently), create an annotated tag as a fallback:
+
+```bash
+git tag -a "v" -m "v"
+```
Push the version bump:
```bash
@@ -253,4 +265,4 @@ After the command runs, note the release URL from the output.
Tell the user:
> "Released vX.Y.Z. Issues #N, #M closed automatically. GitHub Release vX.Y.Z created at ``. Run `make deploy-prod` to ship to production."
-
+
diff --git a/.gemini/skills/setup/SKILL.md b/.gemini/skills/setup/SKILL.md
index 3ca9690..4ab317b 100644
--- a/.gemini/skills/setup/SKILL.md
+++ b/.gemini/skills/setup/SKILL.md
@@ -258,7 +258,104 @@ A value counts as "set" if it is present, uncommented, and non-empty in `.codeca
---
-### Phase 2 — Label population
+### Phase 2 — Permission audit
+
+Check whether the agent's permission configuration covers the shell commands Code Cannon skills use. Read `CodeCannon/permissions.yaml` to get the list of required command prefixes.
+
+**Claude Code:** Read `.claude/settings.local.json` (if it exists) and `.claude/settings.json` (if it exists). Collect all `Bash(...)` entries from the `permissions.allow` arrays in both files. For each command prefix in `permissions.yaml`, check whether an allow rule covers it (e.g. `Bash(git:*)` or `Bash(git *)` covers the `git` prefix).
+
+If all prefixes are covered, display `Agent permissions: all skill commands pre-approved` and continue to Phase 3.
+
+If any prefixes are missing, show:
+
+```
+Agent permissions: some skill commands may prompt for approval.
+
+ Missing allow rules:
+ - Bash(cd:*)
+ - Bash(make:*)
+ ...
+
+ To pre-approve these, add them to .claude/settings.local.json (git-ignored)
+ or .claude/settings.json (shared with team). See docs/index.md for a full example.
+
+ This is optional — you can approve commands individually when prompted instead.
+```
+
+Do not modify any settings file. This is advisory only.
+
+**Other agents (Cursor, Codex, Gemini):** Skip this phase silently — Cursor doesn't prompt, and Codex/Gemini permission systems vary. The docs cover these agents separately.
+
+---
+
+### Phase 3 — Commit signing
+
+Check whether commit signing is already configured at any level (local or global):
+
+```bash
+git config --get commit.gpgsign
+```
+
+If the output is `true`, display `Commit signing: enabled` in the health summary area and skip to Phase 4.
+
+If not `true`, ask: **"Does this project require signed commits? (yes/no)"**
+
+Wait for response.
+
+- **no / skip** → continue to Phase 4.
+- **yes** → proceed with signing setup.
+
+**Verify a signing key exists:**
+
+```bash
+git config --get user.signingkey
+```
+
+**If a signing key is found**, show the proposed change and confirm:
+
+```
+I'll enable commit and tag signing for this repo:
+
+ git config commit.gpgsign true
+ git config tag.gpgsign true
+
+ Signing key:
+
+Proceed? (yes/no)
+```
+
+Wait for confirmation. Write only on yes. If no, skip to Phase 4.
+
+Continue to Phase 4.
+
+**If no signing key is found**, detect the signing format:
+
+```bash
+git config --get gpg.format
+```
+
+- If `ssh` → suggest: `git config user.signingkey ~/.ssh/id_ed25519.pub` (adjust path to the user's key). Ask the user for their SSH public key path.
+- If `gpg` or unset → suggest: run `gpg --list-secret-keys --keyid-format=long` to find a key ID. Ask the user for their GPG key ID.
+
+Once the user provides a key value, show the proposed changes and confirm:
+
+```
+I'll configure signing for this repo:
+
+ git config user.signingkey
+ git config commit.gpgsign true
+ git config tag.gpgsign true
+
+Proceed? (yes/no)
+```
+
+Wait for confirmation. Write only on yes. If no, skip to Phase 4.
+
+If the user has no signing key and doesn't know how to create one, point them to GitHub's signing key documentation and stop: "Set up a signing key first, then run `/setup` again to enable commit signing."
+
+---
+
+### Phase 4 — Label population
Run:
@@ -290,7 +387,7 @@ Wait for response.
After this step (or if labels were non-zero initially), run `gh label list --limit 100 --json name,color,description` again.
-If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 3; otherwise leave it unset so `/start` auto-detects."
+If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 5; otherwise leave it unset so `/start` auto-detects."
Display the results as a numbered list:
@@ -308,7 +405,7 @@ Wait for the user's response.
- **yes** → use all labels
- **numbers** (e.g. `1,3,5`) → use only those labels
-- **no / skip / anything else** → skip this phase, continue to Phase 3
+- **no / skip / anything else** → skip this phase, continue to Phase 5
Show the exact change before writing:
@@ -324,7 +421,7 @@ Wait for confirmation. Write only on yes.
---
-### Phase 3 — Optional config walkthrough (profile-aware)
+### Phase 5 — Optional config walkthrough (profile-aware)
First, infer the current profile using the same rules as Phase 1.
@@ -346,7 +443,7 @@ The walkthrough adapts based on profile. Walk through each applicable unset valu
---
-### Phase 4 — Team sharing
+### Phase 6 — Team sharing
After completing or skipping the config walkthrough, say:
@@ -364,11 +461,11 @@ Add a note: `/start` can be used to create well-formed GitHub issues without wri
## Hard rules
-- Only modify `.codecannon.yaml`. Do not touch any other file (except running `CodeCannon/sync.py`, which modifies `.claude/commands/` — permitted only with explicit user approval).
+- Only modify `.codecannon.yaml` and local git config (Phase 3 signing setup). Do not touch any other file (except running `CodeCannon/sync.py`, which modifies `.claude/commands/` — permitted only with explicit user approval).
- Do not run `sync.py` without explicit user permission.
- Do not create `.codecannon.yaml` without explicit user permission.
- Do not report a configuration problem unless confident the condition is genuinely broken. Prefer false negatives over false positives on all diagnostic checks.
- Never fetch more than 100 labels in a single command. `gh label list --limit 100` is the ceiling.
-- Do not skip any human gate in Phase 2 or Phase 3 — each write requires confirmation.
+- Do not skip any human gate in Phase 3, Phase 4, or Phase 5 — each write requires confirmation.
- If the user skips a config value, do not ask again. Move on.
-
+
diff --git a/.gemini/skills/start/SKILL.md b/.gemini/skills/start/SKILL.md
index 5008a87..f792167 100644
--- a/.gemini/skills/start/SKILL.md
+++ b/.gemini/skills/start/SKILL.md
@@ -25,6 +25,8 @@ Otherwise → go to **Case A: New work**.
> Skip this entirely if `$ARGUMENTS` triggered Case B.
+> **Execution order:** Resolve labels and milestones **now**, before entering Case A Step 1. If milestone auto-detection requires a user prompt (2+ open milestones), that prompt happens here — not later during issue creation. By the time you reach Step 2's human gate, all metadata must already be resolved so that Step 3 can proceed without re-prompting.
+
The argument string may contain optional inline flags after the description. Parse as follows:
1. **Identify flags** — scan for the first token that starts with `--label`, `-l`, `--milestone`, or `-m`. Everything before it is the **description**. Everything from the first flag onward is **flags**.
@@ -107,18 +109,13 @@ Read the relevant code. Propose a concrete implementation approach. Be specific
Say exactly:
-> **"Does this approach sound right? Type `go` to create a GitHub issue and branch, or share any questions/adjustments first."**
+> **"Does this approach sound right? Type `go` to create a GitHub issue and branch, or share any questions/adjustments first. To delegate part of the work to another agent, run `/delegate ` before typing `go`."**
Stop. Wait for the user to respond.
The friendly text question is required regardless of harness mode. If your harness is currently in a preview / plan / dry-run mode where you cannot passively stop and wait (and must instead invoke the harness's own approval mechanism), still include the text question in your response. The harness's approval UI mediates the wait, but it is not a substitute for the question itself. Users expect to see the consistent text language across all modes; do not silently swap it for the harness's UI.
-**Intent classification (not keyword matching):**
-
-- **Affirmative with no conditions** ("go", "yes", "looks good", "let's do it") → continue to Step 3.
-- **Affirmative with conditions** ("go, but first change X", "yes but can we also...") → treat as discussion. Address the conditions, revise the approach if needed, then re-ask.
-- **Questions or pushback** ("what about...", "I'm not sure about...", any adjustment) → discuss, revise approach, re-ask.
-- **Abandonment** ("never mind", "not now", "stop") → stop. Nothing to clean up.
+Proceed only on unconditional approval. If the user's response includes conditions, questions, or adjustments, treat it as discussion — address their input and re-ask. If the user abandons ("never mind", "stop"), stop — nothing to clean up.
### Step 3 — Create GitHub Issue
@@ -147,9 +144,9 @@ gh issue create \
> **IMPORTANT — never pass body content inline in the `gh` command.** Do not use `--body`, `--body-file -`, heredocs (`<` — is the only approach that works without prompts across Claude Code, Gemini CLI, Cursor, and Codex.
-Resolve labels and milestone using the resolution steps in the Parsing section above:
-- **Labels**: use the value from three-tier label resolution. If non-empty, add `--label ""` to the command. If empty (no flag, empty pool, creation not allowed), omit `--label` entirely.
-- **Milestone**: use the value from three-tier milestone resolution. If non-empty, add `--milestone ""` to the command. If empty (no flag, no config default, no open milestones), omit `--milestone` entirely.
+Use the labels and milestone you already resolved in the Parsing section (before Step 1). Do **not** re-run label or milestone resolution here — the values are final:
+- **Labels**: if non-empty, add `--label ""` to the command. If empty, omit `--label` entirely.
+- **Milestone**: if non-empty, add `--milestone ""` to the command. If empty, omit `--milestone` entirely.
**Body structure (required sections, in this order):**
@@ -174,12 +171,6 @@ Resolve labels and milestone using the resolution steps in the Parsing section a
All five sections are required. Write for a non-developer audience — no code, no file paths. Acceptance Criteria must be concrete and verifiable (not vague goals).
-**Complexity scale guidance (for agent use only — do not include the scale definition in the issue body):**
-- **trivial** — single obvious check (e.g. color change, label text, toggle visibility)
-- **moderate** — a few scenarios to verify, minor setup needed (e.g. form validation, a new UI component with a couple of states)
-- **significant** — many scenarios, data setup, or cross-feature impact (e.g. multi-step workflow, permission changes across roles)
-- **extensive** — complex data flows, integration testing, or edge cases spanning multiple areas (e.g. data import/export mapping, API contract changes consumed by multiple clients)
-
**Title rules:**
- ✅ `Fix 'Contact Us' footer link pointing to 404 instead of /contact-us`
- ❌ `Fix broken link`
@@ -257,17 +248,43 @@ Tell the user:
- What was previously done (from agent notes if present)
- What appears to remain
-Ask: **"Does this match your understanding? Type `go` to start coding, or share any questions/adjustments first."**
+Ask: **"Does this match your understanding? Type `go` to start coding, or share any questions/adjustments first. To delegate part of the work to another agent, run `/delegate ` before typing `go`."**
+
+Proceed only on unconditional approval. If the user's response includes conditions, questions, or adjustments, treat it as discussion — address their input and re-ask. If the user wants a fresh start, restart as Case A. If the user abandons, stop — nothing to clean up.
+
+### Step 3 — Investigation findings (conditional)
+
+If the investigation in Steps 1–2 revealed anything that isn't already stated or implied by the issue body — a root cause correction, a related side-effect, a project-wide gotcha — present the findings. If the investigation simply confirmed the ticket, skip this step silently and proceed to Step 4.
+
+Create a temp directory for this invocation:
+
+```bash
+mkdir -p /tmp/CodeCannon && mktemp -d /tmp/CodeCannon/XXXXXX
+```
-**Intent classification (not keyword matching):**
+Present numbered findings:
-- **Affirmative with no conditions** ("go", "yes", "continue", "looks right") → proceed to Step 3.
-- **Affirmative with conditions** ("go, but first...", "yes but let's also...") → treat as discussion. Address the conditions, then re-ask.
-- **Questions or discussion** ("what about...", "can we change...", any adjustment) → discuss, then re-ask.
-- **Fresh start** ("open a new one", "start fresh", "new ticket") → restart as Case A with a new description.
-- **Abandonment** ("never mind", "not now", "stop") → stop. Nothing to clean up.
+> The investigation surfaced the following:
+>
+> 1.
+> 2.
+>
+> **post** as a comment to the ticket, or **skip** to continue.
-### Step 3 — Check out branch
+- `post` → use your file-writing tool (not Bash) to create `/investigation_comment.md`:
+ ```markdown
+ ## Investigation Findings
+
+ -
+ -
+ ```
+ Then post it:
+ ```bash
+ gh issue comment --body-file /investigation_comment.md
+ ```
+- `skip` → proceed silently.
+
+### Step 4 — Check out branch
Ensure the base branch is up-to-date before branching:
@@ -289,13 +306,7 @@ Verify:
git branch --show-current
```
-Post a resumption comment:
-
-```bash
-gh issue comment --body "Resuming work. "
-```
-
-### Step 4 — Write the code
+### Step 5 — Write the code
Continue from where work left off. Do NOT commit.
@@ -313,4 +324,4 @@ When done, say: **"The code is ready for review. Please run `make dev` and test
- The issue is assigned to `@me` at creation. If you are creating a ticket on someone else's behalf, remove the assignee after creation with `gh issue edit --remove-assignee @me`.
- Apply resolved labels and milestone to every new issue. Label resolution order: per-invocation flag → pool selection from `bug, documentation, enhancement, chore` → omit `--label` entirely. Never apply a label outside `bug, documentation, enhancement, chore`.
- Milestone resolution order: per-invocation flag → auto-detected from GitHub open milestones. Never prompt for a milestone more than once per invocation.
-
+
diff --git a/.gemini/skills/submit-for-review/SKILL.md b/.gemini/skills/submit-for-review/SKILL.md
index 6503e61..b1e258f 100644
--- a/.gemini/skills/submit-for-review/SKILL.md
+++ b/.gemini/skills/submit-for-review/SKILL.md
@@ -32,22 +32,16 @@ If the current branch matches any of the above, **abort immediately** and say:
## Step 2 — Type-check gate
-First, move to the repository root so the command resolves against the correct Makefile / project config:
+First, find the repository root:
```
git rev-parse --show-toplevel
```
-Then `cd` into the path returned above:
+Then `cd` to the returned path and verify the make target exists. Extract the target name from `make check` (e.g. `make check` → `check`) and run:
```
-cd
-```
-
-Then verify the make target exists before running it. Extract the target name from `make check` (e.g. `make check` → `check`) and run:
-
-```
-make -n 2>/dev/null
+cd && make -n 2>/dev/null
```
If `make -n` exits non-zero, **stop** and say:
@@ -194,13 +188,7 @@ Wait for the review to complete and report its verdict.
## Step 8 — Act on verdict
-Before merging, verify the merge target exists. Move to the repo root, extract the target name from `make merge` (e.g. `make merge` → `merge`), and run:
-
-```
-git rev-parse --show-toplevel
-```
-
-Then `cd` into the path returned above and check the target:
+Before merging, verify the merge target exists. Find the repo root with `git rev-parse --show-toplevel`, then extract the target name from `make merge` (e.g. `make merge` → `merge`) and run:
```
cd && make -n 2>/dev/null
@@ -291,11 +279,7 @@ Then post it (do NOT use `--body` or heredocs):
gh issue comment --body-file /resolution_comment.md
```
-**Resolution writing rules:**
-- Write for PMs and BAs, not developers. Describe the *outcome*, not the implementation.
-- Reference the original problem from the issue body so the resolution reads as a direct answer to it.
-- Keep it to 1-3 sentences. If one sentence covers it, don't pad.
-- Use the unqualified `#N` form for the PR reference.
+Use the unqualified `#N` form for the PR reference (not `owner/repo#N`).
Report success based on mode:
"PR merged. Issues stay open until testing confirms the fix. Run `make deploy-preview` when ready to deploy to preview."
@@ -366,4 +350,4 @@ If a single `gh issue create` call fails, report the failure for that finding an
- `/submit-for-review` merges only to `dev` — never directly to `main`.
- If `make merge` fails for any reason, report it and stop — do not attempt workarounds.
- The follow-up issue offer in Step 9 runs only after a successful merge and only when the review produced non-blocking findings. Never prompt the user for follow-ups when the review blocked the merge — those findings should be fixed, not ticketed.
-
+
diff --git a/.github/assets/readme-header.jpg b/.github/assets/readme-header.jpg
new file mode 100644
index 0000000..849db61
Binary files /dev/null and b/.github/assets/readme-header.jpg differ
diff --git a/.github/assets/readme-header.png b/.github/assets/readme-header.png
deleted file mode 100644
index f3a2de4..0000000
Binary files a/.github/assets/readme-header.png and /dev/null differ
diff --git a/.github/assets/readme-inline-agents-working-with-humans.jpg b/.github/assets/readme-inline-agents-working-with-humans.jpg
new file mode 100644
index 0000000..775e023
Binary files /dev/null and b/.github/assets/readme-inline-agents-working-with-humans.jpg differ
diff --git a/.github/assets/readme-inline-agents-working-with-humans.png b/.github/assets/readme-inline-agents-working-with-humans.png
deleted file mode 100644
index fb39478..0000000
Binary files a/.github/assets/readme-inline-agents-working-with-humans.png and /dev/null differ
diff --git a/README.md b/README.md
index 04647e1..7c65487 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
-
+
## The problem
@@ -54,7 +54,7 @@ A complete development workflow in five commands:
Plus `/qa` for structured QA workflows and `/setup` for guided onboarding.
-
+
## Philosophy
diff --git a/VERSION b/VERSION
index 4b9fcbe..a918a2a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.5.1
+0.6.0
diff --git a/config.schema.yaml b/config.schema.yaml
index 7f20a21..00182e1 100644
--- a/config.schema.yaml
+++ b/config.schema.yaml
@@ -21,7 +21,7 @@ placeholders:
a different name (e.g. "master", "prod"). All release promotion targets this branch.
default: "main"
category: branches
- used_in: [submit-for-review, deploy]
+ used_in: [submit-for-review, deploy, delegate]
BRANCH_DEV:
description: >
@@ -29,7 +29,7 @@ placeholders:
Set to enable two-branch mode (feature → BRANCH_DEV → BRANCH_PROD). Common values: "development", "dev", "staging".
default: ""
category: branches
- used_in: [submit-for-review, deploy, qa, review-agent]
+ used_in: [submit-for-review, deploy, qa, review-agent, delegate]
BRANCH_TEST:
description: >
diff --git a/docs/index.md b/docs/index.md
index 74b590c..4565208 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -104,6 +104,54 @@ If your project still uses the previous repo URL or folder name:
2. Rename `.agentgate.yaml` to `.codecannon.yaml`.
3. Run `CodeCannon/sync.py --force` once if needed so generated file headers match the new provenance marker.
+## Agent tips and tweaks
+
+Code Cannon skills are agent-agnostic, but each agent has its own quirks. This section collects per-agent configuration tips that smooth out the experience.
+
+### Claude Code
+
+**Reduce approval prompts.** Claude Code prompts for permission on unfamiliar shell commands. Pre-approve the commands Code Cannon uses by adding a `permissions` block to `.claude/settings.json` (project-level, committed) or `.claude/settings.local.json` (personal, git-ignored):
+
+```json
+{
+ "permissions": {
+ "defaultMode": "acceptEdits",
+ "allow": [
+ "Bash(cd:*)",
+ "Bash(git:*)",
+ "Bash(gh:*)",
+ "Bash(make:*)",
+ "Bash(python3:*)",
+ "Bash(./sync.py:*)",
+ "Bash(mkdir:*)",
+ "Bash(mktemp:*)"
+ ],
+ "deny": [
+ "Bash(git push --force)",
+ "Bash(git push --force:*)",
+ "Bash(git reset --hard)",
+ "Bash(git reset --hard:*)",
+ "Bash(rm -rf:*)",
+ "Bash(sudo:*)"
+ ]
+ }
+}
+```
+
+`defaultMode: "acceptEdits"` auto-approves file edits and common filesystem ops. `allow` rules pre-approve matching bash commands (wildcards supported). `deny` rules always win — dangerous operations still prompt. Adjust the `allow` list to match your project's tooling.
+
+### Cursor
+
+Cursor does not prompt for shell commands by default, so no permission configuration is needed. Skills work out of the box.
+
+### Codex
+
+Configure via the `codex` CLI's `--full-auto` flag or sandbox settings. Codex runs in a sandboxed environment, so approval prompts are less common but network access may need to be explicitly enabled for `gh` commands.
+
+### Gemini CLI
+
+Configure via `.gemini/settings.json` or the `--auto-approve` flag. Consult Gemini CLI documentation for the latest permission options.
+
## Further reading
- [Branching models](branching.md) — trunk, two-branch, and three-branch workflows explained
diff --git a/docs/skills/setup.md b/docs/skills/setup.md
index 681b8d1..557b180 100644
--- a/docs/skills/setup.md
+++ b/docs/skills/setup.md
@@ -10,7 +10,7 @@ First-run onboarding and configuration walkthrough.
1. **You're in the Code Cannon repo itself** — explains how Code Cannon works and offers to help you add it to your project.
2. **Partial setup** — the submodule exists but something is missing (sync.py not initialized, `gh` not installed, config file missing, etc.). Walks through fixes one at a time.
-3. **Fully configured** — runs a health check, offers to populate labels from your GitHub repo, and walks through optional config values.
+3. **Fully configured** — runs a health check, configures commit signing if needed, offers to populate labels from your GitHub repo, and walks through optional config values.
## Usage
@@ -47,6 +47,14 @@ When creating `.codecannon.yaml` for the first time, `/setup` asks you to pick a
Profiles are starting points — every setting is independently configurable afterward.
+## Commit signing
+
+After the health summary, `/setup` checks whether `commit.gpgsign` is already enabled in your git config (local or global). If not, it asks whether your project requires signed commits.
+
+If yes, it verifies a signing key is configured and enables `commit.gpgsign` and `tag.gpgsign` at the repo level. If no signing key exists, it detects the signing format (SSH or GPG) and guides you through providing one.
+
+This is a git-level configuration — no changes to `.codecannon.yaml` or skill templates are needed. All skills that create commits or tags benefit automatically.
+
## Label population
Once fully configured, `/setup` fetches your repo's existing GitHub labels and offers to write them to `TICKET_LABELS` in `.codecannon.yaml`. This populates the label pool that `/start` uses when creating issues.
diff --git a/permissions.yaml b/permissions.yaml
new file mode 100644
index 0000000..9ac731c
--- /dev/null
+++ b/permissions.yaml
@@ -0,0 +1,21 @@
+# CodeCannon permissions reference
+# Hand-maintained list of shell command prefixes used across all skills.
+# The /setup skill reads this file to check whether the agent's permission
+# config covers these commands and suggest additions if not.
+#
+# When adding new shell commands to skills, add the prefix here too.
+# sync.py --validate checks that every prefix in skill code blocks appears
+# in this file.
+
+commands:
+ - cd
+ - cp
+ - gh
+ - git
+ - gpg
+ - grep
+ - make
+ - mkdir
+ - mktemp
+ - test
+ - which
diff --git a/skills/delegate.md b/skills/delegate.md
new file mode 100644
index 0000000..51bfbe6
--- /dev/null
+++ b/skills/delegate.md
@@ -0,0 +1,104 @@
+---
+skill: delegate
+type: skill
+description: "Code Cannon: Generate a scoped prompt for delegating work to another agent"
+args: "description of the task to delegate"
+---
+
+## What `/delegate` does
+
+`/delegate` generates a ready-to-paste prompt that gives another AI agent everything it needs to contribute code on the current branch. Run it during the planning gate of `/start` (Step 2), before saying `go`, to carve off part of the work for another agent.
+
+Each `/delegate` invocation also tells the lead agent to skip that work when it proceeds.
+
+---
+
+## Prerequisites
+
+`/delegate` relies on context already loaded by `/start`. If you are not in the middle of a `/start` planning gate (Case A Step 2 or Case B Step 2), say:
+
+> "`/delegate` is meant to be run during the planning step of `/start`, before you type `go`. Run `/start` first to load the issue context, then use `/delegate` to carve off tasks."
+
+Stop.
+
+If `$ARGUMENTS` is empty, ask: **"What task should the prompt describe?"** and wait for a response.
+
+---
+
+## Step 1 — Gather branch and diff context
+
+```bash
+git branch --show-current
+```
+
+```bash
+{{#if BRANCH_DEV}}
+git diff --name-only {{BRANCH_DEV}}...HEAD 2>/dev/null || git diff --name-only {{BRANCH_PROD}}...HEAD
+{{/if}}
+{{#if !BRANCH_DEV}}
+git diff --name-only {{BRANCH_PROD}}...HEAD
+{{/if}}
+```
+
+---
+
+## Step 2 — Build the prompt
+
+Compose the following as a fenced markdown block (` ```markdown `) so the operator can copy it.
+
+Use the issue title, body, and agent implementation notes from the current `/start` session — do not re-fetch them.
+
+```markdown
+## Your task
+
+<$ARGUMENTS — the operator's description of the delegated work>
+
+## Branch
+
+You are on branch ``. Stay on this branch.
+
+## Context
+
+**Issue #: **
+
+
+
+
+
+## What's already been done on this branch
+
+
+
+## Rules
+
+- **Do not commit.** Leave all changes unstaged. The lead developer will handle commits.
+- **Be surgical.** Only modify what is necessary for your task. Do not refactor adjacent code, reorganize imports, or "improve" files outside the scope of your task.
+- **Do not start until the branch exists on the remote.** The lead developer will create and push the branch. Wait until `git checkout && git pull origin ` succeeds before beginning work.
+```
+
+---
+
+## Step 3 — Present to operator and update lead scope
+
+Print the generated prompt preceded by:
+
+> Here's a delegation prompt for: **$ARGUMENTS**
+>
+> Review and edit as needed, then paste into your agent of choice after the lead agent has created and pushed the branch.
+
+Then print the fenced prompt block.
+
+After presenting, record internally that `$ARGUMENTS` has been delegated. When the operator says `go` and the lead agent proceeds to write code, it must skip the delegated work. If multiple `/delegate` invocations occur before `go`, accumulate all of them.
+
+After presenting, say:
+
+> Noted — the lead agent will skip this part when you say `go`. Run `/delegate ` to delegate more, or type `go` to proceed.
+
+---
+
+## Hard rules
+
+- Never commit, push, or modify any files.
+- Never post to GitHub (no comments, labels, or issue updates).
+- Output only — the skill produces text for the operator to copy.
+- Do not suggest or name a specific agent to paste into — that is the operator's choice.
diff --git a/skills/deploy.md b/skills/deploy.md
index ffdcd4b..b584b13 100644
--- a/skills/deploy.md
+++ b/skills/deploy.md
@@ -148,7 +148,19 @@ If the user chose a bump level, map their response to a command:
| "major" | `{{BUMP_MAJOR_CMD}}` |
| A specific version e.g. "2.4.5" | `{{SET_VERSION_CMD}} 2.4.5` |
-These commands update the version manifest, create a git commit, and create a git tag. Do not create commits or tags manually.
+These commands update the version manifest, create a git commit, and create a git tag.
+
+After the bump command runs, verify the tag was actually created:
+
+```bash
+git tag -l "v"
+```
+
+If the tag is missing (some git configs like `tag.forceSignAnnotated = true` reject lightweight tags silently), create an annotated tag as a fallback:
+
+```bash
+git tag -a "v" -m "v"
+```
Push the version bump:
```bash
diff --git a/skills/setup.md b/skills/setup.md
index f6b63b4..e6fded8 100644
--- a/skills/setup.md
+++ b/skills/setup.md
@@ -256,7 +256,104 @@ A value counts as "set" if it is present, uncommented, and non-empty in `.codeca
---
-### Phase 2 — Label population
+### Phase 2 — Permission audit
+
+Check whether the agent's permission configuration covers the shell commands Code Cannon skills use. Read `CodeCannon/permissions.yaml` to get the list of required command prefixes.
+
+**Claude Code:** Read `.claude/settings.local.json` (if it exists) and `.claude/settings.json` (if it exists). Collect all `Bash(...)` entries from the `permissions.allow` arrays in both files. For each command prefix in `permissions.yaml`, check whether an allow rule covers it (e.g. `Bash(git:*)` or `Bash(git *)` covers the `git` prefix).
+
+If all prefixes are covered, display `Agent permissions: all skill commands pre-approved` and continue to Phase 3.
+
+If any prefixes are missing, show:
+
+```
+Agent permissions: some skill commands may prompt for approval.
+
+ Missing allow rules:
+ - Bash(cd:*)
+ - Bash(make:*)
+ ...
+
+ To pre-approve these, add them to .claude/settings.local.json (git-ignored)
+ or .claude/settings.json (shared with team). See docs/index.md for a full example.
+
+ This is optional — you can approve commands individually when prompted instead.
+```
+
+Do not modify any settings file. This is advisory only.
+
+**Other agents (Cursor, Codex, Gemini):** Skip this phase silently — Cursor doesn't prompt, and Codex/Gemini permission systems vary. The docs cover these agents separately.
+
+---
+
+### Phase 3 — Commit signing
+
+Check whether commit signing is already configured at any level (local or global):
+
+```bash
+git config --get commit.gpgsign
+```
+
+If the output is `true`, display `Commit signing: enabled` in the health summary area and skip to Phase 4.
+
+If not `true`, ask: **"Does this project require signed commits? (yes/no)"**
+
+Wait for response.
+
+- **no / skip** → continue to Phase 4.
+- **yes** → proceed with signing setup.
+
+**Verify a signing key exists:**
+
+```bash
+git config --get user.signingkey
+```
+
+**If a signing key is found**, show the proposed change and confirm:
+
+```
+I'll enable commit and tag signing for this repo:
+
+ git config commit.gpgsign true
+ git config tag.gpgsign true
+
+ Signing key:
+
+Proceed? (yes/no)
+```
+
+Wait for confirmation. Write only on yes. If no, skip to Phase 4.
+
+Continue to Phase 4.
+
+**If no signing key is found**, detect the signing format:
+
+```bash
+git config --get gpg.format
+```
+
+- If `ssh` → suggest: `git config user.signingkey ~/.ssh/id_ed25519.pub` (adjust path to the user's key). Ask the user for their SSH public key path.
+- If `gpg` or unset → suggest: run `gpg --list-secret-keys --keyid-format=long` to find a key ID. Ask the user for their GPG key ID.
+
+Once the user provides a key value, show the proposed changes and confirm:
+
+```
+I'll configure signing for this repo:
+
+ git config user.signingkey
+ git config commit.gpgsign true
+ git config tag.gpgsign true
+
+Proceed? (yes/no)
+```
+
+Wait for confirmation. Write only on yes. If no, skip to Phase 4.
+
+If the user has no signing key and doesn't know how to create one, point them to GitHub's signing key documentation and stop: "Set up a signing key first, then run `/setup` again to enable commit signing."
+
+---
+
+### Phase 4 — Label population
Run:
@@ -288,7 +385,7 @@ Wait for response.
After this step (or if labels were non-zero initially), run `gh label list --limit 100 --json name,color,description` again.
-If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 3; otherwise leave it unset so `/start` auto-detects."
+If `TICKET_LABELS` is unset or fewer than 5 labels exist, add a note: "`/start` works best with a clear issue-label pool (`TICKET_LABELS`), and `/qa` needs explicit QA lifecycle labels (`ready-for-qa`, `qa-passed`, `qa-failed`). Consider a lightweight priority scheme (e.g. `priority:high`, `priority:medium`, `priority:low`) if the team needs triage support. If the team runs planned iterations, set `DEFAULT_MILESTONE` in Phase 5; otherwise leave it unset so `/start` auto-detects."
Display the results as a numbered list:
@@ -306,7 +403,7 @@ Wait for the user's response.
- **yes** → use all labels
- **numbers** (e.g. `1,3,5`) → use only those labels
-- **no / skip / anything else** → skip this phase, continue to Phase 3
+- **no / skip / anything else** → skip this phase, continue to Phase 5
Show the exact change before writing:
@@ -322,7 +419,7 @@ Wait for confirmation. Write only on yes.
---
-### Phase 3 — Optional config walkthrough (profile-aware)
+### Phase 5 — Optional config walkthrough (profile-aware)
First, infer the current profile using the same rules as Phase 1.
@@ -344,7 +441,7 @@ The walkthrough adapts based on profile. Walk through each applicable unset valu
---
-### Phase 4 — Team sharing
+### Phase 6 — Team sharing
After completing or skipping the config walkthrough, say:
@@ -362,10 +459,10 @@ Add a note: `/start` can be used to create well-formed GitHub issues without wri
## Hard rules
-- Only modify `.codecannon.yaml`. Do not touch any other file (except running `CodeCannon/sync.py`, which modifies `.claude/commands/` — permitted only with explicit user approval).
+- Only modify `.codecannon.yaml` and local git config (Phase 3 signing setup). Do not touch any other file (except running `CodeCannon/sync.py`, which modifies `.claude/commands/` — permitted only with explicit user approval).
- Do not run `sync.py` without explicit user permission.
- Do not create `.codecannon.yaml` without explicit user permission.
- Do not report a configuration problem unless confident the condition is genuinely broken. Prefer false negatives over false positives on all diagnostic checks.
- Never fetch more than 100 labels in a single command. `gh label list --limit 100` is the ceiling.
-- Do not skip any human gate in Phase 2 or Phase 3 — each write requires confirmation.
+- Do not skip any human gate in Phase 3, Phase 4, or Phase 5 — each write requires confirmation.
- If the user skips a config value, do not ask again. Move on.
diff --git a/skills/start.md b/skills/start.md
index d2d15a1..5fe8fcd 100644
--- a/skills/start.md
+++ b/skills/start.md
@@ -23,6 +23,8 @@ Otherwise → go to **Case A: New work**.
> Skip this entirely if `$ARGUMENTS` triggered Case B.
+> **Execution order:** Resolve labels and milestones **now**, before entering Case A Step 1. If milestone auto-detection requires a user prompt (2+ open milestones), that prompt happens here — not later during issue creation. By the time you reach Step 2's human gate, all metadata must already be resolved so that Step 3 can proceed without re-prompting.
+
The argument string may contain optional inline flags after the description. Parse as follows:
1. **Identify flags** — scan for the first token that starts with `--label`, `-l`, `--milestone`, or `-m`. Everything before it is the **description**. Everything from the first flag onward is **flags**.
@@ -150,18 +152,13 @@ Read the relevant code. Propose a concrete implementation approach. Be specific
Say exactly:
-> **"Does this approach sound right? Type `go` to create a GitHub issue and branch, or share any questions/adjustments first."**
+> **"Does this approach sound right? Type `go` to create a GitHub issue and branch, or share any questions/adjustments first. To delegate part of the work to another agent, run `/delegate ` before typing `go`."**
Stop. Wait for the user to respond.
The friendly text question is required regardless of harness mode. If your harness is currently in a preview / plan / dry-run mode where you cannot passively stop and wait (and must instead invoke the harness's own approval mechanism), still include the text question in your response. The harness's approval UI mediates the wait, but it is not a substitute for the question itself. Users expect to see the consistent text language across all modes; do not silently swap it for the harness's UI.
-**Intent classification (not keyword matching):**
-
-- **Affirmative with no conditions** ("go", "yes", "looks good", "let's do it") → continue to Step 3.
-- **Affirmative with conditions** ("go, but first change X", "yes but can we also...") → treat as discussion. Address the conditions, revise the approach if needed, then re-ask.
-- **Questions or pushback** ("what about...", "I'm not sure about...", any adjustment) → discuss, revise approach, re-ask.
-- **Abandonment** ("never mind", "not now", "stop") → stop. Nothing to clean up.
+Proceed only on unconditional approval. If the user's response includes conditions, questions, or adjustments, treat it as discussion — address their input and re-ask. If the user abandons ("never mind", "stop"), stop — nothing to clean up.
### Step 3 — Create GitHub Issue
@@ -190,9 +187,9 @@ gh issue create \
> **IMPORTANT — never pass body content inline in the `gh` command.** Do not use `--body`, `--body-file -`, heredocs (`<` — is the only approach that works without prompts across Claude Code, Gemini CLI, Cursor, and Codex.
-Resolve labels and milestone using the resolution steps in the Parsing section above:
-- **Labels**: use the value from three-tier label resolution. If non-empty, add `--label ""` to the command. If empty (no flag, empty pool, creation not allowed), omit `--label` entirely.
-- **Milestone**: use the value from three-tier milestone resolution. If non-empty, add `--milestone ""` to the command. If empty (no flag, no config default, no open milestones), omit `--milestone` entirely.
+Use the labels and milestone you already resolved in the Parsing section (before Step 1). Do **not** re-run label or milestone resolution here — the values are final:
+- **Labels**: if non-empty, add `--label ""` to the command. If empty, omit `--label` entirely.
+- **Milestone**: if non-empty, add `--milestone ""` to the command. If empty, omit `--milestone` entirely.
**Body structure (required sections, in this order):**
@@ -217,12 +214,6 @@ Resolve labels and milestone using the resolution steps in the Parsing section a
All five sections are required. Write for a non-developer audience — no code, no file paths. Acceptance Criteria must be concrete and verifiable (not vague goals).
-**Complexity scale guidance (for agent use only — do not include the scale definition in the issue body):**
-- **trivial** — single obvious check (e.g. color change, label text, toggle visibility)
-- **moderate** — a few scenarios to verify, minor setup needed (e.g. form validation, a new UI component with a couple of states)
-- **significant** — many scenarios, data setup, or cross-feature impact (e.g. multi-step workflow, permission changes across roles)
-- **extensive** — complex data flows, integration testing, or edge cases spanning multiple areas (e.g. data import/export mapping, API contract changes consumed by multiple clients)
-
**Title rules:**
- ✅ `Fix 'Contact Us' footer link pointing to 404 instead of /contact-us`
- ❌ `Fix broken link`
@@ -316,17 +307,43 @@ Tell the user:
- What was previously done (from agent notes if present)
- What appears to remain
-Ask: **"Does this match your understanding? Type `go` to start coding, or share any questions/adjustments first."**
+Ask: **"Does this match your understanding? Type `go` to start coding, or share any questions/adjustments first. To delegate part of the work to another agent, run `/delegate ` before typing `go`."**
+
+Proceed only on unconditional approval. If the user's response includes conditions, questions, or adjustments, treat it as discussion — address their input and re-ask. If the user wants a fresh start, restart as Case A. If the user abandons, stop — nothing to clean up.
+
+### Step 3 — Investigation findings (conditional)
+
+If the investigation in Steps 1–2 revealed anything that isn't already stated or implied by the issue body — a root cause correction, a related side-effect, a project-wide gotcha — present the findings. If the investigation simply confirmed the ticket, skip this step silently and proceed to Step 4.
+
+Create a temp directory for this invocation:
+
+```bash
+mkdir -p /tmp/CodeCannon && mktemp -d /tmp/CodeCannon/XXXXXX
+```
-**Intent classification (not keyword matching):**
+Present numbered findings:
-- **Affirmative with no conditions** ("go", "yes", "continue", "looks right") → proceed to Step 3.
-- **Affirmative with conditions** ("go, but first...", "yes but let's also...") → treat as discussion. Address the conditions, then re-ask.
-- **Questions or discussion** ("what about...", "can we change...", any adjustment) → discuss, then re-ask.
-- **Fresh start** ("open a new one", "start fresh", "new ticket") → restart as Case A with a new description.
-- **Abandonment** ("never mind", "not now", "stop") → stop. Nothing to clean up.
+> The investigation surfaced the following:
+>
+> 1.
+> 2.
+>
+> **post** as a comment to the ticket, or **skip** to continue.
-### Step 3 — Check out branch
+- `post` → use your file-writing tool (not Bash) to create `/investigation_comment.md`:
+ ```markdown
+ ## Investigation Findings
+
+ -
+ -
+ ```
+ Then post it:
+ ```bash
+ gh issue comment --body-file /investigation_comment.md
+ ```
+- `skip` → proceed silently.
+
+### Step 4 — Check out branch
Ensure the base branch is up-to-date before branching:
@@ -364,13 +381,7 @@ Verify:
git branch --show-current
```
-Post a resumption comment:
-
-```bash
-gh issue comment --body "Resuming work. "
-```
-
-### Step 4 — Write the code
+### Step 5 — Write the code
Continue from where work left off. Do NOT commit.
diff --git a/skills/submit-for-review.md b/skills/submit-for-review.md
index a2a2564..7681ef8 100644
--- a/skills/submit-for-review.md
+++ b/skills/submit-for-review.md
@@ -35,22 +35,16 @@ If the current branch matches any of the above, **abort immediately** and say:
## Step 2 — Type-check gate
-First, move to the repository root so the command resolves against the correct Makefile / project config:
+First, find the repository root:
```
git rev-parse --show-toplevel
```
-Then `cd` into the path returned above:
+Then `cd` to the returned path and verify the make target exists. Extract the target name from `{{CHECK_CMD}}` (e.g. `make check` → `check`) and run:
```
-cd
-```
-
-Then verify the make target exists before running it. Extract the target name from `{{CHECK_CMD}}` (e.g. `make check` → `check`) and run:
-
-```
-make -n 2>/dev/null
+cd && make -n 2>/dev/null
```
If `make -n` exits non-zero, **stop** and say:
@@ -213,13 +207,7 @@ Wait for the review to complete and report its verdict.
## Step 8 — Act on verdict
-Before merging, verify the merge target exists. Move to the repo root, extract the target name from `{{MERGE_CMD}}` (e.g. `make merge` → `merge`), and run:
-
-```
-git rev-parse --show-toplevel
-```
-
-Then `cd` into the path returned above and check the target:
+Before merging, verify the merge target exists. Find the repo root with `git rev-parse --show-toplevel`, then extract the target name from `{{MERGE_CMD}}` (e.g. `make merge` → `merge`) and run:
```
cd && make -n 2>/dev/null
@@ -321,11 +309,7 @@ Then post it (do NOT use `--body` or heredocs):
gh issue comment --body-file /resolution_comment.md
```
-**Resolution writing rules:**
-- Write for PMs and BAs, not developers. Describe the *outcome*, not the implementation.
-- Reference the original problem from the issue body so the resolution reads as a direct answer to it.
-- Keep it to 1-3 sentences. If one sentence covers it, don't pad.
-- Use the unqualified `#N` form for the PR reference.
+Use the unqualified `#N` form for the PR reference (not `owner/repo#N`).
Report success based on mode:
{{#if !BRANCH_DEV}}
diff --git a/sync.py b/sync.py
index 7f35d11..b9fed67 100755
--- a/sync.py
+++ b/sync.py
@@ -369,6 +369,44 @@ def validate_placeholders(skill_files, project_config):
return errors
+def validate_permissions(skill_files):
+ """Check that command prefixes in skill code blocks are listed in permissions.yaml."""
+ perms_path = CODECANNON_DIR / 'permissions.yaml'
+ if not perms_path.exists():
+ return [" permissions.yaml not found"]
+
+ perms = parse_yaml_simple(perms_path.read_text())
+ allowed = set(perms.get('commands', []))
+ if not allowed:
+ return [" permissions.yaml has no commands listed"]
+
+ code_block_re = re.compile(r'```bash\n(.*?)```', re.DOTALL)
+ errors = []
+ seen = set()
+
+ for skill_path in skill_files:
+ text = skill_path.read_text()
+ for block in code_block_re.findall(text):
+ for line in block.strip().splitlines():
+ line = line.strip()
+ if not line or line.startswith('#') or line.startswith('<'):
+ continue
+ token = line.split()[0]
+ # Only check tokens that look like shell commands
+ if not (token[0].islower() or token.startswith('./')):
+ continue
+ # Normalize ./CodeCannon/sync.py → CodeCannon/sync.py
+ if token.startswith('./'):
+ token = token[2:]
+ # For path-like commands (CodeCannon/sync.py), check the full path
+ # For simple commands (git, make), check the prefix
+ if token not in allowed and token not in seen:
+ seen.add(token)
+ errors.append(f" {skill_path.name}: command '{token}' not in permissions.yaml")
+
+ return errors
+
+
def self_update_or_exit():
"""Update the CodeCannon checkout via git pull --ff-only. Only runs on main."""
try:
@@ -450,16 +488,29 @@ def main():
else:
skill_files = all_skill_files
- # --validate: pre-flight placeholder check, no writes
+ # --validate: pre-flight placeholder check + permissions check, no writes
if args.validate:
+ failed = False
errors = validate_placeholders(skill_files, project_config)
if errors:
- print("Validation failed — undefined placeholders:\n")
+ print("Placeholder validation failed — undefined placeholders:\n")
for e in errors:
print(e)
- sys.exit(1)
+ failed = True
+ else:
+ print("Placeholder validation passed — all placeholders are defined.")
+
+ perm_errors = validate_permissions(all_skill_files)
+ if perm_errors:
+ print("\nPermission validation failed — commands not in permissions.yaml:\n")
+ for e in perm_errors:
+ print(e)
+ failed = True
else:
- print("Validation passed — all placeholders are defined.")
+ print("Permission validation passed — all command prefixes are listed.")
+
+ if failed:
+ sys.exit(1)
return
if args.dry_run: