[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>
This commit is contained in:
471
scripts/spawn-model-audit.ps1
Normal file
471
scripts/spawn-model-audit.ps1
Normal file
@ -0,0 +1,471 @@
|
||||
<#
|
||||
.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
|
||||
Reference in New Issue
Block a user