Files
claude_mac/skills/tour/SKILL.md
T
bchanot 6104545e76 feat(skills): push state read from facts, never pushed by the skills
Run C2 of manual-push mode (BDR-111/BDR-112). The four flows that pushed
on their own, or claimed the branch was on origin, now read the truth
after the fact and hand the user the exact command:

- client-handover-writer: the "Push to origin now?" question and its
  push block are gone (the hooks had already pushed in auto-push mode;
  push-guard denies it in manual mode). A reusable PUSH STATE READ
  (branch, origin probe, `git rev-list --count origin/<br>..<br>`, the
  verb only to word the reason) runs after commit-change, at the top of
  the deploy pause, after "Deployed" and before each end report. The
  branch name is validated against an allowlist before it is placed in
  any command or hint (a hostile branch name is otherwise a shell
  injection). Pending → the user pushes BEFORE the deploy pause; the
  deploy brief says "after your push". `Push:` line in both reports.
- release-candidate STEP 6: two ahead counts + the verb; anything other
  than auto with both counts 0 prints one user command
  `! git push --atomic origin main develop v<X.Y.Z>` and stops; the tag
  gate stays for auto mode; `hold` notes --follow-tags; version regex.
- release-executor: push claims qualified (auto-push mode, best effort).
- tour: mode-agnostic rule; STEP 3 reads one `git -C <project>` fact per
  project (suffix-aware branch, --remotes=origin, origin probe) and the
  summary row says on origin / local only with the user command.
2026-10-07 14:02:51 +02:00

353 lines
18 KiB
Markdown

