All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m30s
- NHOM A restart-verify: A1 tier:'fable' tren reviewer pin-opus -> resolved claude-fable-5 (spawn-param THANG); A2 no-tier -> claude-opus-4-8 DU lead=Fable (pin an runtime); A3 autoMemory WRITE-path OK (project_h23_spawn_precedence.md moi). - B1 sweep all-inherit floor-song: 15 dong/5 file + 3 cung-lop lot-pattern + 5 dong H23-stale o 3 command-file (H1 bat) -> VA HET; spawn-model-audit Section-D flush H8->S124 + effort-check (fault-inject 2-chieu PASS, 14 duong-gia -> 0); governance-detectors C3 seed +worker-tier-pin; re-grep 0 floor-song; suite 45 = baseline 0-new. - vocab-alias-map §3 canonical flip -> worker-tier-pin (all-inherit/two-tier = 2 alias cu); STATUS:6 FROZEN-diary label (het H24-3 lag); STATUS:25 + agents/README:5 sync. - Email hub h23-precedence-test-result stamp 945cf3ad (reviewer-gate FAIL bat 3 loi truoc stamp -> gotcha #83 aggregate-cumulative) + _index cung luot. - B3/B4 owner-gated: soan b3-b4-owner-gated-proposals.md CHO ANH (mega-line retier + budget:74 wording) — 0 apply. - Closeout: harvest 4 lane + 2 monitor (H2 GATE PASS 5/5, 0/29 orphan; H1 3/4 clean + 5-spot fix) + curate moved-not-cut reviewer/inv-cb + carry disposition (DONG 2: restart-s124, h23-fable-test-postrestart; MO re-stamp 9) + H24 counter=5 + pilot H22 D5 (pause-roundtrip + hook-fire re-verified; cross-model PENDING). - Governance/tooling-only: 0 prod-code, 0 mig, test 509 GIU, bundle FROZEN. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
502 lines
27 KiB
PowerShell
502 lines
27 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 - MEASURED at SE 2026-07-16 (S126), after S124
|
|
re-pinned 14/14 agents to 'model: opus' (ALIAS pin): spawn param 'fable' on agentType
|
|
reviewer (pin 'opus') RESOLVED claude-fable-5 (run wf_f960dae2-fa0, 3 records); a
|
|
control lane with NO override resolved claude-opus-4-8 under lead=Fable => the pin
|
|
itself works at runtime AND the spawn param BEATS it. Caveats: alias-pin only (the
|
|
hub form pins FULL-ID 'claude-opus-4-8' 17/17 - NOT covered by this measurement);
|
|
retrospective - says nothing about future spawns. Older 'inherit' evidence: below.
|
|
(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.
|
|
UPDATE 2026-07-16 (S126): the hard-pin contest HAS now been measured - see NOT-MEASURABLE (ii)
|
|
above, which flipped to MEASURED. Param 'fable' beat frontmatter 'model: opus' on a real lane.
|
|
|
|
.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 - S124 worker-tier-pin: agent frontmatter (model: opus + effort: max)
|
|
# (was: H8 all-inherit - superseded by owner-decision S124 2026-07-15, adap erratum-h8)
|
|
# ===========================================================================
|
|
Write-Section 'D - frontmatter worker-tier-pin (S124)'
|
|
|
|
# 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' })
|
|
$pinCount = 0
|
|
$effortOkCount = 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
|
|
$effortVal = $null
|
|
$effortLine = 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 ($inFm -and $t -match '^effort\s*:\s*(.+?)\s*$') {
|
|
$effortVal = $Matches[1].Trim()
|
|
$effortLine = $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 the worker-tier pin' `
|
|
'add "model: opus" to the agent frontmatter (S124 worker-tier-pin)'
|
|
}
|
|
elseif ($modelVal -cne 'opus') {
|
|
Write-Flag 'MED' ("{0}:{1}" -f (Rel $a.FullName), $modelLine) `
|
|
("frontmatter has 'model: {0}' - S124 worker-tier-pin expects every agent pinned 'model: opus'" -f $modelVal) `
|
|
'set "model: opus", or have the owner ratify the change and record it in STATUS.md Sub-agents'
|
|
}
|
|
else {
|
|
$pinCount++
|
|
}
|
|
|
|
if ($null -eq $effortVal) {
|
|
Write-Flag 'MED' (Rel $a.FullName) `
|
|
'no "effort:" key in frontmatter - S124 worker-tier-pin expects "effort: max" alongside the model pin' `
|
|
'add "effort: max" to the agent frontmatter (S124 worker-tier-pin)'
|
|
}
|
|
elseif ($effortVal -cne 'max') {
|
|
Write-Flag 'MED' ("{0}:{1}" -f (Rel $a.FullName), $effortLine) `
|
|
("frontmatter has 'effort: {0}' - S124 worker-tier-pin expects 'effort: max'" -f $effortVal) `
|
|
'set "effort: max", or have the owner ratify the change and record it in STATUS.md Sub-agents'
|
|
}
|
|
else {
|
|
$effortOkCount++
|
|
}
|
|
}
|
|
|
|
# verdict COMPUTED from the counts, never a pasted label (W0.4 lesson 2)
|
|
Write-Host (" frontmatter: {0}/{1} agent .md carry 'model: opus' and {2}/{1} carry 'effort: max' (S124 worker-tier-pin)" -f `
|
|
$pinCount, $agentFiles.Count, $effortOkCount)
|
|
if ($agentFiles.Count -eq 0) {
|
|
Write-Host ' [NO-SCOPE] 0 agent .md found - a clean result here would be VACUOUS.' -ForegroundColor Yellow
|
|
}
|
|
elseif (($pinCount -eq $agentFiles.Count) -and ($effortOkCount -eq $agentFiles.Count)) {
|
|
Write-Host (" [OK] worker-tier-pin holds ({0}/{0} model + {0}/{0} effort)" -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. TESTED 2026-07-16 (S126) - spawn param vs frontmatter HARD-PIN: the param WON (reviewer pinned opus +'
|
|
Write-Host ' spawn fable -> claude-fable-5; no-override control -> claude-opus-4-8 under lead=Fable). ALIAS-pin only;'
|
|
Write-Host ' the full-id-pin form (hub) is still untested here. Inherit-case evidence: 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
|