Playbook: Repo Consistency Audit

When to run

  • After onboarding a new repo (verify new-repo-setup.md checklist was fully completed)
  • Monthly — first Monday of the month
  • After any bulk change to .claude/ infrastructure in p24-infra (settings/commands template change)
  • When a Claude session reports “missing skill” or permission errors in a project VS Code window

Repos in scope

RepoLocal cloneStack
radieu/p24-infraC:\code_2026\p24-infraDevOps / infra
radieu/et-operational-platformd:\code_2026\et-oper\et-operational-platformNext.js
radieu/Art-AgencyC:\code_2026\Art-AgencyNext.js
p24-infra/brandpilotC:\code_2026\brandpilot\brandpilotNext.js (org: p24-infra, not radieu)
radieu/whatsup-android-chat-pullerC:\code_2026\whatsup-android-chat-pullerKotlin + Python
radieu/radekkonarski-personal-brandC:\code_2026\radekkonarski-personal-brandContent / n8n
radieu/amazon-kdp-tangod:\code_2026\amazon-kdp-tangoPython CLI

Audit script

Run this entire block in PowerShell to get a full snapshot:

$repos = @(
  @{ name="p24-infra";                    path="C:\code_2026\p24-infra";                                  gh="radieu/p24-infra" },
  @{ name="et-operational-platform";      path="d:\code_2026\et-oper\et-operational-platform";            gh="radieu/et-operational-platform" },
  @{ name="Art-Agency";                   path="C:\code_2026\Art-Agency";                                 gh="radieu/Art-Agency" },
  @{ name="brandpilot";                   path="C:\code_2026\brandpilot\brandpilot";                      gh="radieu/brandpilot" },
  @{ name="whatsup-android-chat-puller";  path="C:\code_2026\whatsup-android-chat-puller";                gh="radieu/whatsup-android-chat-puller" },
  @{ name="radekkonarski-personal-brand"; path="C:\code_2026\radekkonarski-personal-brand";               gh="radieu/radekkonarski-personal-brand" },
  @{ name="amazon-kdp-tango";             path="d:\code_2026\amazon-kdp-tango";                           gh="radieu/amazon-kdp-tango" }
)
 
$REQUIRED_LABELS  = @("bug","enhancement","plan","human-action","queued","ai-review-queued","ai-review-rejected","patch")
$REQUIRED_MILESTONES = @("Triage","Design","In Progress","Review","Staging","Main")
$REQUIRED_COMMANDS = @("sr.md","commit.md","create-pr.md")
 
