From 39843f70d710fab2b7a0ef62d70d1e9e76a9619f Mon Sep 17 00:00:00 2001 From: Tim Simms Date: Mon, 16 Mar 2026 11:47:50 +0000 Subject: [PATCH] chore: harden branch protection and align docs with config-driven branch model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enable required PR reviews and enforce admins on main via GitHub API - Replace unsupported CronCreate/CronDelete polling with background shell loop - Update README assertion count (116 → 124) and add /deploynope-console command - Refresh COVERAGE-MATRIX.md: merge into production = deny, reset = conditional deny/ask, actual test counts - Replace 39 hardcoded 'master' references across 6 demo HTML files with production-branch terminology - Standardize stage tag format in CLAUDE.md and COVERAGE-MATRIX.md Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/commands/deploynope-deploy.md | 28 ++++++++++------ CLAUDE.md | 2 +- README.md | 3 +- docs/deploy-process-demo.html | 28 ++++++++-------- docs/hooks-demo.html | 4 +-- docs/index.html | 2 +- docs/preflight-postdeploy-demo.html | 24 +++++++------- docs/rollback-demo.html | 16 +++++----- docs/staging-contention-demo.html | 18 +++++------ docs/stale-config-demo.html | 8 ++--- tests/COVERAGE-MATRIX.md | 46 +++++++++++++-------------- 11 files changed, 94 insertions(+), 85 deletions(-) diff --git a/.claude/commands/deploynope-deploy.md b/.claude/commands/deploynope-deploy.md index b7df13a..c3902db 100644 --- a/.claude/commands/deploynope-deploy.md +++ b/.claude/commands/deploynope-deploy.md @@ -320,21 +320,29 @@ the option to poll until staging is released rather than abandoning the workflow If the user accepts: -1. **Set up a recurring poll** using `CronCreate` with a `*/1 * * * *` schedule that runs: +1. **Set up a background polling loop** using the Bash tool with `run_in_background: true`: ```shell - git fetch origin && git tag -l "staging/active" + while true; do + git fetch origin 2>/dev/null + if ! git tag -l "staging/active" | grep -q "staging/active"; then + UNRELEASED=$(git log origin/..origin/ --oneline 2>/dev/null) + if [ -z "$UNRELEASED" ]; then + echo "STAGING_CLEAR" + exit 0 + fi + fi + echo "Still waiting — staging claimed." + sleep 60 + done ``` - - If `staging/active` still exists → report "Still waiting — staging claimed by ``." - - If `staging/active` is gone **and** `git log origin/..origin/ --oneline` - shows no unreleased commits → report **"Staging is now clear!"** and proceed. + - The loop checks every 60 seconds whether `staging/active` has been removed. + - When the tag is gone **and** there are no unreleased commits on staging, it prints + `STAGING_CLEAR` and exits. + - You will be notified when the background task completes — do not poll or sleep-wait for it. -2. **Clean up immediately** — as soon as staging is detected as clear (or the user cancels - the wait), delete the cron job using `CronDelete` with the job ID returned by `CronCreate`. - Never leave a polling job running after it has served its purpose. - -3. **Resume the deployment flow** — once staging is clear and the cron job is cleaned up, +2. **Resume the deployment flow** — once the background poll reports staging is clear, continue from the "Claiming staging" step below without requiring the user to re-invoke the deployment command. diff --git a/CLAUDE.md b/CLAUDE.md index 6a79f2b..c3ca80a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,7 +43,7 @@ reset, release, etc.), the confirmation prompt **must** include a visible Examples: > "Ready to commit all of this? **`🤓 DeployNOPE 2.10.0 · Feature`**" > "Shall I push this to origin? **`🤓 DeployNOPE 2.10.0 · Feature`**" -> "Ready to reset `master` to match `staging`? **`⚠️ DeployNOPE 2.10.0 · Production`**" +> "Ready to reset `main` to match `staging`? **`⚠️ DeployNOPE 2.10.0 · Production`**" The absence of the tag on a deployment-related confirmation is itself a red flag that the framework was not loaded. diff --git a/README.md b/README.md index f0790f6..9deef5f 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ All commands are prefixed with `deploynope-` so they stay distinct in your slash | `/deploynope-rollback` | Guides you through rolling back production to a previous release. Supports standard (through staging) and emergency (skip staging) modes. Handles frontend cache-busting automatically. | | `/deploynope-stale-check` | Identifies stale branches, aging PRs, and pipeline bottlenecks. Helps keep the repo tidy and surfaces work that may have been forgotten. | | `/deploynope-verify-rules` | A read-only self-check that confirms the deployment ruleset is loaded and Claude understands all 10 critical safety rules. Good for sanity-checking before a big release. | +| `/deploynope-console` | **DEPRECATED** (removed in 2.10.0). The sidecar console log feature has been replaced by inline chat tags (` DeployNOPE · `). | --- @@ -221,7 +222,7 @@ If you previously had these commands inside a project's `.claude/commands/`, you ## Tests -DeployNOPE includes a bash test suite covering all 9 hooks with 116 assertions. Tests create disposable git repos, simulate hook JSON input, and verify deny/ask/passthrough decisions. +DeployNOPE includes a bash test suite covering all 9 hooks with 124 assertions. Tests create disposable git repos, simulate hook JSON input, and verify deny/ask/passthrough decisions. ```bash ./tests/run-tests.sh # all tests diff --git a/docs/deploy-process-demo.html b/docs/deploy-process-demo.html index 51b20a7..772eaf9 100644 --- a/docs/deploy-process-demo.html +++ b/docs/deploy-process-demo.html @@ -418,8 +418,8 @@

