Run one development cycle
Goal: move docs/v1-dev-plan.md forward by exactly one task, in exactly one PR, leaving
main green and the plan file an accurate picture of where the work stands. This is the executor
half of the pair — /sg-plan writes the plan, this skill burns it down.
Wire it up with:
/loop 6h /sg-develop
Invocation forms: /sg-develop (next ready task) · /sg-develop M3-04 (that task specifically).
Guardrails
All sg-maintain global guardrails apply — one activity per cycle, one PR per cycle, never
self-merge a code PR, never execute scanned or generated attack content, preflight before every PR,
start from fresh main. Plus:
- One task per cycle. A deep plan is answered with more cycles, never a bigger cycle. If the
task turns out to be two tasks, implement the first and file the second as a new plan row.
- Never plan here. If the milestone has no expanded cards, stop and hand off to
/sg-plan
(§2). Do not invent tasks not in the plan; do not reorder milestones.
- Contracts are frozen.
scan/sign/verify CLI surface, JSON shape, and exit codes are
additive-only until v1.0. A breaking change needs a documented migration and the owner's call —
raise it in the PR rather than shipping it.
- Label. This skill's PRs extend guardrail 4a's type-label set with
develop.
Create it once: gh label create develop -c 0e8a16 -d "roadmap implementation" --force.
0. Sync and reconcile the plan
git checkout main && git pull --ff-only
Read docs/v1-dev-plan.md. Before selecting anything, reconcile status with reality — this is
what makes the tracker survive across sessions:
- For every row marked
in-progress with a PR link: gh pr view <n> --json state,mergedAt.
Merged → set the row to done. Closed unmerged → back to todo with a change-log line.
- For every
in-progress row without a PR: check for an existing branch
(git branch -a | grep <TASK-ID>) — an interrupted cycle. Resume it (§3) rather than starting
something new.
- If a row claims
todo but the functionality demonstrably already exists in the repo, mark it
done with a change-log line instead of re-implementing it.
Reconciliation edits ride along in this cycle's PR. If reconciliation is the only thing that
changed and no task is startable, ship it as a docs-only PR and stop.
1. Load cycle state
State lives in .claude/development/ (per-machine, git-ignored — create on first run):
The plan file is the source of truth for task status; state.json only records cadence.
2. Select exactly one task
In order:
- A task the caller named explicitly, if it is
todo or resumable.
- A resumable
in-progress task from §0.2.
- Otherwise the first
todo task, in plan order (milestone order, then table order) whose
Deps are all done.
Skip blocked, owner, and dropped rows — never work them, but report them at the end so
the owner sees what is waiting on them.
Hand off instead of guessing when: the current milestone is titles-only, or every remaining
todo is dependency-blocked, or the next task's card has no executable acceptance check. Say so,
run nothing else, and tell the user to run /sg-plan <milestone>. A clean hand-off is a good
cycle; bump consecutive_noops and log it.
Mark the chosen row in-progress as you start, so a concurrent cycle does not grab it.
3. Implement it
- Re-read the task card and the source section of
docs/v1-dev-roadmap.md it came from, plus
the design authority for the area: docs/surfaceguard-design.md for architecture,
docs/rule-verification.md for what an SG- id means, CLAUDE.md for invariants.
- Match the surrounding code. Package responsibilities in
CLAUDE.md are the map: parsing in
pkg/skill, matching in pkg/rules, orchestration in pkg/scan, output in pkg/report,
signing in pkg/attest/pkg/verify. The CLI stays a thin cobra wrapper returning
exitErr{code,msg} — never os.Exit.
- Preserve the invariants the task touches: the line-offset mapping (findings report true
SKILL.md line numbers), refs as a sub-kind of body, no code execution anywhere in the scan
path, no hard-coded vendor root of trust, ast_references carried through every output format.
- Detections are data. Anything expressible as a rule-pack YAML edit does not become Go code;
bump the pack's
version: in the same commit.
- Tests with the change, not after it. Table tests in the owning package; golden files where
the deliverable is an output format; a fixture in
testdata/ where the deliverable is a
detection. Attack strings in fixtures are inert test data — never run them.
- Offline path in the same task for anything that introduces a network dependency, and keep
heavy dependencies behind a build tag or an isolated package so the default binary stays lean.
- Docs in the same PR — README section,
docs/ page, or both, per the task card.
If the task proves materially harder or different than its card, do not silently redefine it:
implement the part that is right, and amend the card (or add a follow-up row) in the same PR with a
change-log line saying what changed and why.
4. Verify
Standard preflight is sg-maintain §Ship it step 1: gofmt -l . empty · go vet ./... ·
go test ./... · exit-code smoke (scan testdata/malicious → 1, scan testdata/benign → 0) ·
scan any skill bundle you touched · pack version: bumped if you edited a pack.
Beyond it, this cycle owes:
- The task card's acceptance check, run verbatim, with its output pasted into the PR body.
- The cross-cutting checklist in
docs/v1-dev-plan.md §2, ticked honestly.
- If the change can move findings — any rule pack, matcher, scoring, or target change —
regenerate evaluation and confirm no unexplained movement in the corpus counts:
go build -o surfaceguard ./cmd/surfaceguard && evaluation/scripts/run_scans.sh && python3 evaluation/scripts/aggregate.py
— mind the parallelism cap in CLAUDE.md; never pass more than the machine's core count.
- If the change touches performance-sensitive paths, confirm the budget:
scan well under a
second on a typical bundle, cached verify in single-digit ms.
5. Update the tracker
In the same commit as the code:
- Set the task's row to
in-progress and (after the PR exists) fill its PR column — a short
follow-up commit on the same branch, docs(plan): link <TASK-ID> to #<n>, is the normal way.
The row flips to done at the next cycle's reconciliation, when the PR is actually merged.
- Append one line to the plan's Change log if the card itself changed.
- Append to
.claude/development/log.md:## cycle <N> — <ISO timestamp>
- task: <TASK-ID> — <title>
- result: <PR #, or "hand-off: <reason>", or "no-op: <reason>">
- notes: <what the next cycle should know — surprises, follow-ups filed, owner-blocked rows>
- Update
state.json: bump cycle, set last_task, reset consecutive_noops to 0 on real work.
6. Ship
Ship per sg-maintain §Ship it, with:
- branch
dev/<TASK-ID>-<slug> · label develop
- paths the files the task touched, plus
docs/v1-dev-plan.md
- commit conventional, scoped to the change — e.g.
feat(report): emit SARIF 2.1.0 from scan (M3-01). Put the task ID in the subject so the plan
and the git history stay linkable.
- evidence for the PR body: the task ID and its goal, what was built, the acceptance check
command and its output, the cross-cutting checklist, and any card amendment or follow-up row.
Closes #<n> when the task has a tracking issue.
A task PR touches pkg//cmd//testdata/, so it is a code PR — leave it open for the owner
(guardrail 4). A docs-only task (a spike like M4-01, a docs task, a reconciliation-only cycle) is
merged right away once CI is green.
Then report: the task, the PR link, what the next cycle will pick, and any blocked/owner rows
the owner needs to clear.
Notes
- A milestone is complete when every one of its rows is
done, dropped, or owner — say so in
the log and let the next cycle's /sg-plan expand the following milestone.
- If a cycle errors out mid-way, log the failure and leave the branch un-PR'd; §0.2 resumes it.
- Skill bundles under
.claude/skills/ are signed: editing one staleness its .skillsig, which
this machine cannot fix. List every touched bundle under a "needs re-signing" line in the PR body
(guardrail 5) — never keygen or re-sign with a substitute key.
1---2name: sg-develop3description: Run one surfaceguard development cycle — reconcile the plan with merged PRs, pick the next ready task from docs/v1-dev-plan.md, implement it end-to-end with tests and docs, and open one PR. Progress is tracked in the plan file so consecutive sessions resume without context. Use when asked to continue development, implement the next roadmap task, work on a milestone, or when the development loop fires on a schedule.4---56# Run one development cycle78Goal: move `docs/v1-dev-plan.md` forward by **exactly one task**, in **exactly one PR**, leaving9`main` green and the plan file an accurate picture of where the work stands. This is the executor10half of the pair — `/sg-plan` writes the plan, this skill burns it down.1112Wire it up with:1314```15/loop 6h /sg-develop16```1718Invocation forms: `/sg-develop` (next ready task) · `/sg-develop M3-04` (that task specifically).1920## Guardrails2122All `sg-maintain` global guardrails apply — one activity per cycle, one PR per cycle, never23self-merge a code PR, never execute scanned or generated attack content, preflight before every PR,24start from fresh `main`. Plus:2526- **One task per cycle.** A deep plan is answered with more cycles, never a bigger cycle. If the27 task turns out to be two tasks, implement the first and file the second as a new plan row.28- **Never plan here.** If the milestone has no expanded cards, stop and hand off to `/sg-plan`29 (§2). Do not invent tasks not in the plan; do not reorder milestones.30- **Contracts are frozen.** `scan`/`sign`/`verify` CLI surface, JSON shape, and exit codes are31 additive-only until v1.0. A breaking change needs a documented migration and the owner's call —32 raise it in the PR rather than shipping it.33- **Label.** This skill's PRs extend guardrail 4a's type-label set with `develop`.34 Create it once: `gh label create develop -c 0e8a16 -d "roadmap implementation" --force`.3536## 0. Sync and reconcile the plan3738```sh39git checkout main && git pull --ff-only40```4142Read `docs/v1-dev-plan.md`. Before selecting anything, **reconcile status with reality** — this is43what makes the tracker survive across sessions:44451. For every row marked `in-progress` with a PR link: `gh pr view <n> --json state,mergedAt`.46 Merged → set the row to `done`. Closed unmerged → back to `todo` with a change-log line.472. For every `in-progress` row **without** a PR: check for an existing branch48 (`git branch -a | grep <TASK-ID>`) — an interrupted cycle. Resume it (§3) rather than starting49 something new.503. If a row claims `todo` but the functionality demonstrably already exists in the repo, mark it51 `done` with a change-log line instead of re-implementing it.5253Reconciliation edits ride along in this cycle's PR. If reconciliation is the *only* thing that54changed and no task is startable, ship it as a docs-only PR and stop.5556## 1. Load cycle state5758State lives in `.claude/development/` (per-machine, git-ignored — create on first run):5960- `.claude/development/state.json`:61 ```json62 { "cycle": 0, "last_task": "", "consecutive_noops": 0 }63 ```64- `.claude/development/log.md` — append-only human log, newest last.6566The **plan file is the source of truth** for task status; `state.json` only records cadence.6768## 2. Select exactly one task6970In order:71721. A task the caller named explicitly, if it is `todo` or resumable.732. A resumable `in-progress` task from §0.2.743. Otherwise the **first `todo` task, in plan order** (milestone order, then table order) whose75 `Deps` are all `done`.7677Skip `blocked`, `owner`, and `dropped` rows — never work them, but **report them** at the end so78the owner sees what is waiting on them.7980**Hand off instead of guessing** when: the current milestone is titles-only, or every remaining81`todo` is dependency-blocked, or the next task's card has no executable acceptance check. Say so,82run nothing else, and tell the user to run `/sg-plan <milestone>`. A clean hand-off is a good83cycle; bump `consecutive_noops` and log it.8485Mark the chosen row `in-progress` as you start, so a concurrent cycle does not grab it.8687## 3. Implement it8889- Re-read the task card **and** the source section of `docs/v1-dev-roadmap.md` it came from, plus90 the design authority for the area: `docs/surfaceguard-design.md` for architecture,91 `docs/rule-verification.md` for what an `SG-` id means, `CLAUDE.md` for invariants.92- **Match the surrounding code.** Package responsibilities in `CLAUDE.md` are the map: parsing in93 `pkg/skill`, matching in `pkg/rules`, orchestration in `pkg/scan`, output in `pkg/report`,94 signing in `pkg/attest`/`pkg/verify`. The CLI stays a thin cobra wrapper returning95 `exitErr{code,msg}` — never `os.Exit`.96- **Preserve the invariants** the task touches: the line-offset mapping (findings report true97 `SKILL.md` line numbers), `refs` as a sub-kind of `body`, no code execution anywhere in the scan98 path, no hard-coded vendor root of trust, `ast_references` carried through every output format.99- **Detections are data.** Anything expressible as a rule-pack YAML edit does not become Go code;100 bump the pack's `version:` in the same commit.101- **Tests with the change, not after it.** Table tests in the owning package; golden files where102 the deliverable is an output format; a fixture in `testdata/` where the deliverable is a103 detection. Attack strings in fixtures are inert test data — never run them.104- **Offline path in the same task** for anything that introduces a network dependency, and keep105 heavy dependencies behind a build tag or an isolated package so the default binary stays lean.106- **Docs in the same PR** — README section, `docs/` page, or both, per the task card.107108If the task proves materially harder or different than its card, do not silently redefine it:109implement the part that is right, and amend the card (or add a follow-up row) in the same PR with a110change-log line saying what changed and why.111112## 4. Verify113114Standard preflight is `sg-maintain` §Ship it step 1: `gofmt -l .` empty · `go vet ./...` ·115`go test ./...` · exit-code smoke (`scan testdata/malicious` → 1, `scan testdata/benign` → 0) ·116`scan` any skill bundle you touched · pack `version:` bumped if you edited a pack.117118Beyond it, this cycle owes:1191201. **The task card's acceptance check, run verbatim**, with its output pasted into the PR body.1212. The cross-cutting checklist in `docs/v1-dev-plan.md §2`, ticked honestly.1223. If the change can move findings — any rule pack, matcher, scoring, or target change —123 regenerate evaluation and confirm no unexplained movement in the corpus counts:124 `go build -o surfaceguard ./cmd/surfaceguard && evaluation/scripts/run_scans.sh && python3 evaluation/scripts/aggregate.py`125 — **mind the parallelism cap in `CLAUDE.md`**; never pass more than the machine's core count.1264. If the change touches performance-sensitive paths, confirm the budget: `scan` well under a127 second on a typical bundle, cached `verify` in single-digit ms.128129## 5. Update the tracker130131In the same commit as the code:132133- Set the task's row to `in-progress` and (after the PR exists) fill its **PR** column — a short134 follow-up commit on the same branch, `docs(plan): link <TASK-ID> to #<n>`, is the normal way.135 The row flips to `done` at the *next* cycle's reconciliation, when the PR is actually merged.136- Append one line to the plan's **Change log** if the card itself changed.137- Append to `.claude/development/log.md`:138 ```139 ## cycle <N> — <ISO timestamp>140 - task: <TASK-ID> — <title>141 - result: <PR #, or "hand-off: <reason>", or "no-op: <reason>">142 - notes: <what the next cycle should know — surprises, follow-ups filed, owner-blocked rows>143 ```144- Update `state.json`: bump `cycle`, set `last_task`, reset `consecutive_noops` to 0 on real work.145146## 6. Ship147148Ship per **`sg-maintain` §Ship it**, with:149150- **branch** `dev/<TASK-ID>-<slug>` · **label** `develop`151- **paths** the files the task touched, plus `docs/v1-dev-plan.md`152- **commit** conventional, scoped to the change — e.g.153 `feat(report): emit SARIF 2.1.0 from scan (M3-01)`. Put the task ID in the subject so the plan154 and the git history stay linkable.155- **evidence** for the PR body: the task ID and its goal, what was built, the **acceptance check156 command and its output**, the cross-cutting checklist, and any card amendment or follow-up row.157 `Closes #<n>` when the task has a tracking issue.158159A task PR touches `pkg/`/`cmd/`/`testdata/`, so it is a **code PR** — leave it open for the owner160(guardrail 4). A docs-only task (a spike like `M4-01`, a docs task, a reconciliation-only cycle) is161merged right away once CI is green.162163Then report: the task, the PR link, what the next cycle will pick, and any `blocked`/`owner` rows164the owner needs to clear.165166## Notes167168- A milestone is complete when every one of its rows is `done`, `dropped`, or `owner` — say so in169 the log and let the next cycle's `/sg-plan` expand the following milestone.170- If a cycle errors out mid-way, log the failure and leave the branch un-PR'd; §0.2 resumes it.171- Skill bundles under `.claude/skills/` are signed: editing one staleness its `.skillsig`, which172 this machine cannot fix. List every touched bundle under a "needs re-signing" line in the PR body173 (guardrail 5) — never keygen or re-sign with a substitute key.