foreach ($r in $repos) {
  $p = $r.path
  Write-Host "`n=== $($r.name) ===" -ForegroundColor Cyan
 
  # --- LOCAL CHECKS ---
  $settingsJson     = Test-Path "$p\.claude\settings.json"
  $settingsLocal    = Test-Path "$p\.claude\settings.local.json"
  $claudeMd         = Test-Path "$p\CLAUDE.md"
  $gitattributes    = Test-Path "$p\.gitattributes"
  $hasSopsLfRule    = if ($gitattributes) { (Get-Content "$p\.gitattributes") -match "eol=lf" } else { $false }
  $cmdDir           = "$p\.claude\commands"
  $commands         = if (Test-Path $cmdDir) { (Get-ChildItem $cmdDir -Filter "*.md").Name } else { @() }
 
  if (-not $settingsJson)  { Write-Host "  [CRITICAL] .claude/settings.json MISSING" -ForegroundColor Red }
  if ($settingsLocal -and -not $settingsJson) { Write-Host "  [WARN]     has settings.local.json but not settings.json — not committed, agents get no auto-approvals" -ForegroundColor Yellow }
  if (-not $claudeMd)      { Write-Host "  [CRITICAL] CLAUDE.md MISSING" -ForegroundColor Red }
  if (-not $gitattributes) { Write-Host "  [WARN]     .gitattributes MISSING (risk: CRLF in scripts/SOPS files)" -ForegroundColor Yellow }
  elseif (-not $hasSopsLfRule) { Write-Host "  [WARN]     .gitattributes exists but no eol=lf rule" -ForegroundColor Yellow }
 
  foreach ($cmd in $REQUIRED_COMMANDS) {
    if ($commands -notcontains $cmd) {
      Write-Host "  [WARN]     .claude/commands/$cmd MISSING — /$(($cmd -replace '\.md$','')) unavailable in VS Code" -ForegroundColor Yellow
    }
  }
  if ($commands.Count -gt 0) { Write-Host "  [OK]       $($commands.Count) commands: $($commands -join ', ')" -ForegroundColor Green }
 
  # --- GITHUB CHECKS ---
  $defaultBranch = gh repo view $r.gh --json defaultBranchRef --jq ".defaultBranchRef.name" 2>$null
  $labels        = gh label list --repo $r.gh --json name --jq "[.[].name]" 2>$null | ConvertFrom-Json
  $milestones    = gh api "repos/$($r.gh)/milestones?state=all&per_page=50" --jq "[.[].title]" 2>$null | ConvertFrom-Json
 
  if ($defaultBranch -eq "master") { Write-Host "  [WARN]     default branch is 'master' — should be 'main' or 'dev'" -ForegroundColor Yellow }
  elseif ($defaultBranch -eq "main" -and $r.name -ne "p24-infra") { Write-Host "  [INFO]     default branch is 'main' (consider 'dev' for PR-first workflow)" -ForegroundColor DarkYellow }
 
  foreach ($lbl in $REQUIRED_LABELS) {
    if ($labels -notcontains $lbl) { Write-Host "  [WARN]     label '$lbl' MISSING" -ForegroundColor Yellow }
  }
 
  foreach ($ms in $REQUIRED_MILESTONES) {
    if ($milestones -notcontains $ms) { Write-Host "  [WARN]     milestone '$ms' MISSING" -ForegroundColor Yellow }
  }
 
  # Detect duplicate/legacy milestones
  $dupes = $milestones | Group-Object { $_.ToLower() } | Where-Object { $_.Count -gt 1 }
  foreach ($d in $dupes) { Write-Host "  [BUG]      duplicate milestones (case): $($d.Group -join ', ')" -ForegroundColor Red }
 
  $legacyMs = $milestones | Where-Object { $_ -in @("triage","To Review","Done","Dev","Blocked","pending-for-ai","ai-processing","rc-testing","human-todo","RC","rc1","RC1") }
  foreach ($lm in $legacyMs) { Write-Host "  [INFO]     legacy/non-standard milestone: '$lm'" -ForegroundColor DarkYellow }
}

Gap taxonomy

CRITICAL — blocks agent operation

  • Missing CLAUDE.md — agent has no project context; will hallucinate conventions
  • Missing .claude/settings.json — every tool call prompts; skills fail mid-execution

WARN — degrades developer experience

  • Missing required command (sr.md, commit.md, create-pr.md) — skill unavailable in VS Code
  • Missing standard label — issue pipeline skills fail or silently skip labelling
  • Missing standard milestone — create-pr.md / worker fails to set milestone
  • .gitattributes missing or no eol=lf — CRLF corruption risk for shell scripts and SOPS files on Windows
  • settings.local.json instead of settings.json — un-committed; new clones / CI agents have no auto-approvals

BUG — data corruption

  • Duplicate milestones (case-variant e.g. triage + Triage) — worker scripts that match by name will hit wrong milestone

INFO — tech debt

  • Default branch master — old convention; rename to main
  • Default branch main in app repos — p24-infra workflow expects dev as integration branch for PRs
  • Legacy milestones left over from old pipeline versions

Remediation quick-reference

Add missing .claude/settings.json

See docs/playbooks/new-repo-setup.md §Step 5b — copy template, adjust stack-specific entries.

Add missing command

$target = "C:\code_2026\<repo>\.claude\commands"
Copy-Item "C:\code_2026\p24-infra\.claude\commands\sr.md"     "$target\sr.md"
Copy-Item "C:\code_2026\p24-infra\.claude\commands\commit.md" "$target\commit.md"
# create-pr.md: use p24-infra version only if repo uses the same Projects board
# otherwise use whatsup-android-chat-puller version as template
Copy-Item "C:\code_2026\whatsup-android-chat-puller\.claude\commands\create-pr.md" "$target\create-pr.md"