🤓 DeployNOPE

-

Deployment Process — the core 17-step workflow that all releases follow from staging to master to release

-
staging → master → release
+

Deployment Process — the core 17-step workflow that all releases follow from staging to production to release

+
staging → production → release
@@ -460,12 +460,12 @@

How It Works

🛡

Branch Protection

-

Direct pushes to staging, master, and development are blocked. All changes flow through release branches with controlled resets.

+

Direct pushes to staging, the production branch, and development are blocked. All changes flow through release branches with controlled resets.

🛑

Human Gates

-

Three mandatory human checkpoints: release readiness, staging validation, and master reset. No gate can be skipped or automated away.

+

Three mandatory human checkpoints: release readiness, staging validation, and production reset. No gate can be skipped or automated away.

🔒
@@ -475,7 +475,7 @@

Staging Contention

🔄

Cross-Repo Parity

-

Before promoting to master, version numbers are checked across all related repositories to prevent mismatched deployments.

+

Before promoting to production, version numbers are checked across all related repositories to prevent mismatched deployments.

📋
@@ -485,7 +485,7 @@

Release Manifests

Auto Post-Deploy

-

After master is updated, branches are synced, staging is cleared, Confluence notes are written, and health checks run automatically.

+

After the production branch is updated, branches are synced, staging is cleared, Confluence notes are written, and health checks run automatically.

@@ -498,7 +498,7 @@

Auto Post-Deploy

// --- Step definitions --- const ALL_STEPS = [ { id: 'merge-features', icon: '🔀', title: 'Merge feature branches into release branch', desc: 'All approved feature branches are merged into the release branch via fast-forward merges.', gate: false, tags: ['feature'] }, - { id: 'sync-master', icon: '🔄', title: 'Sync release branch with master', desc: 'Rebase or merge master into the release branch to pick up any hotfix commits.', gate: false, tags: ['feature', 'chore'] }, + { id: 'sync-master', icon: '🔄', title: 'Sync release branch with production', desc: 'Rebase or merge the production branch into the release branch to pick up any hotfix commits.', gate: false, tags: ['feature', 'chore'] }, { id: 'update-changelog', icon: '📝', title: 'Update changelog', desc: 'CHANGELOG.md is updated with all changes included in this release.', gate: false, tags: ['feature', 'hotfix'] }, { id: 'confirm-ready', icon: '📣', title: 'Confirm release branch ready', desc: 'Human reviews the release branch and confirms it is ready to proceed.', gate: true, tags: ['feature', 'hotfix', 'chore'] }, { id: 'contention-check', icon: '🔎', title: 'Staging contention check', desc: 'Three-point check: unreleased commits, staging/active tag, stale branch guard.', gate: false, tags: ['feature', 'hotfix', 'chore'] }, @@ -506,11 +506,11 @@

