diff --git a/agents/client-handover-writer.md b/agents/client-handover-writer.md index 1ba9c0c..c9a7271 100644 --- a/agents/client-handover-writer.md +++ b/agents/client-handover-writer.md @@ -62,7 +62,7 @@ and degrading Google's NAP-consistency signal. Pipeline (each step gates the next): 1. Baseline audits: SEO+GEO and security hardening in parallel. 2. Fix loops: apply each audit's bundle (AUTO items, ONE gate for GATED ones) and re-audit until ≥17/20 or `MAX_ITERATIONS` hit. -3. Commit + push if files changed. +3. Commit if files changed (the hooks push in auto-push mode; the push state is read, never assumed). 4. Deploy pause: list deploy artifacts + process, wait for user confirmation. 5. Live-site validation against the deployed URL. 6. Per-axis gate: every score ≥17/20 OR stop + roadmap. @@ -567,40 +567,45 @@ If `PENDING_CHANGES` non-empty → invoke /commit-change skill via subagent: > commit). Use Conventional Commits format. After committing, return the > SHA list." -Then, **before pushing, STOP and ask for an explicit GO** — the push is an -outward-facing action and never fires autonomously: +**PUSH STATE READ.** Three separate Bash calls, never combined, read-only: +`git branch --show-current` → `
`; +`git remote get-url origin >/dev/null 2>&1 && echo origin || echo no-origin`; +`git rev-list --count origin/
..
2>/dev/null || echo unknown` → +`ahead`. Validate `
` against `^[A-Za-z0-9._/-]+$` before using it +anywhere (git accepts shell metacharacters in branch names). On mismatch: +state = `unknown (branch name contains characters this pipeline refuses to +interpolate: push by hand after renaming the branch)`, interpolate NOTHING, +skip the rev-list and the verb. The validated `
` is the only name ever +placed in a `! git push -u origin
` hint (STEP 5, deploy brief, +reports); every re-run of PUSH STATE READ inherits this rule. +If `ahead` ≠ 0 and origin exists: +`bash "$HOME/.claude/lib/gitflow.sh" push-mode` → anything other than `auto` +is treated like `manual` (stderr line kept verbatim when `invalid`). +State, first match wins, in this order: (1) no commits were made this run +or the gitflow fallback left changes uncommitted → +`nothing to push (no commits this run)` / `uncommitted changes (no gitflow +model): publish by hand`, stop; (2) `
` invalid → the unknown state +above; (3) `no-origin` → `not on origin (no origin remote: add one first)`; +(4) `ahead` = 0 → `on origin`; (5) otherwise (`ahead` > 0 or `unknown`) +→ `pending — you: ! git push -u origin
` + reason: push mode `manual` → +`(manual push mode)`, `auto` → `(not on origin: no remote-tracking ref or +the hook push did not land)`, `invalid` → `()`. +The pipeline never runs `git push` itself. -> AskUserQuestion — "Changes committed on ``. Push to origin now? -> - A) Yes — push `` to origin -> - B) No — I'll push manually before confirming deploy" +`pending` → tell the user NOW: `Commits are local only. Push first: +! git push -u origin
`. -Only on **A** run the push; on **B** skip it and note "push deferred to user" -in the STEP 8 summary, then continue. - -> **Red flag — STOP:** never `git push` without option-A GO; never -> `gitflow finish`/`merge`. This pipeline commits and (on GO) pushes a working -> branch — it never integrates into a protected branch. - -```bash -CURRENT_BRANCH=$(git branch --show-current) -git push origin "$CURRENT_BRANCH" 2>&1 -``` - -If push fails (no remote, auth issue, conflict): capture error, report to -user via AskUserQuestion: - -``` -"Push failed: . Pipeline needs the changes published before deploy. -Options: -- A) Retry push (after I fix it manually) -- B) Skip push — I'll publish manually before confirming deploy -- C) Abort pipeline" -``` +> **Red flag — STOP:** never `git push` (the hooks push in auto-push mode; +> otherwise the user does); never `gitflow finish`/`merge`. This pipeline +> commits a working branch — it never integrates into a protected branch. --- ## STEP 6 — DEPLOY PAUSE +Re-run PUSH STATE READ (every path reaches STEP 6, some without STEP 5's +read). + Skip if `PROJECT_TYPE != web` (non-web has no deploy-then-validate flow — set `VALIDATE_SKIPPED=true` and jump to STEP 8). @@ -625,10 +630,15 @@ Commits added in this session: Tailor to project deploy method (use DEPLOY_HINTS): -- **Vercel/Netlify/Cloudflare Pages auto-deploy from git**: "Push has been - done. The platform deploys automatically — usually 1-3 min. Watch the - dashboard. Tell me when the new version is live." -- **GitHub Actions / GitLab CI**: "Workflow `` should run on push. +When the state is `pending`, the brief OPENS with +`First push: ! git push -u origin
`. When `on origin`, keep "Push has +been done. …". + +- **Vercel/Netlify/Cloudflare Pages auto-deploy from git**: "The platform + deploys automatically after your push (a working branch gives a preview + at most; production builds from the production branch) — usually 1-3 + min. Watch the dashboard. Tell me when the new version is live." +- **GitHub Actions / GitLab CI**: "Workflow `` runs on your push. Watch CI status. Tell me when it's green and live." - **Manual upload (FTP / SSH)**: "Upload these files to the server: ``. If using rsync, here's a template: `rsync -avz dist/ user@server:/path`." @@ -650,6 +660,9 @@ AskUserQuestion: - C) Skip /web-validate — proceed to handover doc with VALIDATE marked SKIPPED ``` +After option A "Deployed": re-run PUSH STATE READ; still `pending` → ask +again (the live site cannot hold these commits). + If A → proceed to STEP 7. If B → exit cleanly with state report. If C → mark `VALIDATE_SKIPPED=true` and jump to STEP 8. @@ -683,8 +696,8 @@ SCORE_VALIDATE_AFTER=$(extract_score .claude/audits/VALIDATE.md) Note: VALIDATE has no `_BEFORE` (first run is post-deploy). The before/after table for VALIDATE shows `—` for before, `` for after. -If /web-validate produced new fixes in source code, run STEP 5 again (mini-commit -+ push) BEFORE moving to STEP 8 — but DO NOT loop /web-validate. The remaining +If /web-validate produced new fixes in source code, run STEP 5 again (mini-commit; +push state read, never assumed) BEFORE moving to STEP 8 — but DO NOT loop /web-validate. The remaining deploy of those fixes is mentioned to the user in the final doc. --- @@ -806,9 +819,14 @@ Below-threshold audits: Roadmap written to .claude/audits/HANDOVER-ROADMAP.md. Tasks appended to .claude/tasks/TODO.md. +Push: + Resolve P0 items, then re-run /client-handover. ``` +Re-run PUSH STATE READ right before printing this report (never reuse +the STEP 5 snapshot). + If `ALL_PASS = true` → proceed to STEP 9 (memory load + doc generation). ### Roadmap structure (when gate fails) @@ -1170,9 +1188,13 @@ Parse the returned `HANDOVER-DOC REPORT`: - `STATUS: DONE` → report the `MD` / `HTML` / `PDF` paths to the user, plus the `GATES` line and any `NOTES` caveats (e.g. `[À COMPLÉTER]` markers left in NAP, deploy chapter included/skipped). + Re-run PUSH STATE READ right before printing, then add the bullet: + - Push: - `STATUS: BLOCKED` → surface the report verbatim (including which PACKAGE field the doc-writer flagged) and stop — do not retry or - patch the PACKAGE silently. + patch the PACKAGE silently. Re-run PUSH STATE READ and add the same + bullet: + - Push: In BOTH branches, then clean the transient draft: `rm -f ".audit/handover-draft-${RUNID}.md"` (run-scoped, gitignored — diff --git a/agents/release-executor.md b/agents/release-executor.md index f33a180..b86aa05 100644 --- a/agents/release-executor.md +++ b/agents/release-executor.md @@ -77,15 +77,17 @@ actual branch; never finish whatever happens to be checked out. output verbatim; do not attempt to resolve it yourself. 2. **Tag AFTER finish, on `main`** — never before: `git tag -a v main -m "release "` (annotated, so it lands on - main's release-merge commit). Finish has already pushed `main` and - `develop` through the lib's hooks (BDR-095); the tag stays local until - the dispatcher's tag-push gate. + main's release-merge commit). In auto-push mode finish pushes `main` + and `develop` (best effort: the lib warns and returns 0 on a failed + push; the dispatcher re-verifies with ahead counts) (BDR-095); in + manual push mode they stay local. The tag stays local until the + dispatcher's tag-push gate. ### Forbidden in this span -`git push` (any remote, any ref — `main`/`develop` ride the lib's hook -pushes during finish; the dispatcher owns the tag-push gate), deciding the -version number, the when-to-release decision, attribution trailers of any -kind. +`git push` (any remote, any ref — `main`/`develop` ride the lib's +pushes during finish in auto-push mode; the dispatcher owns the tag-push +gate), deciding the version number, the when-to-release decision, +attribution trailers of any kind. --- diff --git a/skills/client-handover/SKILL.md b/skills/client-handover/SKILL.md index 73ebfaf..d7df8f0 100644 --- a/skills/client-handover/SKILL.md +++ b/skills/client-handover/SKILL.md @@ -45,7 +45,7 @@ The agent runs a **ship-and-handover pipeline** with explicit gates: - Re-invoke the audit subagent in audit mode: it re-scores and returns the next FIX BUNDLE; it applies nothing (a dispatched child cannot hold a gate). - Repeat up to `MAX_ITERATIONS` (default 5). - If still < 17/20 after cap → escalate to user with concrete remaining issues; user decides continue / stop / manual intervention. -4. **COMMIT + PUSH** — If files changed during fix loops, run /commit-change (atomic logical commits) then `git push`. +4. **COMMIT + PUSH** — If files changed during fix loops, run /commit-change (atomic logical commits); the gitflow hooks push in auto-push mode, otherwise (manual push mode, or a hook push that failed) the agent tells the user to push with `! git push -u origin ` BEFORE the deploy pause. 5. **DEPLOY PAUSE** — List exact deploy artifacts: changed files since baseline, deploy hints from project (vercel.json, netlify.toml, Dockerfile, .github/workflows/deploy.yml, etc.), and the deploy process in plain words. Use AskUserQuestion: "Deploy done? (Yes / Not yet / Skip validate)". Block until Yes or Skip. 6. **/web-validate (live site)** — Run validator-analyzer against the deployed URL. Capture `SCORE_VALIDATE`. 7. **GATE — per-axis threshold ≥17/20** — Compute final `SCORE_*_AFTER` for SEO classique, GEO (IA), HARDEN, VALIDATE. If ANY < 17/20: STOP. Generate `.claude/audits/HANDOVER-ROADMAP.md` with prioritized analysis of what's blocking each below-threshold axis. Do NOT write the client deliverable. Report to user. diff --git a/skills/release-candidate/SKILL.md b/skills/release-candidate/SKILL.md index 7eddd35..4437664 100644 --- a/skills/release-candidate/SKILL.md +++ b/skills/release-candidate/SKILL.md @@ -25,8 +25,9 @@ The two mechanical spans (prep, finish+tag) run on the sonnet-pinned gate needed here, dispatch does the job. This dispatcher keeps everything the executor must never own: the version-NUMBER decision (judgment — derives from semver change nature), and the two human gates (when to release, and -the tag push). A human gate sits BETWEEN the two spans by construction, so the -executor is never dispatched twice in one call. +the tag push (auto-push mode; in manual push mode the user pushes main, +develop and the tag in one command)). A human gate sits BETWEEN the two +spans by construction, so the executor is never dispatched twice in one call. ## When to use - `develop` is ahead of `main` and you want to publish a version. @@ -54,6 +55,8 @@ Read the `## [Unreleased]` section of `CHANGELOG.md` and the commits on `develop` since `main`. Apply the Versioning rule above (breaking → MAJOR, features → MINOR, fixes → PATCH) and settle `` before dispatching anything — the executor never derives or second-guesses this number. +The version must match `^[0-9]+\.[0-9]+\.[0-9]+$` before it is placed in +any command or tag; anything else stops the run. ### STEP 3 — Dispatch: prep ``` @@ -93,10 +96,22 @@ Parse the `RELEASE-EXEC REPORT`: not an auto-retry. ### STEP 6 — Tag push GATE (ASK) -`main` and `develop` are already on origin: the lib pushes every merge as -it lands (`_gitflow_merge_into` + the post-merge hook, BDR-095). Only the -tag is left. STOP. On explicit go only ([[LRN-069]]) — run the tag push -HERE, in this dispatcher, never delegated to the executor: +Read the state, separate Bash calls: +`git rev-list --count origin/main..main 2>/dev/null || echo unknown`, +`git rev-list --count origin/develop..develop 2>/dev/null || echo unknown`, +`bash "$HOME/.claude/lib/gitflow.sh" push-mode`. +- Anything other than `auto` from the verb (manual, invalid, empty, usage + error) OR either count ≠ 0 or `unknown` → Claude pushes nothing + (push-guard would refuse it in manual mode; a failed lib push is the + user's call, BDR-095). Print ONE command for the user and STOP, no + question: `! git push --atomic origin main develop v` (invalid: + quote the verb's stderr line verbatim; auto with a count ≠ 0 or unknown: + say `main/develop not on origin (no remote-tracking ref or the lib's push + did not land)`; no origin remote (`git remote get-url origin` fails): say + `add an origin remote first`). +- Push mode `auto` and both counts 0 → main and develop are on origin; only + the tag is left. STOP. On explicit go only ([[LRN-069]]) — run the tag + push HERE, never delegated: ``` AskUserQuestion: Push tag v to origin? — go / hold @@ -105,13 +120,16 @@ Go → ```bash git push origin v ``` -`hold` → stop; the release is on origin (main + develop), the tag stays local. +`hold` → stop; the release is on origin (main + develop), the tag stays +local until the next push of main (`--follow-tags` on every lib and hook +push). ## Common mistakes - Tagging before `gitflow finish` → tag wouldn't sit on main's merge commit. Tag AFTER, on main. - Auto-firing finish because tests pass → finish is a HUMAN gate. - Restarting the tag at v1.0.0 → desyncs from the CHANGELOG lineage. Continue it. - Pushing the tag without the ASK gate → [[LRN-069]]. +- Pushing anything in manual push mode → print the one user command, push nothing. ## Validation `RC_WORK=$(mktemp -d) RC_TAG=1 bash lib/tests/run-release-candidate.sh` → 5/5 (fan-out + tag on main). `RC_TAG=0` reds the tag assertion — proves the lib alone never tags (the gap this skill fills). diff --git a/skills/tour/SKILL.md b/skills/tour/SKILL.md index 3f7ca46..9f88245 100644 --- a/skills/tour/SKILL.md +++ b/skills/tour/SKILL.md @@ -237,6 +237,16 @@ order: 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 remote get-url origin >/dev/null 2>&1 || echo no-origin` + then + `git -C rev-list --count --not --remotes=origin 2>/dev/null || echo unknown` + (`` = the name `gitflow start` returned, suffixed `-2`/`-3` on a + same-day re-run — never the bare `chore/tour-`). 0 → `on origin`; + else `local only → ! git -C push -u origin ` (probe + printed `no-origin` → `local only (no origin remote)`). ```markdown ## Tour 2026-07-04 — branch chore/tour-2026-07-04 — 2 iterations — CONVERGED @@ -255,8 +265,8 @@ 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 - ~/proj/site : NOT CONVERGED (3 it.) — 2 open residuals | chore/tour-2026-07-04, 6 commits + ~/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. @@ -270,9 +280,10 @@ without that approval — neither this repo's nor any target project's. - Branch via the gitflow lib; **never `gitflow finish`, never merge, never push `main`/`develop`** — "the tour is green" is not a signal. - The chore branch's own commits are pushed by the gitflow hooks - (BDR-095); a `push FAILED` hook warning is a report residual, fixed - with a plain `git push -u origin chore/tour-`. + 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 push -u origin ` — 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.