Add missing label

$repo = "radieu/<repo>"
gh label create "plan"              --repo $repo --color "5319E7" --description "Design/planning issue"
gh label create "human-action"      --repo $repo --color "fbca04" --description "Waiting for human decision or action"
gh label create "queued"            --repo $repo --color "0e8a16" --description "Queued for implementation"
gh label create "ai-review-queued"  --repo $repo --color "0075ca" --description "PR queued for AI auto-review"
gh label create "ai-review-rejected" --repo $repo --color "d93f0b" --description "PR rejected by AI review"
gh label create "patch"             --repo $repo --color "e4e669" --description "Small fix or tweak"

Add missing standard milestones

$repo = "radieu/<repo>"
foreach ($ms in @("Triage","Design","In Progress","Review","Staging","Main")) {
  gh api "repos/$repo/milestones" -X POST -f title="$ms" 2>$null | Out-Null
}

Fix duplicate milestones

# List all, close the lower-case/legacy duplicate manually (cannot delete milestones via gh cli — use API)
$repo = "radieu/<repo>"
$dup = gh api "repos/$repo/milestones?state=all" --jq "[.[] | select(.title == \"triage\")] | .[].number" 2>$null
gh api "repos/$repo/milestones/$dup" -X PATCH -f state=closed 2>$null

Add gitattributes LF rule

$path = "C:\code_2026\<repo>"
$rule = "`n# Force LF for shell scripts and SOPS files`n*.sh text eol=lf`nsecrets/*.env.sops text eol=lf`n"
[System.IO.File]::WriteAllText("$path\.gitattributes",
  (Get-Content "$path\.gitattributes" -Raw -ErrorAction SilentlyContinue) + $rule,
  [System.Text.UTF8Encoding]::new($false))

Rename default branch master → main

$repo = "radieu/<repo>"
$local = "d:\code_2026\<repo>"
git -C $local checkout -b main master
git -C $local push origin main
gh api "repos/$repo" -X PATCH -f default_branch=main
git -C $local push origin --delete master

Findings snapshot — 2026-06-28

Reposettings.jsonsr.mdcommit.mdcreate-pr.mdgitattributesdefault branchlabel gapsmilestone issues
p24-infra✅✅✅✅✅main (by design)plan, queued, ai-review-queuednone
et-operational-platform✅✅✅✅❌mainplan, human-action, queued, ai-review-queuedmany legacy + duplicates
Art-Agency❌ (has settings.local.json)✅✅✅❌mainplan, queued, ai-review-queuedcorruption: “System.Collections.Hashtable.title” milestone
brandpilot✅❌✅✅❌?plan, queued, ai-review-queuedunknown
whatsup-android-chat-puller✅✅✅✅❌dev ✅ai-review-queuednone
radekkonarski-personal-brand✅❌❌❌✅mainplan, queued, ai-review-queuednone
amazon-kdp-tango✅❌❌❌❌master ❌plan, human-action, queued, ai-review-queuedonly has “RC”

Priority fix list (from findings)

P1 — Critical:

  • Art-Agency: rename settings.local.json → settings.json (commit it) — or create fresh settings.json

P2 — Warn, blocks skills:

  • brandpilot: add sr.md
  • radekkonarski-personal-brand: add sr.md, commit.md, create-pr.md
  • amazon-kdp-tango: add sr.md, commit.md, create-pr.md

P2 — Data bugs:

  • et-operational-platform: close/delete legacy milestones (triage duplicate, To Review, Done, Blocked, pending-for-ai, ai-processing, rc-testing, Dev, human-todo, rc1, RC)
  • Art-Agency: delete corrupted System.Collections.Hashtable.title milestone