Auto Post-Deploy

{ id: 'reset-staging', icon: '🔃', title: 'Reset staging to release branch', desc: 'git reset --hard staging to the release branch HEAD, then force push.', gate: false, tags: ['feature', 'hotfix', 'chore'] }, { id: 'validate-staging', icon: '👁', title: 'Validate on staging', desc: 'Human verifies the release works correctly on the staging environment.', gate: true, tags: ['feature', 'hotfix', 'chore'] }, { id: 'cross-repo-parity', icon: '🔄', title: 'Cross-repo version parity check', desc: 'Verify version numbers match across all related repositories before promotion.', gate: false, tags: ['feature', 'hotfix'] }, - { id: 'reset-master', icon: '🛑', title: 'Reset master to match staging', desc: 'Human confirms master reset. Master is force-reset to match staging exactly.', gate: true, tags: ['feature', 'hotfix', 'chore'] }, - { id: 'codepipeline', icon: '⚙', title: 'Confirm CodePipeline healthy', desc: 'Wait for CI/CD pipeline to report all green after the master push.', gate: false, tags: ['feature', 'hotfix', 'chore'] }, + { id: 'reset-master', icon: '🛑', title: 'Reset production to match staging', desc: 'Human confirms production reset. The production branch is force-reset to match staging exactly.', gate: true, tags: ['feature', 'hotfix', 'chore'] }, + { id: 'codepipeline', icon: '⚙', title: 'Confirm CodePipeline healthy', desc: 'Wait for CI/CD pipeline to report all green after the production push.', gate: false, tags: ['feature', 'hotfix', 'chore'] }, { id: 'github-release', icon: '🎁', title: 'Create GitHub Release', desc: 'Tag and publish a GitHub Release with auto-generated release notes.', gate: false, tags: ['feature', 'hotfix', 'chore'] }, { id: 'write-manifest', icon: '📋', title: 'Write release manifest', desc: 'Immediately write the release manifest to the repository after the GitHub Release.', gate: false, tags: ['feature', 'hotfix', 'chore'] }, - { id: 'sync-branches', icon: '🔁', title: 'Sync staging + development with master', desc: 'Reset staging and development branches to match the new master.', gate: false, tags: ['feature', 'hotfix', 'chore'] }, + { id: 'sync-branches', icon: '🔁', title: 'Sync staging + development with production', desc: 'Reset staging and development branches to match the new production branch.', gate: false, tags: ['feature', 'hotfix', 'chore'] }, { id: 'clear-staging', icon: '🔓', title: 'Clear staging (remove tag)', desc: 'Delete the staging/active tag to release the staging lock for other deployments.', gate: false, tags: ['feature', 'hotfix', 'chore'] }, { id: 'confluence', icon: '📜', title: 'Write Confluence release notes', desc: 'Publish structured release notes to the team Confluence space.', gate: false, tags: ['feature', 'hotfix'] }, { id: 'post-deploy', icon: '✅', title: 'Post-deploy checks', desc: 'Automatic health checks, smoke tests, and deployment verification.', gate: false, tags: ['feature', 'hotfix', 'chore'] }, @@ -518,7 +518,7 @@

Auto Post-Deploy

