Files
solution-erp/scripts/spawn-model-audit.ps1
pqhuy1987 d07bbb98c5 [CLAUDE] Infra: W1 - 2 detector H24 + 3 script governance (eol-hygiene / wal-recovery 4-ca / spawn-model-audit)
Wave adap-6-broadcast W1 - 4 lane disjoint qua /hmw RUN-TRACE (wf_de36dea9-265, 4/4 lane 0-error).
Lead TU CHAY LAI ca 4 + tu fault-inject, KHONG tin return (gotcha #53 fire 2/2 @W0.6).

(1) governance-detectors.ps1 455->715
    - GAP-3 +2 token canonical: menu=54 (STATUS.md:19); policy = row CHUA co (W2 land)
      -> FAIL-LOUD + SKIP, khong im lang gia vo xanh.
    - Detector H24-1 title-freshness: anchor_patterns[] = MANG 2 dang (fix C2). Bat buoc co dang
      '**Status (post Session N <em-dash> YYYY-MM-DD):**' vi permission-matrix/SKILL.md
      (positive-control SONG) dung dang do va KHONG co 'Last updated'. Neu 1 regex => detector
      loai thang chinh positive-control cua no => W4 bat-kha-thi.
      NEO-PHAM-VI load-bearing: STATUS.md:6 chua 2026-08-01 / 1987-03-07 / 2026-20-42 (cat tu
      mark-id). Regex-ngay-bat-ky lay max => 2026-20-42 = ngay KHONG ton tai => moc-phai rac
      => trai<phai khong bao gio dung => 0 flag VINH VIEN ma van xanh.
    - Detector H24-2 carry-age INFORM-only: M doc tu agent-memory/memory-budget.json
      h24_cadence.light_every; thieu key => FAIL-LOUD, 0 so nhip hardcode.
    - Bug lane tu bat: PS ten bien CASE-INSENSITIVE => $M (cadence) == $m (loop match) => ghi de
      cadence => vo nhanh fail-loud. exit 0 CHE bug nay; chi stderr moi lo.
    - M3 scope giu: 4 hang so owner-da-ky KHONG dung (walLineCount 40 / ratio / maxGotcha / liveVariants).

(2) agent-frontmatter-eol-check.ps1 (NEW) - HYGIENE-only
    W0.6 spawn-probe @S121 do duoc CRLF-TOLERANT (agent CRLF 276 byte CR spawn an du 4 tang;
    token 45248 LF vs 45240 CRLF, lech 8 = chenh chu trong prompt) => gia thuyet 'CRLF giet
    registry' BI BAC => script ha cap xuong hygiene, khai that ca o header lan thong diep FLAG
    runtime. Scope-chat 37 file (agents/commands/skills), KHONG repo-wide (188 = duong-gia
    factory). Tu in GREEN-BUT-VACUOUS thay vi nhan cong.

(3) wal-recovery-test.ps1 (NEW) - 4/4 ca, 30 assert
    FIX #3-bis: bien re nhanh = DAY RANGE, KHONG phai NONWAL. Counterfactual chay luat cu =>
    tai hien DEADLOCK that voi rebase-exit=0 (hong ma bao thanh cong). Assert
    sut-has-no-NONWAL-param bang Get-Command reflection, KHONG grep => ne tu-tham-chieu (W0.4#1).

(4) spawn-model-audit.ps1 (NEW) - PA-2b
    FAIL-LOUD khi thieu TIER2_EXPECTED_FULL_ID (W2 chua land). Muc E liet 6 dieu no KHONG chung,
    gom precedence spawn-param vs frontmatter (CHUA TEST, defer phien lead=Fable) + fix#8a chua thu.

VERIFY LEAD TU DO (khong phai so sub khai):
  TOTAL FLAGS 42->49 (>=46) . permission-matrix/SKILL.md FIRE 3 dong (W4 kha thi; KHONG lane nao
  cham file do) . exit 0 + stderr 0 x4 . non-ASCII 0/4 . teeth 2/2: inject 276 CR -> FLAG, go -> 0;
  inject claude-opus-9-9 -> FLAG mismatch, sua -> het. Containment sach.

LOI LEAD @S121 (di vao adap-report, khong im):
  (a) San-1 lech: marker wf: dang ky SAU khi phong (luat = TRUOC).
  (b) Khang dinh fact-tren-dia ma KHONG do dia: lead phan 'backfill [carry:*] = orphan chua ai lam'
      -> SAI, da co 7 khoa tu 4727d16 (S119, da push). Co che: ra BANG WAVE thay khong ai duoc
      giao => suy ra chua lam. 'Khong wave nao so huu' != 'chua co tren dia'. Brief lead co 4 tien
      de sai (config path / acceptance ngay 07-14 vs that 07-15 / carry / do-dai); lane (1) do dia va va het.

GAP THAT thay claim sai (lane 1 tim, lead verify dia): carry-age vacuous vi CAU TRUC - 45
logic-segment nhung carry-lines=1 => streak==1 => 0 fire vinh vien ke ca sau W2. Lo = thieu nghi
thuc RE-STAMP moi phien => giao W3 session-end.md, KHONG phai W2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 12:47:55 +07:00

472 lines
25 KiB
PowerShell

<#
.SYNOPSIS
spawn-model-audit.ps1 - H23 PA-2b: spawn-model audit, resolved-vs-expected. INFORMATIONAL.
.DESCRIPTION
Owner-decision S119 PA-2 (runs/2026-07-15-S119-adap-6-broadcast/owner-decisions-15-07-2026.md:59-60)
splits "anchor the model version" in two, because anchoring AT SPAWN is NOT possible at SE
(the spawn param takes an enum alias only; SE has 0 full-id in any definition file - H8 all-inherit):
PA-2a .claude/workflows/hmw.js declares TIER2_EXPECTED_FULL_ID = 'claude-opus-4-8'.
PA-2b THIS script compares the model actually RESOLVED at spawn against that constant.
Drift => print FLAG => owner decides re-pin. NO enforcement (H23 section 2(4)).
Design rules (mirror scripts/governance-detectors.ps1):
(1) NO-API - reads files only. NEVER calls a model/API.
(2) FLAG-only - prints FLAGs, NEVER edits files.
(3) PowerShell 5.1, offline. ASCII-only script body (gotcha #30); target files read -Encoding UTF8.
(4) DETECT-only. Exit code ALWAYS 0 (audit/inform, never a build gate).
---------------------------------------------------------------------------
MEASURABLE vs NOT-MEASURABLE - read before trusting any line of this report
---------------------------------------------------------------------------
MEASURED (this script really reads it, offline, from disk):
* expected = the TIER2_EXPECTED_FULL_ID constant in hmw.js [section A]
* resolved = "message":{"model":"..."} in the per-lane spawn transcripts
<TranscriptRoot>/<slug>/<session>/subagents/workflows/wf_*/agent-*.jsonl
Verified present S119: 9 lanes 'claude-opus-4-8' + 2 lanes 'claude-fable-5'. [section B]
NOT MEASURABLE HERE - declared, never silently passed:
(i) FORWARD drift. This audit is RETROSPECTIVE: it reads what ALREADY resolved in recorded
runs. It cannot pre-verify what the NEXT spawn will resolve to. A clean report does NOT
promise the next spawn stays on the expected version. This is exactly why H23 section 2(4)
makes it informational, not enforcement.
(ii) PRECEDENCE vs a frontmatter HARD-PIN (the hub form: 17/17 'model: claude-opus-4-8').
UNTESTED at SE and UNTESTABLE here: SE has 0 hard-pinned agents (12/12 'model: inherit',
owner order S66/H8). Only the inherit case has evidence - see the next block.
(iii)Whether the spawn param REJECTS a full-id (fix #8a). Per harness docs the param is an enum
alias (sonnet|opus|haiku|fable); passing a full-id has NEVER been tried. Not tried here.
(iv) Documentation coverage. This measures EXPLICITNESS IN TRANSCRIPTS only. A clean report must
NOT be read as "the floor is fully applied" (owner-decisions:65, honesty declaration 3).
PRECEDENCE - spawn-param vs frontmatter 'inherit': MEASURED 2026-07-15, no longer "never tested".
Spec v2:271 recorded this as CHUA TUNG DUOC TEST, reasoning that a discriminating experiment
needs lead=Fable. That reasoning only covered the tier:'opus' direction. The discriminating
condition is simply spawn-param family != lead family - and the tier:'fable' direction
satisfies it under lead=Opus. It ALREADY RAN, unplanned, in run wf_cb964f83-331:
lead model = claude-opus-4-8 (290/290 records, session 3d9bec56, zero Fable)
agentType = reviewer (.claude/agents/reviewer.md:5 = 'model: inherit')
spawn param = fable (/fable-real escape-hatch, hmw.js:44 returns the alias)
RESOLVED = claude-fable-5 (2/2 lanes, 39 + 42 assistant records)
If frontmatter 'inherit' had won, resolved would be the lead's claude-opus-4-8. It was not.
=> the spawn param TAKES EFFECT on a frontmatter-'inherit' agent => the GAP#6 fix is NOT a no-op.
HONEST LIMIT: this does not prove 'inherit' LOSES a precedence contest. 'inherit' may simply mean
"defer to the caller", in which case there is no contest at all. Both readings give the same
operative answer (the param takes effect); this data cannot separate them, and does not try.
.PARAMETER RepoRoot
Repo root. Default = 2 levels up from this script (scripts/ -> repo root).
Every path derives from this, so -RepoRoot <temp tree> makes fault-injection possible.
.PARAMETER TranscriptRoot
Root of the CLI project transcripts. Default derives from $env:USERPROFILE (NOT a hardcoded
absolute path). The per-project folder name is derived from $RepoRoot, so a fault-inject tree
resolves to a slug with no transcripts and section B reports NO-SCOPE instead of faking a number.
.PARAMETER SessionId
Audit one specific session folder. Default = most recent session that HAS spawn transcripts.
Default-to-current is deliberate: scanning old history surfaces pre-convention runs, which is
noise, not a bug storm (owner-decisions:64, honesty declaration 2).
.EXAMPLE
powershell.exe -ExecutionPolicy Bypass -File scripts/spawn-model-audit.ps1
#>
param(
[string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path,
[string]$TranscriptRoot = (Join-Path $env:USERPROFILE '.claude\projects'),
[string]$SessionId = ''
)
$ErrorActionPreference = 'Continue'
# ---------------------------------------------------------------------------
# Owner-ratified expectation (S119 PA-2a, owner-decisions-15-07-2026.md:59).
# Deliberate 2-key design: the expected version is declared TWICE - once in hmw.js (PA-2a) and
# once here - so that a unilateral edit of ONE of them is FLAGGED instead of passing silently.
# Cost, stated plainly: an owner re-pin must touch BOTH files. That cost IS the check.
# NOT a fallback: if hmw.js has no constant, section A fails loud - it never borrows this value.
# ---------------------------------------------------------------------------
$OWNER_RATIFIED_TIER2_FULL_ID = 'claude-opus-4-8'
# Family that the 'opus' alias maps to. Alias locks the FAMILY, not the version (H23 section 2(2)):
# same family + different version = the drift this audit exists to catch.
$TIER2_FAMILY_PREFIX = 'claude-opus-'
# ---------------------------------------------------------------------------
# Helpers (shape mirrors scripts/governance-detectors.ps1:43-67)
# ---------------------------------------------------------------------------
$script:FlagCount = 0
function Write-Flag {
param(
[ValidateSet('HIGH', 'MED', 'LOW')] [string]$Severity,
[string]$Where,
[string]$Desc,
[string]$Resolve
)
$color = switch ($Severity) { 'HIGH' { 'Red' } 'MED' { 'Yellow' } default { 'Gray' } }
Write-Host ("[SPAWN-AUDIT] {0,-4} | {1} | {2} | resolve: {3}" -f $Severity, $Where, $Desc, $Resolve) -ForegroundColor $color
$script:FlagCount++
}
function Write-Section($title) {
Write-Host ''
Write-Host ("===== $title =====") -ForegroundColor Cyan
}
function Rel($full) {
$r = $full
if ($full.StartsWith($RepoRoot, [StringComparison]::OrdinalIgnoreCase)) {
$r = $full.Substring($RepoRoot.Length).TrimStart('\', '/')
}
return ($r -replace '\\', '/')
}
Write-Host ''
Write-Host '########## spawn-model-audit.ps1 - H23 PA-2b (INFORMATIONAL, exit 0 always) ##########' -ForegroundColor Cyan
Write-Host ("RepoRoot: {0}" -f $RepoRoot) -ForegroundColor DarkGray
# ===========================================================================
# SECTION A - PA-2a: the EXPECTED constant in hmw.js (STATIC, measurable)
# ===========================================================================
Write-Section 'A - expected constant (PA-2a, hmw.js)'
$hmwPath = Join-Path $RepoRoot '.claude\workflows\hmw.js'
$expected = $null
if (-not (Test-Path $hmwPath)) {
Write-Flag 'HIGH' (Rel $hmwPath) 'hmw.js not found - cannot resolve the expected model version' `
'run from a repo root that contains .claude/workflows/hmw.js'
}
else {
# STRUCTURAL anchor, NOT a loose string grep (W0.4 lesson 1 - self-reference).
# Capture the VALUE of the assignment. A bare grep for the version string is NOT acceptable
# evidence here: hmw.js:36 already contains the literal 'claude-opus-4-8' inside a COMMENT
# ("KHONG con demote-pin ..."), so a naive grep returns >=1 hit and passes VACUOUSLY even when
# the constant is absent or holds a wrong value. The naive count is printed below as a
# counter-example only - it is never used as evidence.
$RX_CONST = 'TIER2_EXPECTED_FULL_ID\s*=\s*[''"]([^''"]+)[''"]'
$constHits = @(Select-String -Path $hmwPath -Pattern $RX_CONST -Encoding UTF8 -AllMatches)
$naiveHits = @(Select-String -Path $hmwPath -Pattern ([regex]::Escape($OWNER_RATIFIED_TIER2_FULL_ID)) -Encoding UTF8)
Write-Host (" anti-vacuous note: naive grep '{0}' in hmw.js = {1} hit(s) [comment text] -> NOT used as evidence" -f `
$OWNER_RATIFIED_TIER2_FULL_ID, $naiveHits.Count) -ForegroundColor DarkGray
if ($constHits.Count -eq 0) {
# FAIL-LOUD. No hardcoded default is substituted: an audit that invents the value it is
# meant to verify would report a PASS about itself.
Write-Flag 'HIGH' (Rel $hmwPath) `
'expected constant missing (W2 chua land): TIER2_EXPECTED_FULL_ID not declared in hmw.js' `
("land PA-2a: add TIER2_EXPECTED_FULL_ID = '{0}' next to resolveModel() in hmw.js" -f $OWNER_RATIFIED_TIER2_FULL_ID)
Write-Host ' -> section B comparison will report SKIPPED (no expected value to compare against)' -ForegroundColor DarkGray
}
else {
$values = @()
foreach ($h in $constHits) {
foreach ($m in $h.Matches) { $values += $m.Groups[1].Value }
}
$distinctValues = @($values | Sort-Object -Unique)
Write-Host (" found {0} definition(s) of TIER2_EXPECTED_FULL_ID; distinct value(s): {1}" -f `
$values.Count, ($distinctValues -join ', '))
foreach ($h in $constHits) {
Write-Host (" {0}:{1}" -f (Rel $hmwPath), $h.LineNumber) -ForegroundColor DarkGray
}
if ($distinctValues.Count -gt 1) {
Write-Flag 'HIGH' (Rel $hmwPath) `
("conflicting definitions: TIER2_EXPECTED_FULL_ID declared with {0} different values [{1}]" -f `
$distinctValues.Count, ($distinctValues -join ' vs ')) `
'keep exactly ONE authoritative definition of TIER2_EXPECTED_FULL_ID'
}
$expected = $distinctValues[0]
# PASS/FAIL is COMPUTED from the measured value (W0.4 lesson 2 - never a label pasted
# next to a number that says otherwise).
if ($expected -cne $OWNER_RATIFIED_TIER2_FULL_ID) {
Write-Flag 'HIGH' (Rel $hmwPath) `
("mismatch vs owner-ratified expectation: hmw.js declares '{0}' but S119 PA-2a ratified '{1}'" -f `
$expected, $OWNER_RATIFIED_TIER2_FULL_ID) `
("set TIER2_EXPECTED_FULL_ID = '{0}', or have the owner re-ratify and update this script" -f $OWNER_RATIFIED_TIER2_FULL_ID)
}
else {
Write-Host (" [OK] hmw.js expected = '{0}' = owner-ratified (S119 PA-2a)" -f $expected) -ForegroundColor Green
}
if ($expected -match '\[1m\]') {
Write-Flag 'MED' (Rel $hmwPath) `
("expected value carries a '[1m]' context-window suffix: '{0}'" -f $expected) `
'drop the [1m] suffix - it is not part of the model id (gotcha #37)'
}
}
}
# ===========================================================================
# SECTION B - PA-2b: the RESOLVED model, from spawn transcripts (MEASURED)
# ===========================================================================
Write-Section 'B - resolved model (PA-2b, spawn transcripts)'
# Project folder name derives from $RepoRoot (path separators -> '-'), so a fault-inject tree
# resolves to a slug with no transcripts -> NO-SCOPE, never a faked measurement.
$slug = $RepoRoot -replace '[:\\/_]', '-'
$projDir = Join-Path $TranscriptRoot $slug
$dispatch = 0 # spawn lanes found (one agent-*.jsonl = one lane)
$explicit = 0 # lanes where a resolved model is actually recorded
$mismatch = 0 # lanes resolved to the tier-2 FAMILY but NOT the expected version = alias drift
$otherFam = 0 # lanes resolved outside the tier-2 family (escape-hatch / inherited lead)
$modelTally = @{}
Write-Host (" transcript root: {0}" -f $TranscriptRoot) -ForegroundColor DarkGray
Write-Host (" project slug : {0} (derived from RepoRoot)" -f $slug) -ForegroundColor DarkGray
if (-not (Test-Path $projDir)) {
Write-Host ' [NO-SCOPE] no transcript folder for this repo - nothing to measure, nothing claimed.' -ForegroundColor DarkGray
Write-Host ' (expected when running against a fault-inject / temp tree)' -ForegroundColor DarkGray
}
else {
$sessionDir = $null
if ($SessionId -ne '') {
$cand = Join-Path $projDir $SessionId
if (Test-Path $cand) { $sessionDir = Get-Item $cand }
else { Write-Flag 'MED' (Rel $cand) 'requested -SessionId not found' 'pass an existing session id, or omit to use the most recent' }
}
else {
$sessionDir = Get-ChildItem -Path $projDir -Directory -ErrorAction SilentlyContinue |
Where-Object { Test-Path (Join-Path $_.FullName 'subagents\workflows') } |
Sort-Object LastWriteTime -Descending | Select-Object -First 1
}
if ($null -eq $sessionDir) {
Write-Host ' [NO-SCOPE] no session with spawn transcripts under this project - nothing measured.' -ForegroundColor DarkGray
}
else {
Write-Host (" session scope : {0} (last write {1:yyyy-MM-dd HH:mm})" -f $sessionDir.Name, $sessionDir.LastWriteTime)
Write-Host ' scope = CURRENT session only, by design (old runs predate the convention; owner-decisions:64)' -ForegroundColor DarkGray
# STRUCTURAL anchor: the model key of an assistant record sits immediately inside
# "message":{ ... . Anchoring on the record shape (not a bare "model": grep) keeps quoted
# JSON inside message CONTENT - agents routinely paste transcript snippets - from being
# counted as if it were a real dispatch.
$RX_MODEL = '"message"\s*:\s*\{\s*"model"\s*:\s*"([^"]+)"'
$wfRoot = Join-Path $sessionDir.FullName 'subagents\workflows'
$laneFiles = @(Get-ChildItem -Path $wfRoot -Recurse -Filter 'agent-*.jsonl' -File -ErrorAction SilentlyContinue)
foreach ($lane in $laneFiles) {
$dispatch++
$hits = @(Select-String -Path $lane.FullName -Pattern $RX_MODEL -Encoding UTF8 -AllMatches)
$models = @()
foreach ($h in $hits) {
foreach ($m in $h.Matches) { $models += $m.Groups[1].Value }
}
$distinct = @($models | Sort-Object -Unique)
$wfName = Split-Path (Split-Path $lane.FullName -Parent) -Leaf
$agentType = '?'
$metaPath = ($lane.FullName -replace '\.jsonl$', '.meta.json')
if (Test-Path $metaPath) {
$metaTxt = Get-Content -Path $metaPath -Raw -Encoding UTF8
$mm = [regex]::Match($metaTxt, '"agentType"\s*:\s*"([^"]+)"')
if ($mm.Success) { $agentType = $mm.Groups[1].Value }
}
if ($distinct.Count -eq 0) {
Write-Host (" {0}/{1} [{2}] -> no model recorded (lane produced no assistant record)" -f `
$wfName, $lane.Name, $agentType) -ForegroundColor DarkGray
continue
}
$explicit++
foreach ($d in $distinct) {
if ($modelTally.ContainsKey($d)) { $modelTally[$d] = $modelTally[$d] + 1 }
else { $modelTally[$d] = 1 }
}
$verdict = 'other-family'
$inFamily = @($distinct | Where-Object { $_.StartsWith($TIER2_FAMILY_PREFIX) })
if ($null -eq $expected) {
$verdict = 'SKIPPED (no expected constant)'
}
elseif ($inFamily.Count -eq 0) {
$verdict = 'other-family (escape-hatch / inherited lead - not a drift signal)'
$otherFam++
}
else {
$bad = @($inFamily | Where-Object { $_ -cne $expected })
if ($bad.Count -gt 0) {
$mismatch++
$verdict = 'MISMATCH'
Write-Flag 'HIGH' ("{0}/{1}" -f $wfName, $lane.Name) `
("alias-drift: resolved '{0}' is in the tier-2 family but expected '{1}'" -f ($bad -join ', '), $expected) `
'owner decides re-pin (H23 section 2(2)); update TIER2_EXPECTED_FULL_ID once ratified'
}
else {
$verdict = 'match'
}
}
Write-Host (" {0}/{1} [{2}] -> {3} ({4} record(s)) : {5}" -f `
$wfName, $lane.Name, $agentType, ($distinct -join ', '), $models.Count, $verdict)
}
Write-Host ''
Write-Host (" COUNTS: {0} dispatch | {1} explicit | {2} mismatch" -f $dispatch, $explicit, $mismatch)
Write-Host ' (dispatch = lanes found | explicit = lanes with a model actually recorded | mismatch = tier-2-family lanes off the expected version)' -ForegroundColor DarkGray
if ($modelTally.Count -gt 0) {
Write-Host ' resolved model tally (lanes per model):'
foreach ($k in ($modelTally.Keys | Sort-Object)) {
Write-Host (" {0,-24} {1} lane(s)" -f $k, $modelTally[$k])
}
}
if ($null -eq $expected) {
Write-Host ' [SKIPPED] resolved-vs-expected NOT evaluated: section A found no expected constant.' -ForegroundColor Yellow
Write-Host ' This is NOT a pass. The lanes above are reported raw, uncompared.' -ForegroundColor Yellow
}
elseif ($dispatch -eq 0) {
Write-Host ' [NO-SCOPE] 0 lanes in scope - a 0-mismatch count here would be VACUOUS, not a pass.' -ForegroundColor Yellow
}
elseif ($mismatch -eq 0) {
Write-Host (" [OK] 0/{0} lane(s) drifted off expected '{1}' (retrospective only - says nothing about the NEXT spawn)" -f `
$explicit, $expected) -ForegroundColor Green
}
}
}
# ===========================================================================
# SECTION C - canonical cross-check: docs/STATUS.md, the Sub-agents row
# ===========================================================================
Write-Section 'C - canonical cross-check (STATUS.md Sub-agents row)'
# Row label read from the file, not guessed: docs/STATUS.md:24 = "| Sub-agents | **12** | ...".
# That row is the single-owner flip-chain canonical; hmw.js:34 and agents/README point at it.
$SUBAGENT_ROW_LABEL = 'Sub-agents'
$FLIPCHAIN_MARKER = 'CANONICAL single-owner flip-chain'
$statusPath = Join-Path $RepoRoot 'docs\STATUS.md'
if (-not (Test-Path $statusPath)) {
Write-Flag 'MED' (Rel $statusPath) 'docs/STATUS.md not found - cannot cross-check the roster canonical' `
'run against a repo root that contains docs/STATUS.md'
}
else {
$rowPat = '^\|\s*' + [regex]::Escape($SUBAGENT_ROW_LABEL) + '\s*\|\s*\*\*(\d+)'
$rowHit = Select-String -Path $statusPath -Pattern $rowPat -Encoding UTF8 | Select-Object -First 1
if ($null -eq $rowHit) {
Write-Flag 'MED' (Rel $statusPath) `
("canonical row '| {0} | **N** |' not found in STATUS.md CURRENT STATE table" -f $SUBAGENT_ROW_LABEL) `
'restore the Sub-agents row, or update this script if the canonical owner moved'
}
else {
$canonSub = [int]$rowHit.Matches[0].Groups[1].Value
Write-Host (" STATUS.md:{0} canonical {1} = {2}" -f $rowHit.LineNumber, $SUBAGENT_ROW_LABEL, $canonSub)
if ($rowHit.Line -notmatch [regex]::Escape($FLIPCHAIN_MARKER)) {
Write-Flag 'LOW' ("{0}:{1}" -f (Rel $statusPath), $rowHit.LineNumber) `
("the {0} row no longer carries the '{1}' marker" -f $SUBAGENT_ROW_LABEL, $FLIPCHAIN_MARKER) `
'restore the marker, or re-point this cross-check at the new canonical owner'
}
else {
Write-Host (" [OK] row carries the flip-chain canonical marker") -ForegroundColor Green
}
# disk cross-check: the canonical count must not itself be stale.
$agentDir = Join-Path $RepoRoot '.claude\agents'
if (Test-Path $agentDir) {
$diskAgents = @(Get-ChildItem -Path $agentDir -Filter *.md -File -ErrorAction SilentlyContinue |
Where-Object { $_.Name -ne 'README.md' })
Write-Host (" disk cross-check: .claude/agents/*.md minus README = {0}" -f $diskAgents.Count)
if ($diskAgents.Count -ne $canonSub) {
Write-Flag 'MED' (Rel $statusPath) `
("canonical-itself-stale: STATUS {0}=**{1}** but disk has {2} agent .md" -f `
$SUBAGENT_ROW_LABEL, $canonSub, $diskAgents.Count) `
("re-ground the STATUS.md {0} row to {1}" -f $SUBAGENT_ROW_LABEL, $diskAgents.Count)
}
else {
Write-Host (" [OK] canonical {0} matches disk ({1})" -f $canonSub, $diskAgents.Count) -ForegroundColor Green
}
}
}
}
# ===========================================================================
# SECTION D - H8 all-inherit: agent frontmatter
# ===========================================================================
Write-Section 'D - frontmatter all-inherit (H8)'
# GLOB, never a hardcoded roster size: the roster moves 12 -> 14 at W2, and a hardcoded count
# would self-age into a false FAIL the moment it lands (fix #9(h) / R2-M3).
$agentDir = Join-Path $RepoRoot '.claude\agents'
if (-not (Test-Path $agentDir)) {
Write-Flag 'MED' (Rel $agentDir) '.claude/agents not found - cannot check frontmatter' `
'run against a repo root that contains .claude/agents'
}
else {
$agentFiles = @(Get-ChildItem -Path $agentDir -Filter *.md -File -ErrorAction SilentlyContinue |
Where-Object { $_.Name -ne 'README.md' })
$inheritCount = 0
foreach ($a in $agentFiles) {
$lines = @(Get-Content -Path $a.FullName -Encoding UTF8)
# frontmatter = the block between the first '---' and the next '---'
$modelVal = $null
$modelLine = 0
$inFm = $false
for ($i = 0; $i -lt $lines.Count; $i++) {
$t = $lines[$i].Trim()
if ($t -eq '---') {
if (-not $inFm) { $inFm = $true; continue }
break
}
if ($inFm -and $t -match '^model\s*:\s*(.+?)\s*$') {
$modelVal = $Matches[1].Trim()
$modelLine = $i + 1
}
}
if ($null -eq $modelVal) {
Write-Flag 'MED' (Rel $a.FullName) `
'no "model:" key in frontmatter - the lane falls back to a harness default, NOT inherit' `
'add "model: inherit" to the agent frontmatter (H8 all-inherit)'
}
elseif ($modelVal -cne 'inherit') {
Write-Flag 'MED' ("{0}:{1}" -f (Rel $a.FullName), $modelLine) `
("frontmatter pins 'model: {0}' - H8 says every agent inherits the lead" -f $modelVal) `
'set "model: inherit", or have the owner ratify the pin and record it in STATUS.md Sub-agents'
}
else {
$inheritCount++
}
}
# verdict COMPUTED from the counts, never a pasted label (W0.4 lesson 2)
Write-Host (" frontmatter: {0}/{1} agent .md carry 'model: inherit'" -f $inheritCount, $agentFiles.Count)
if ($agentFiles.Count -eq 0) {
Write-Host ' [NO-SCOPE] 0 agent .md found - a clean result here would be VACUOUS.' -ForegroundColor Yellow
}
elseif ($inheritCount -eq $agentFiles.Count) {
Write-Host (" [OK] all-inherit holds ({0}/{0})" -f $agentFiles.Count) -ForegroundColor Green
}
}
# ===========================================================================
# SECTION E - declared limits (printed EVERY run, so no report can over-read)
# ===========================================================================
Write-Section 'E - what this report does NOT say'
Write-Host ' 1. SPOT-CHECK BY CONVENTION, NOT ENFORCEMENT. Nothing here blocks a spawn (H23 section 2(4)).'
Write-Host ' 2. CURRENT SESSION ONLY. Older runs predate the convention; scanning them yields noise, not a bug storm.'
Write-Host ' 3. NOT "the floor is applied". This measures explicitness in transcripts, never documentation coverage.'
Write-Host ' 4. RETROSPECTIVE ONLY. It reads models that ALREADY resolved; it cannot pre-verify the NEXT spawn.'
Write-Host ' 5. NOT TESTED - spawn param vs a frontmatter HARD-PIN: SE has 0 hard-pinned agents (12/12 inherit),'
Write-Host ' so that precedence case has no evidence here. The inherit case IS measured (see header, wf_cb964f83-331).'
Write-Host ' 6. NOT TRIED - whether the spawn param rejects a full-id (fix #8a). Docs say enum alias; never attempted.'
# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
Write-Section 'Summary'
Write-Host ("TOTAL FLAGS: {0}" -f $script:FlagCount) -ForegroundColor Cyan
Write-Host 'NOTE: informational audit. Exit 0 always (never fails a build). FLAGs are advisory - owner decides re-pin.' -ForegroundColor DarkGray
exit 0