P3 — Standards:

  • All repos except p24-infra + whatsup: add standard labels (plan, queued, ai-review-queued, ai-review-rejected, patch)
  • All repos except p24-infra + radekkonarski-brand: add .gitattributes with LF rule
  • amazon-kdp-tango: rename default branch master → main; add full standard milestone set
  • et-operational-platform, Art-Agency, radekkonarski-personal-brand: set default branch to dev

Domain 9 — Live-system-state playbook spot-check

This is the quarterly (90d) check from compliance-audit-policy.md Domain 9: “spot-check 10% of playbooks against live system state.” Unlike the repo-config audit script above (which only reads GitHub + local clones), this check compares what a playbook says the running system looks like against what the production servers actually report — so it requires SSH / Docker / psql / mongosh access and must run on a worker that has it.

Routing (#7296). A dev-coder / dev-issue worker has no production server access and structurally cannot run this check — which is why it was deferred two consecutive quarters. The [Audit Due] issue for audit_type=process is therefore labelled infra-task by scripts/compliance-audit-due-check.py, so it reroutes to a bms-4 infra-task worker (see worker-issue.md Step 0b-ROLE). Run this section from that worker (server SSH via docs/playbooks/server-ssh-connectivity.md). Read secrets by key NAME only — never print a value.

Procedure

  1. Pick the 10% sample. List every .md under docs/playbooks/ and select every 10th file by sorted name (same sampling the doc-consistency pass uses), skipping playbooks with no live-state claims (pure process/policy docs). Prefer playbooks whose steps assert concrete server facts.

  2. For each sampled playbook, diff its documented facts against live command output. The table below maps the common playbook claim types to the authoritative live command to diff against. A mismatch is a PARTIAL/FAIL finding → file a [Compliance Gap] issue naming the playbook, the documented value, and the live value.

    Playbook claims…Live command to diff againstServer(s)
    Server IP / role (e.g. CLAUDE.md §Server IPs, SSH playbooks)hostname -I, hostnamethe named node
    A container / compose service name or that it is runningdocker ps --format '{{.Names}}\t{{.Status}}'host running that stack
    A compose file path / service listdocker compose -f <path> config --servicesmonitoring → vps-i1, etc.
    A systemd unit exists / is enabled (e.g. p24-queue-dispatcher.service, crons)systemctl is-active <unit> / systemctl is-enabled <unit>the named node
    A cron is installed (e.g. claude-token-refresh)test -f /etc/cron.d/<name> && grep -c <cmd> /etc/cron.d/<name>the named node
    A UFW allowlist / open port (e.g. MongoDB 27017 on bms-2/3)ufw status numberedthe named node
    An env_file / .env path a service readstest -f <path> && echo exists (never cat)the named node
    MongoDB replica-set topology (PRIMARY/arbiter members)mongosh --quiet --eval 'rs.status().members.map(m=>m.name+":"+m.stateStr)' (conn string from SOPS, key name only)bms-2
    A DB table / view referenced by ops (dev_r_services etc.)SELECT 1 FROM <rel> LIMIT 1 via psql / PostgRESTSupabase
    An OS / package version claimlsb_release -ds, docker --versionthe named node
  3. Record the result in dev_r_compliance_audits (policy §Audit Execution Process Step 3): one row, audit_type='process', result = pass (all sampled playbooks matched live state), partial (some drift, gaps filed), or deferred (a required access was genuinely unavailable — name it in summary). Set next_audit_due = today + 90d. Never print secret values in summary.

  4. Close the [Audit Due] issue and link every [Compliance Gap] issue filed.

Notes

  • Destructive checks stay out of scope for the autonomous worker (DR restore, reboot). Record deferred with a human-action issue naming the manual step — mirror how the #1972 rows handle D3.
  • This is complementary to the doc-internal consistency pass (broken-link + decommissioned-tool scan) a dev-coder worker can do; a full Domain 9 audit is both parts. The routing above ensures the live-state part is no longer silently skipped.

Escalation

If audit script fails with authentication error: gh auth status — ensure authenticated as radieu. If milestone deletion fails via API: delete manually in GitHub Settings → Milestones. If .gitattributes change triggers unexpected diffs: run git add --renormalize . once and commit.