---
name: tour
effort: xhigh
description: |
Use when the user wants ONE grouped pass over a whole project (or a
list of projects) covering all hygiene axes together: code cleanup +
security (semgrep/cso) + TODO-vs-reality check + doc sync, auto-fixing
and re-auditing until a clean pass — even without naming the axes.
NOT one axis alone (/code-clean, /cso, /audit-delta, /reconcile, /doc),
one bug (/hotfix, /bugfix), dashboard (/health), branch diff (/review).
Triggers: "tour", "tir groupé", "grand ménage", "fais un tour sur les
projets", "sweep", "full pass", "vérifie et corrige tout".
argument-hint: "[project paths… — blank = current repo] [--report-only]"
allowed-tools:
- Read
- Edit
- Write
- Bash
- Grep
- Glob
- Agent
- AskUserQuestion
---
# /tour — grouped multi-axis sweep (clean + security + reconcile + doc)
## MODEL GATE (blocking — run before any other step)
Run `$HOME/.claude/lib/model-gate.md`. Reflection here (planning, audit
judgment, loop decisions) requires Fable/Opus. Verdict `small` → STOP: the
gate prints the remedy; end the turn — no later step, no dispatch. Nominal
(big) path is silent.
EFFORT SHIFTS: follow `$HOME/.claude/lib/effort-shift.md` (BDR-107): medium when a dispatch span starts, own level before challenge synthesis, low at the bookkeeping tail, max at escalation; every shift goes in the same message as the step's first tool call, a lone Skill call is a no-op.
One pipeline per project: **security → clean → re-verify → reconcile →
doc → convergence re-audit**, looping until a full pass applies zero new
fixes. Auto mode by design: fixes are committed on a dedicated
`chore/tour-<date>` branch that this skill **never merges** — the branch
plus its report IS the approval gate, reviewed by the human afterwards.
Core principle: **autonomy on the working branch, never on shared
state.** The skill may edit code freely on its own branch; it may NOT
silently rewrite declared state (target TODO, memory registries) or
integrate anything (merge/finish/push to `main`/`develop`).
## When NOT to use
| Situation | Skill |
|-----------|-------|
| One axis only (cleanup / security / TODO / doc) | `/code-clean`, `/cso`, `/reconcile`, `/doc` |
| Recurring single-axis audit scoped to the delta | `/audit-delta` |
| One obvious bug | `/hotfix`, `/bugfix` |
| Quality dashboard, no fixes | `/health` |
| Review a branch/PR diff | `/review`, `/code-review` |
| All axes, fix, loop to clean, 1..N projects | **this skill** |
## STEP 0 — ARGS & PROJECT LIST
- Paths in `$ARGUMENTS` → project list. No paths → current repo only.
- ONE project → run STEP 1 → STEP 3 inline in this loop (nominal path,
unchanged).
- TWO OR MORE projects → parallel fan-out, STEP 0b: the repos are
independent working trees on independent chore branches — nothing the
runners write can collide.
- `--report-only` → run every audit, apply NO fix, write reports only
(the flag is forwarded to every runner).
- A failure in one project never aborts the tour: record it in that
project's report section — or as its global-summary row when the
runner itself died — and move on.
## STEP 0b — MULTI-PROJECT FAN-OUT (one runner per project, BDR-084)
Dispatch ONE runner per project, ALL in a SINGLE message — sequential
dispatch of independent repos defeats the purpose.
Model discipline (the user-fixed invariant behind this mode):
- The runner is dispatched with **NO `model` override** — it inherits the
session model, already validated big by the MODEL GATE above. The runner
carries this skill's reflection (fix decisions, convergence calls); it
must never be pinned down to an executor tier.
- Inside a runner, every dispatched agent keeps the tier this skill
already defines: security-auditor (sonnet frontmatter), the Phase B
audit (analyzer opus pin or `model="opus"`), doc-syncer (audit on
`model="opus"`, patch on its sonnet frontmatter — BDR-077).
Runner dispatch, one per project:
```
Agent(subagent_type="general-purpose",
description="tour runner — <project basename>",
prompt="Read ~/.claude/skills/tour/SKILL.md and execute STEP 1 → STEP 3
for EXACTLY ONE project: <absolute path>. Flags: <--report-only|none>.
Skip STEP 0/0b (routing) and the global summary — the dispatcher owns
them. Every rule of the skill applies unchanged: max 3 iterations,
never merge/finish/push main|develop, scoped commits, report appended to that
project's own .claude/audits/TOUR.md. Return EXACTLY: the project's
one-line global-summary row (STEP 3 format), then BRANCH: <name|no
branch>, then REPORT: <path>.")
```
The main loop then:
1. Collects each runner's summary row. A dead or mute runner becomes the
row `<path> : RUNNER FAILED — <reason> | no report` — never a silent
absence (a mute runner is NEVER a pass).
2. Prints the global summary (STEP 3 format) once ALL runners returned.
3. Runs the gated capitalize offer — MAIN LOOP ONLY, never inside a
runner: registries are shared state, and gate decisions stay here
(LRN-083).
LRN-083 derogation, recorded in BDR-084: the per-project iteration loop
(fix decisions, convergence) runs INSIDE its dispatched runner. Bounded
because nothing a runner decides touches shared state — independent
repos, per-repo chore branches, branches left UNMERGED for human review
exactly as in inline mode.
## STEP 1 — PRECONDITIONS (per project)
All git commands use `git -C <project>`. Check, in order:
1. Is a git repository → else SKIP (recorded, not an error).
2. Working tree **clean** (`git status --porcelain` empty) → else this
project runs **report-only**: never mix the user's WIP with tour
fixes, never stash someone else's work.
3. `develop` exists and `~/.claude/lib/gitflow.sh` is available → start
the working branch via the lib, never by hand:
`bash ~/.claude/lib/gitflow.sh start chore tour-YYYY-MM-DD`
(append `-2`, `-3`… if the branch already exists). Missing develop
or lib → **report-only** + suggest `gitflow init` in the report.
4. Detect project checks once (tests, lint, build, type-check — from
package.json/Makefile/CLAUDE.md). Record what exists; "none found"
is itself a report line.
Report file: `.claude/audits/TOUR.md` in the target project (create
`.claude/audits/` if absent). Append-only — never rewrite past runs.
## STEP 2 — ITERATION LOOP (max 3 per project)
Each iteration runs phases A→D in fixed order. **Convergence** = one
full iteration that applies **zero fixes** and finds **zero new
findings** with project checks green. Converged → STEP 3. Not converged
after 3 iterations → STOP, residuals stay `open` in the report, say so
honestly in the summary. Never loop past 3.
### Phase A — SECURITY (deterministic floor first)
1. Dispatch the SAST gate (fresh every iteration):
```
Agent(subagent_type="security-auditor", description="tour security — semgrep SAST",
prompt="MODE: audit\nSCOPE: project (full tree, respect .gitignore)\nPROJECT: <path>\nREPORT: .claude/audits/.tour-semgrep.md\nFollow agents/security-auditor.md exactly. Pinned rulesets, no login. Write ONLY to REPORT. End with REPORT_WRITTEN: <path>.")
```
semgrep ABSENT → DEGRADED (checklist only) is surfaced in the
report, not a silent downgrade and not a blocker.
2. gstack ON (`/cso` available) → **iteration 1 only**, dispatch a cso
posture audit (deps CVE, OWASP) in audit mode; fold its findings in.
3. Fix policy (skip in `--report-only`): CRITICAL/HIGH → fix now.
MEDIUM/LOW → fix only if local and behavior-preserving, else leave
`open`. Every fix minimal, CLAUDE.md security defaults apply.
A CRITICAL/HIGH fix that changes the API contract (new required
header/param, changed status codes, moved paths) is NOT applied — a
breaking change is the human's call (CLAUDE.md: confirm before a
breaking change). Its report row becomes
`open — needs decision (BREAKING)` with the proposed patch attached,
and the global summary line carries the **BREAKING** count.
Behaviour-preserving CRITICAL/HIGH fixes stay auto-applied.
4. Commit scoped: `git add <files touched>` (never `-A`),
`fix(security): …`.
### Phase B — CLEAN
1. Dispatch a read-only cleanup audit (analyzer — opus-pinned, BDR-076 —
or general-purpose with `model="opus"`; NOT the sonnet code-cleaner,
which is now a fix executor): dead code, unused imports/exports,
commented-out blocks, stale flags, norm violations. Findings as
`id | file:line | finding | proposed fix`.
2. Apply **behavior-preserving** fixes only. A finding that would change
behavior is a bug, not cleanup → log to
`.claude/audits/BUGS-FOUND.md`, leave the code alone.
3. Commit scoped: `chore(clean): …`.
### Phase C — RE-VERIFY (after any fix)
1. Run the project checks found in STEP 1. Lint alone is NOT
verification when tests/build exist.
2. Fresh read-only subagent re-audits the files modified this
iteration (same axis prompts). Pass = approved findings resolved AND
zero new findings introduced.
3. Fail → fix → recheck, max 3 attempts inside the iteration; still
failing → **revert this phase's commits** (fail closed), findings
back to `open`, recorded in the report.
### Phase D — RECONCILE + DOC
1. **Reconcile — REPORT-ONLY, always, even in auto mode.** Confront
declared state (target TODO checkboxes, registry statuses) against
real state (git log, files, branches) — reuse `lib/reconcile.sh`
oracles when available. Every gap goes in the report as
`declared X | real Y | suggested edit`. **Never check a box, never
restructure, never edit the target project's TODO.md or
`.claude/memory/`** — an inferred checkbox is exactly the lie
/reconcile exists to catch. The human applies suggestions via
`/reconcile` later.
2. **Doc sync** — two-mode doc-syncer, mirrors /ship-feature STEP 8
(BDR-077: audit judgment on opus, patch on the sonnet pin):
`Agent(subagent_type="doc-syncer", model="opus")` with `MODE: audit`
+ `auto-mode scope: <files this tour touched>`; public docs only
(README, INSTALL, USAGE, CHANGELOG…), never `.claude/**`, never
CLAUDE.md. NONE → done. `[MINOR]` PATCH PLAN → re-dispatch
`Agent(subagent_type="doc-syncer")` (sonnet frontmatter) with
`MODE: patch` + the plan verbatim, then commit its `PATCHED_FILES:`
via `bash ~/.claude/lib/doc-commit.sh` when available, else a scoped
`docs: …` commit of exactly those paths. SIGNIFICANT → not applied:
report row `suggested` with the plan item (the tour has no human
gate mid-run).
### End of iteration
Fixes were applied (any phase) OR new findings appeared → run another
iteration (fixes can invalidate earlier audits — that is the point of
the loop). Otherwise → converged.
## STEP 3 — REPORT, CLEANUP & SUMMARY (per project, then global)
Append to `.claude/audits/TOUR.md`, then close the run in this exact
order:
1. Write the run section (template below).
2. **Delete the scratch audit files** this run created
(`.claude/audits/.tour-semgrep*` and similar) — their content is
folded into TOUR.md. A tree left dirty here forces the NEXT tour
into report-only: the skill must not self-block.
3. Commit the report as the run's final commit (`docs(tour): report`) —
EXCEPT in `--report-only` mode: no chore branch exists there, so the
commit would land on the user's current branch (possibly develop — the
red flag below forbids that). Report-only leaves TOUR.md uncommitted
and says so in the summary.
4. Confirm `git status --porcelain` is clean (runtime junk the sandbox
cannot delete, e.g. `__pycache__/`, becomes a report residual line).
5. Push state, only when a branch exists (report-only, skipped or
dirty-tree projects have none: their row keeps `no branch`, no push
column). Two read-only calls, probe first:
`git -C <abs project> remote get-url origin >/dev/null 2>&1 || echo no-origin`
then
`git -C <abs project> rev-list --count <branch> --not --remotes=origin 2>/dev/null || echo unknown`
(`<branch>` = the name `gitflow start` returned, suffixed `-2`/`-3` on a
same-day re-run — never the bare `chore/tour-<date>`). 0 → `on origin`;
else `local only → ! git -C <abs project> push -u origin <branch>` (probe
printed `no-origin` → `local only (no origin remote)`).
```markdown
## Tour 2026-07-04 — branch chore/tour-2026-07-04 — 2 iterations — CONVERGED
| ID | Axis | File | Sev | Finding | Status |
|----|------|------|-----|---------|--------|
| SEC-1 | security | app.py:17 | high | shell=True + concat | fixed |
| SEC-2 | security | app.py:14 | high | no authz on POST /backup | open — needs decision (BREAKING): new required X-Backup-Token header, patch attached |
| CLN-1 | clean | utils.py:9 | - | dead legacy_md5 | fixed |
| REC-1 | reconcile | TODO.md | - | "/health" unchecked, shipped 2d92696 | suggested |
| DOC-1 | doc | README.md | - | phantom /status endpoint | fixed |
Checks: pytest PASS, ruff PASS. Residuals: SEC-2 (needs decision). Commits: 4. BREAKING: 1 (SEC-2).
```
Global summary inline, one line per project (append `BREAKING: n` to
any project line with contract-changing fixes left open for decision):
```
TOUR COMPLETE — 2026-07-04
~/proj/api : CONVERGED (2 it.) — 3 fixed, 1 suggested | chore/tour-2026-07-04, 4 commits | on origin
~/proj/site : NOT CONVERGED (3 it.) — 2 open residuals | chore/tour-2026-07-04, 6 commits | local only → ! git -C ~/proj/site push -u origin chore/tour-2026-07-04
~/proj/lib : report-only (dirty tree) | no branch
Branches left UNMERGED — review each, then `gitflow finish` on your GO.
Reconcile suggestions pending — apply via /reconcile.
```
Then offer to capitalize (gated, per CLAUDE.md): recurring cross-project
patterns → learnings, tour verdict → evals. Never write registries
without that approval — neither this repo's nor any target project's.
## Rules
- Branch via the gitflow lib; **never `gitflow finish`, never merge,
never push `main`/`develop`** — "the tour is green" is not a signal.
The gitflow hooks push the chore branch in auto-push mode only; when it
is not on origin (manual push mode, or a `push FAILED` warning) the USER
pushes it — `! git -C <abs project> push -u origin <branch>` — the tour
never pushes or retries.
- Scoped pathspecs only; `git add -A` is forbidden.
- Target TODO.md and target `.claude/memory/` are READ-ONLY. Reconcile
produces suggestions, not edits.
- Only the four axes. No unrequested bootstrap (.gitignore, registries,
configs, features) — infrastructure gaps are report lines, not work.
- Security floor = security-auditor (pinned semgrep + checklist). An
ad-hoc grep is never "the security pass".
- Max 3 iterations per project; max 3 fix attempts per re-verify.
Residuals are reported, not silently retried forever.
- Dirty tree / no develop / no gitflow lib → report-only, stated in the
report. Never stash, never branch by hand.
- Reports append-only. One report per project, in that project.
## Common mistakes
| Mistake | Fix |
|---------|-----|
| Checking TODO boxes "obviously done" during reconcile | Report-only. Suggested edits, human applies. |
| Writing BDR/LRN/journal entries in the target project | Registries only via the gated capitalize offer, end of tour. |
| grep/ruff pass = security done | security-auditor agent (pinned semgrep) is the floor, every iteration. |
| Findings live only in the final chat message | TOUR.md is what the human reviews before merging. Write it. |
| "Bonus hygiene" (.gitignore, templates, bootstrap) | Out of scope. Report line, not work. |
| Loop "until clean" with no bound | Max 3 iterations, then honest residuals. |
| Merging/finishing because everything is green | Green ≠ GO. Branch stays; human merges. |
| Stashing a dirty tree to proceed | Report-only for that project. |
| One TOUR.md for all projects in the config repo | Each project gets its own `.claude/audits/TOUR.md`. |
| Fixing a behavior-changing "cleanup" finding | That is a bug → BUGS-FOUND.md, untouched code. |
| Scratch audit files left untracked at the end | Delete them in STEP 3.2 — a dirty tree self-blocks the next tour. |
| Contract-changing security fix auto-applied | Not applied: row `open — needs decision (BREAKING)` + proposed patch; **BREAKING** count in the summary line. |
## Red flags — STOP
- About to `Edit` a target project's TODO.md or `.claude/memory/*`.
- About to run `gitflow finish`, `git merge`, or push `main`/`develop`.
- About to `git add -A` or commit on `main`/`develop`.
- Starting iteration 4, or "just one more loop, it's almost clean".
- Security phase done without the security-auditor agent and without a
DEGRADED notice in the report.
- Creating any file the audit did not require (.gitignore, templates).
- Ending a project's run with `git status --porcelain` non-empty and no
residual line explaining every leftover path.
## TDD note (skill itself)
Baseline-tested per writing-skills (vendored superpowers skill;
2026-07-04, seeded fixture, no skill): the agent branched correctly via
gitflow and did not merge, BUT (1) silently rewrote the target TODO (checked
boxes,
restructured) during "reconcile"; (2) authored BDR/journal registry
entries autonomously; (3) ran security as ad-hoc grep + ruff — no
semgrep, no pinned rulesets; (4) left findings only in its final chat
message — no persistent report to review before merge; (5) bootstrapped
unrequested .gitignore + memory registries ("bonus hygiene");
(6) looped without a stated bound (converged at pass 2 by luck). Phase
D.1, the registry rule, Phase A.1, STEP 3, the scope rule and the
3-iteration bound counter each observed failure.
GREEN run (same day, fresh fixture, skill followed): all six gaps
closed — TODO zero-diff, no registry writes, semgrep every iteration,
TOUR.md committed, no scope creep, converged in 3 bounded iterations on
an unmerged chore branch. Two new holes surfaced and patched
(REFACTOR): scratch semgrep files left untracked (would self-block the
next run — STEP 3.2) and a contract-changing security fix not flagged
(BREAKING tag in template). The REFACTOR additions are
template-structural and were not re-run through a third full fixture
pass — re-test on first real use.