const TERMINAL_COMMANDS = { 'merge-features': 'git checkout release/2.5.0 && git merge --ff-only feature/new-widget', - 'sync-master': 'git fetch origin master && git rebase origin/master', + 'sync-master': 'git fetch origin main && git rebase origin/main', 'update-changelog': 'echo "## 2.5.0" >> CHANGELOG.md && git add CHANGELOG.md', 'confirm-ready': '# HUMAN GATE: Awaiting confirmation that release branch is ready...', 'contention-check': 'git fetch origin && git tag -l "staging/active"', @@ -526,11 +526,11 @@

Auto Post-Deploy

'reset-staging': 'git checkout staging && git reset --hard release/2.5.0 && git push --force origin staging', 'validate-staging': '# HUMAN GATE: Validate release on staging environment...', 'cross-repo-parity': 'node scripts/check-parity.js --repos api,frontend,shared --version 2.5.0', - 'reset-master': '# HUMAN GATE: git checkout master && git reset --hard staging && git push --force origin master', + 'reset-master': '# HUMAN GATE: git checkout main && git reset --hard staging && git push --force origin main', 'codepipeline': 'aws codepipeline get-pipeline-state --name prod-pipeline | jq .stageStates[].latestExecution.status', - 'github-release': 'gh release create v2.5.0 --generate-notes --target master', + 'github-release': 'gh release create v2.5.0 --generate-notes --target main', 'write-manifest': 'echo \'{"version":"2.5.0","date":"2026-03-14","sha":"abc1234"}\' > manifests/2.5.0.json', - 'sync-branches': 'git checkout staging && git reset --hard master && git push --force origin staging development', + 'sync-branches': 'git checkout staging && git reset --hard main && git push --force origin staging development', 'clear-staging': 'git tag -d staging/active && git push origin :refs/tags/staging/active', 'confluence': 'node scripts/publish-confluence.js --version 2.5.0 --space DEPLOY', 'post-deploy': 'curl -sf https://api.example.com/health && echo "All checks passed"', diff --git a/docs/hooks-demo.html b/docs/hooks-demo.html index 3827b37..76fd9b2 100644 --- a/docs/hooks-demo.html +++ b/docs/hooks-demo.html @@ -470,7 +470,7 @@

Hook Overview Deny
Intercepts: gh pr create
-
Hard-blocks PRs targeting master, main, staging, or development. Only release branches are valid PR targets — no exceptions.
+
Hard-blocks PRs targeting the production branch (main/master), staging, or development. Only release branches are valid PR targets — no exceptions.
@@ -725,7 +725,7 @@

Deny

{ text: '', delay: 300 }, { text: '[hook] check-gh-pr-create.sh intercepting gh pr create...', delay: 600 }, { text: 'PR target: main', delay: 900 }, - { text: 'Checking protected branch list: master, main, staging, development', delay: 1200 }, + { text: 'Checking protected branch list: main, master, staging, development', delay: 1200 }, { text: 'Target branch "main" is protected.', delay: 1500 }, { text: '', delay: 1700 }, { text: '🛑 DENIED — PRs to main are not allowed.', delay: 1900 }, diff --git a/docs/index.html b/docs/index.html index 4b98222..ff1f7c7 100644 --- a/docs/index.html +++ b/docs/index.html @@ -207,7 +207,7 @@

Staging Contention Polling

🚀

Deployment Process

-

The full 17-step deployment pipeline — from feature merge through staging, master reset, release, and post-deploy. Three workflow variants.

+

The full 17-step deployment pipeline — from feature merge through staging, production reset, release, and post-deploy. Three workflow variants.

interactive step-through sim diff --git a/docs/preflight-postdeploy-demo.html b/docs/preflight-postdeploy-demo.html index 0cda60c..aca14f2 100644 --- a/docs/preflight-postdeploy-demo.html +++ b/docs/preflight-postdeploy-demo.html @@ -535,7 +535,7 @@

✅ Post-Deploy After Deploy
  • - Master reset completed (master = staging) + Production reset completed (production = staging)
  • @@ -563,7 +563,7 @@

    ✅ Post-Deploy After Deploy
  • - Branch alignment (master = staging = dev) + Branch alignment (production = staging = dev)
  • @@ -609,12 +609,12 @@

    Branch Alignment Report

    - master = staging + production = staging ✅ abc1234 — heads match - master = development + production = development ✅ abc1234 — heads match @@ -749,14 +749,14 @@

    Branch Alignment Report

    // ---- Post-Deploy ---- const postdeployLabels = [ - 'Master reset completed (master = staging)', + 'Production reset completed (production = staging)', 'GitHub Releases created', 'Release manifest written', 'Release branch merged into development', 'Branch protection re-enabled', 'Staging cleared (staging/active removed)', 'Confluence release notes written', - 'Branch alignment (master = staging = dev)', + 'Branch alignment (production = staging = dev)', 'Changelog updated', 'Production smoke test confirmed' ]; @@ -815,12 +815,12 @@

    Branch Alignment Report

    if (alignmentHealthy) { tbody.innerHTML = ` - master = staging + production = staging ✅ abc1234 — heads match - master = development + production = development ✅ abc1234 — heads match @@ -848,14 +848,14 @@

    Branch Alignment Report

    } else { tbody.innerHTML = ` - master = staging + production = staging ❌ - master: abc1234, staging: def5678 — 3 commits ahead + main: abc1234, staging: def5678 — 3 commits ahead - master = development + production = development ❌ - master: abc1234, dev: 9ab0cde — 5 commits behind + main: abc1234, dev: 9ab0cde — 5 commits behind Version parity diff --git a/docs/rollback-demo.html b/docs/rollback-demo.html index b4ed382..0fe4dca 100644 --- a/docs/rollback-demo.html +++ b/docs/rollback-demo.html @@ -681,11 +681,11 @@

    🔄 Cross-Repo Version Parity

    -

    🚀 Reset Master (Backend First)

    -

    Reset the master branch to the rollback target. Backend repos go first to avoid frontend pointing at non-existent API endpoints.

    +

    🚀 Reset Production (Backend First)

    +

    Reset the production branch to the rollback target. Backend repos go first to avoid frontend pointing at non-existent API endpoints.

    git reset --hard v2.2.0
    - git push origin master --force + git push origin main --force
    @@ -758,12 +758,12 @@

    💥 Frontend Cache-Bust Branch

    -

    🚨 Reset Master Directly Staging Skipped

    -

    Hard-reset master to the rollback target without staging validation. This is the dangerous step that emergency mode enables.

    +

    🚨 Reset Production Directly Staging Skipped

    +

    Hard-reset the production branch to the rollback target without staging validation. This is the dangerous step that emergency mode enables.

    # STAGING VALIDATION SKIPPED
    git reset --hard v2.2.0
    - git push origin master --force + git push origin main --force
    @@ -921,7 +921,7 @@

    Interactive Simulation

    { label: 'Validate on staging', icon: '👁', log: 'HUMAN GATE: Awaiting staging validation confirmation...', level: 'warn', gate: true }, { label: 'Staging validated', icon: '✅', log: 'User confirmed: staging looks good', level: 'success', gateResume: true }, { label: 'Cross-repo version parity check', icon: '🔄', log: 'All repos aligned at v2.2.0 ✓', level: 'success' }, - { label: 'Reset master (backend first)', icon: '🚀', log: 'git reset --hard v2.2.0 && git push origin master --force', level: 'info' }, + { label: 'Reset production (backend first)', icon: '🚀', log: 'git reset --hard v2.2.0 && git push origin main --force', level: 'info' }, { label: 'Annotate GitHub Release', icon: '📝', log: 'Marked v2.3.0 as rolled-back in GitHub Release', level: 'info' }, { label: 'Production smoke test', icon: '🚦', log: 'HUMAN GATE: Awaiting production smoke test confirmation...', level: 'warn', gate: true }, { label: 'Smoke test passed', icon: '✅', log: 'User confirmed: production is healthy', level: 'success', gateResume: true }, @@ -934,7 +934,7 @@

    Interactive Simulation

    { label: 'CONFIRM: Target version', icon: '🎯', log: 'HUMAN GATE: Confirm rollback target version...', level: 'warn', gate: true }, { label: 'Target confirmed: v2.2.0', icon: '🎯', log: 'User confirmed target: v2.2.0', level: 'success', gateResume: true }, { label: 'Frontend cache-bust branch', icon: '💥', log: 'Bumping package.json: 2.2.0 → 2.2.1 (cache-bust patch)', level: 'info' }, - { label: 'Reset master directly (STAGING SKIPPED)', icon: '🚨', log: 'DANGER: Skipping staging! git reset --hard v2.2.0 && git push origin master --force', level: 'error' }, + { label: 'Reset production directly (STAGING SKIPPED)', icon: '🚨', log: 'DANGER: Skipping staging! git reset --hard v2.2.0 && git push origin main --force', level: 'error' }, { label: 'CONFIRM: CodePipeline status', icon: '🚦', log: 'HUMAN GATE: Is CodePipeline deploying v2.2.0?', level: 'warn', gate: true }, { label: 'Pipeline confirmed green', icon: '🚦', log: 'User confirmed: CodePipeline deploying v2.2.0', level: 'success', gateResume: true }, { label: 'Mandatory post-rollback validation', icon: '👁', log: 'HUMAN GATE: Validate all critical paths in production...', level: 'warn', gate: true }, diff --git a/docs/staging-contention-demo.html b/docs/staging-contention-demo.html index e932662..9b6759e 100644 --- a/docs/staging-contention-demo.html +++ b/docs/staging-contention-demo.html @@ -538,7 +538,7 @@

    🛑 Staging Blocked

    Polling Offered

    Instead of stopping, DeployNOPE offers to poll every minute until staging clears.

    - # CronCreate: */1 * * * *
    + # Background poll: check every 60s
    git fetch origin
    git tag -l "staging/active"
    @@ -549,10 +549,10 @@

    Polling Offered

    Staging Cleared

    -

    The tag is gone, no unreleased commits remain. Cron job is cleaned up immediately via CronDelete.

    +

    The tag is gone, no unreleased commits remain. Background poll exits automatically.

    - CronDelete dee018eb
    - # Polling cancelled + STAGING_CLEAR
    + # Background poll completed
  • @@ -612,7 +612,7 @@

    Staging Environment

    [00:00] Agent A claims staging for 2.4.0
    [00:01] Agent B staging contention detected — staging/active exists
    -
    [00:01] Agent B polling started (CronCreate */1 * * * *)
    +
    [00:01] Agent B background poll started (every 60s)
    @@ -635,12 +635,12 @@

    Offer

    Poll

    -

    CronCreate checks staging/active every minute. Reports status each tick.

    +

    Background shell loop checks staging/active every 60 seconds. Reports status each tick.

    🗑

    Clean Up

    -

    CronDelete fires immediately when staging clears or the user cancels. No orphaned jobs.

    +

    Background poll exits automatically when staging clears. No orphaned processes.

    @@ -713,7 +713,7 @@

    Safe

    btn.disabled = true; addLog('Agent B', 'staging/active cleared!', 'success'); - addLog('Agent B', 'CronDelete dee018eb — polling cancelled', 'success'); + addLog('Agent B', 'STAGING_CLEAR — background poll completed', 'success'); setTimeout(() => { // Agent B claims @@ -763,7 +763,7 @@

    Safe

    terminal.innerHTML = `
    [00:00] Agent A claims staging for 2.4.0
    [00:01] Agent B staging contention detected — staging/active exists
    -
    [00:01] Agent B polling started (CronCreate */1 * * * *)
    +
    [00:01] Agent B background poll started (every 60s)
    `; startPolling(); diff --git a/docs/stale-config-demo.html b/docs/stale-config-demo.html index ae9ed79..2755ce1 100644 --- a/docs/stale-config-demo.html +++ b/docs/stale-config-demo.html @@ -936,10 +936,10 @@

    DeployNOPE · ` tag on every response | `CLAUDE.md` | Medium — visibility only | ## Passthrough Safety (Tested)