All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m30s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
340 lines
17 KiB
PowerShell
340 lines
17 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
agent-frontmatter-eol-check.ps1 - HYGIENE-only EOL check for agent/command/skill frontmatter.
|
|
NOT a fatal-defect guard. The "CRLF kills the agent registry" claim was REFUTED by experiment
|
|
(S121 W0.6) and this script must not be read, cited, or gated on as if it enforced that claim.
|
|
|
|
.DESCRIPTION
|
|
WHAT IT DOES
|
|
Enforces the .gitattributes policy line '* text=auto eol=lf' (repo root) on the frontmatter
|
|
of the rule-set files:
|
|
.claude/agents/*.md
|
|
.claude/commands/*.md
|
|
.claude/skills/**/SKILL.md
|
|
It reads RAW BYTES and counts CR (0x0D) inside the YAML frontmatter block.
|
|
|
|
WHAT IT IS NOT - the honest grade of this check (read before trusting it)
|
|
The S121 W0.6 spawn-probe MEASURED the opposite of the assumption that motivated this
|
|
script. A fully CRLF-ised agent file (276 CR bytes) spawned CORRECTLY through all four
|
|
stages: discovery / parse / spawn / execute. Token accounting was 45248 (LF) vs 45240
|
|
(CRLF); that 8-token delta is explained by wording differences in the prompt, not by EOL.
|
|
Verdict: CRLF-TOLERANT. The "LF-only frontmatter reader" hypothesis was REFUTED.
|
|
=> This is therefore HYGIENE, not defect-prevention. No CR finding produced here is known
|
|
to break anything. Do not describe it as fatal. Do not gate a build on it.
|
|
|
|
WHY IT STILL EXISTS - the scope limit of that refutation
|
|
The W0.6 measurement covers: BEHAVIOUR level only / .claude/agents/ only / ONE win32 build.
|
|
AXIS limit (S129, hub erratum 2026-07-16 4-cell matrix): W0.6 was an EOL-only A/B - the
|
|
description-quote format was held constant - so it establishes CRLF-vs-LF spawn tolerance
|
|
only, and says NOTHING about the description-colon-quote axis of the hub death-matrix.
|
|
Do not cite it as evidence for or against quoting being load-bearing.
|
|
NOT covered: .claude/commands/, .claude/skills/, hook .ps1 readers, any other build or
|
|
platform. LF hygiene on those surfaces remains unverified-by-experiment, and the
|
|
.gitattributes eol=lf policy applies to them regardless of whether any reader currently
|
|
chokes. That residual - plus keeping the worktree consistent with the committed blob -
|
|
is the entire value proposition. It is a small one. It is stated at its real size on purpose.
|
|
|
|
SEVERITY LADDER (a deliberate consequence of the refutation)
|
|
HIGH - reserved for SELF-BROKEN only: the synthetic control below failed, so the scan
|
|
result is meaningless. Never used for a CR finding - none is known to break anything.
|
|
MED - CR inside the frontmatter block: the surface a parser would read, and the surface
|
|
W0.6 probed on agents/ only.
|
|
LOW - CR in the body only: same eol=lf policy, further from any reader hot-path.
|
|
Exit code is ALWAYS 0. Inform/hygiene net, never a hard gate.
|
|
|
|
POSITIVE CONTROL - SYNTHETIC, and declared as such (spec v2 (4)(a))
|
|
The scoped set measured 0 CR at S121, so a green run is the expected outcome. A green run
|
|
therefore proves NOTHING by itself: a detector that is merely broken is also green. Every
|
|
run self-tests the byte-reader against two IN-MEMORY buffers BEFORE touching disk:
|
|
(+) CRLF synthetic agent file -> reader MUST report frontmatter CR > 0
|
|
(-) LF synthetic agent file -> reader MUST report frontmatter CR = 0
|
|
These controls are synthetic BY NECESSITY, not by preference: because W0.6 refuted
|
|
CRLF-fatality, no real "file killed by CRLF" exists to serve as a natural positive control.
|
|
Note the limit of what a synthetic control buys: it proves the READER works. It does NOT
|
|
prove that CRLF harms anything - the experiment says it does not.
|
|
If either control fails, a HIGH self-broken FLAG is raised and the scan is reported
|
|
UNTRUSTWORTHY.
|
|
|
|
DESIGN NOTES
|
|
- Byte-reader, not text-reader: Get-Content (default and -Raw) drops or normalises CR, so a
|
|
text-level check could never fail on a CRLF file - it would be vacuous BY CONSTRUCTION.
|
|
[System.IO.File]::ReadAllBytes is used instead. Side effect: no text decoding happens at
|
|
all, so target-file encoding is irrelevant and gotcha #30 (ANSI-vs-UTF8 mojibake) cannot
|
|
reach the measurement. This script body is still ASCII-only per gotcha #30 / #37.
|
|
- No self-reference hazard (W0.4 lesson): the test is STRUCTURAL - count 0x0D inside a
|
|
byte-delimited region - not a string-grep, so it cannot match a quoted claim written
|
|
about itself. It also lives in scripts/ and scans only .claude/{agents,commands,skills},
|
|
so it is path-disjoint from its own scan set as well.
|
|
- Counts come from globs, never hardcoded (spec v3 fix #9h). Every PASS/FAIL/GREEN word
|
|
printed below is COMPUTED from a measured number (W0.4 meta-count lesson: never print a
|
|
verdict label next to a number that says otherwise).
|
|
- agents/README.md is intentionally IN scope. R2-C1 excludes README from the ROSTER count
|
|
(it is not an agent); EOL policy is per-file, so README is governed like any other file.
|
|
The two rules differ on purpose - do not "fix" one by copying the other.
|
|
- Scope is deliberately NOT repo-wide. A repo-wide 'i/lf w/crlf' scan matches 188 files
|
|
(131 of them EF Migrations); an acceptance of "count == 0" over that set would be a
|
|
false-price factory, and mass rm+checkout is forbidden this session (spec v2 W0.2).
|
|
The 188 stay INFORM-only and are not this script's business.
|
|
|
|
.PARAMETER RepoRoot
|
|
Repo root. Default = resolved 1 level up from this script (scripts/ -> repo root).
|
|
Every path is derived from it, so the script can be pointed at a temp tree for fault-injection.
|
|
|
|
.EXAMPLE
|
|
powershell.exe -ExecutionPolicy Bypass -File scripts/agent-frontmatter-eol-check.ps1
|
|
|
|
.EXAMPLE
|
|
powershell.exe -ExecutionPolicy Bypass -File scripts/agent-frontmatter-eol-check.ps1 -RepoRoot C:\Temp\eol-faultinject
|
|
#>
|
|
param(
|
|
[string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
|
)
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers - shape mirrors scripts/governance-detectors.ps1:43-67 so FLAG output
|
|
# from the two nets reads the same way.
|
|
# ---------------------------------------------------------------------------
|
|
$script:FlagCount = 0
|
|
|
|
function Write-Flag {
|
|
param(
|
|
[ValidateSet('HIGH', 'MED', 'LOW')] [string]$Severity,
|
|
[string]$Where, # file:line, or file
|
|
[string]$Desc,
|
|
[string]$Resolve
|
|
)
|
|
$color = switch ($Severity) { 'HIGH' { 'Red' } 'MED' { 'Yellow' } default { 'Gray' } }
|
|
Write-Host ("[EOL] {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
|
|
}
|
|
|
|
# Make a path repo-relative for readable FLAG output (forward slashes).
|
|
function Rel($full) {
|
|
$r = $full
|
|
if ($full.StartsWith($RepoRoot, [StringComparison]::OrdinalIgnoreCase)) {
|
|
$r = $full.Substring($RepoRoot.Length).TrimStart('\', '/')
|
|
}
|
|
return ($r -replace '\\', '/')
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Byte-level frontmatter reader (the whole point - see DESIGN NOTES).
|
|
#
|
|
# Splits on 0x0A only, then strips one optional trailing 0x0D per line, so the
|
|
# frontmatter delimiters are located identically in LF and CRLF files. The
|
|
# returned offsets are BYTE offsets, which is what lets us attribute every single
|
|
# CR to either the frontmatter block or the body.
|
|
# ---------------------------------------------------------------------------
|
|
function Measure-EolBytes {
|
|
param([byte[]]$Bytes)
|
|
|
|
if ($null -eq $Bytes) { $Bytes = New-Object byte[] 0 }
|
|
$len = $Bytes.Length
|
|
|
|
# Skip a UTF-8 BOM so a BOM'd file does not silently read as "no frontmatter".
|
|
$start = 0
|
|
$hasBom = $false
|
|
if ($len -ge 3 -and $Bytes[0] -eq 0xEF -and $Bytes[1] -eq 0xBB -and $Bytes[2] -eq 0xBF) {
|
|
$hasBom = $true
|
|
$start = 3
|
|
}
|
|
|
|
# Locate the frontmatter block: line 0 must be exactly '---'; the block ends at
|
|
# the next line that is exactly '---'. $fmEnd = byte offset one past that line.
|
|
$fmEnd = -1
|
|
$lineStart = $start
|
|
$lineIdx = 0
|
|
for ($i = $start; $i -le $len; $i++) {
|
|
$atEof = ($i -eq $len)
|
|
if (-not $atEof -and $Bytes[$i] -ne 0x0A) { continue }
|
|
|
|
$endExcl = $i
|
|
if ($endExcl -gt $lineStart -and $Bytes[$endExcl - 1] -eq 0x0D) { $endExcl-- }
|
|
|
|
# Byte -> char is Latin-1 here; harmless because the only comparison is the
|
|
# ASCII delimiter '---'. Multi-byte UTF-8 in frontmatter cannot false-match it.
|
|
$sb = New-Object System.Text.StringBuilder
|
|
for ($k = $lineStart; $k -lt $endExcl; $k++) { [void]$sb.Append([char]$Bytes[$k]) }
|
|
$content = $sb.ToString().TrimEnd()
|
|
|
|
if ($lineIdx -eq 0) {
|
|
if ($content -ne '---') { break } # no frontmatter block at all
|
|
}
|
|
elseif ($content -eq '---') {
|
|
$fmEnd = if ($atEof) { $len } else { $i + 1 }
|
|
break
|
|
}
|
|
|
|
if ($atEof) { break } # opening '---' but never closed
|
|
$lineIdx++
|
|
$lineStart = $i + 1
|
|
}
|
|
|
|
# Attribute every CR byte. With no frontmatter block, the whole file counts as body.
|
|
$split = if ($fmEnd -ge 0) { $fmEnd } else { $start }
|
|
$fmCr = 0
|
|
$bodyCr = 0
|
|
for ($i = $start; $i -lt $len; $i++) {
|
|
if ($Bytes[$i] -eq 0x0D) {
|
|
if ($i -lt $split) { $fmCr++ } else { $bodyCr++ }
|
|
}
|
|
}
|
|
|
|
return [pscustomobject]@{
|
|
HasFrontmatter = ($fmEnd -ge 0)
|
|
HasBom = $hasBom
|
|
FmCr = $fmCr
|
|
BodyCr = $bodyCr
|
|
TotalCr = ($fmCr + $bodyCr)
|
|
Bytes = $len
|
|
}
|
|
}
|
|
|
|
function Measure-EolFile {
|
|
param([string]$Path)
|
|
try {
|
|
$bytes = [System.IO.File]::ReadAllBytes($Path)
|
|
}
|
|
catch {
|
|
Write-Host (" [warn] cannot read bytes: {0} - {1}" -f (Rel $Path), $_.Exception.Message) -ForegroundColor DarkGray
|
|
return $null
|
|
}
|
|
return (Measure-EolBytes $bytes)
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# SYNTHETIC CONTROL - runs BEFORE the disk scan.
|
|
# Declared synthetic on purpose: W0.6 refuted CRLF-fatality, so there is no real
|
|
# dead file to use as a natural positive control (spec v2 (4)(a)).
|
|
# ---------------------------------------------------------------------------
|
|
Write-Section 'Synthetic control (DECLARED SYNTHETIC) - does the byte-reader have teeth?'
|
|
|
|
$ctrlCrlf = "---`r`nname: _synthetic-control`r`ndescription: synthetic positive control`r`n---`r`nbody line`r`n"
|
|
$ctrlLf = "---`nname: _synthetic-control`ndescription: synthetic negative control`n---`nbody line`n"
|
|
|
|
$ctrlPos = Measure-EolBytes ([System.Text.Encoding]::ASCII.GetBytes($ctrlCrlf))
|
|
$ctrlNeg = Measure-EolBytes ([System.Text.Encoding]::ASCII.GetBytes($ctrlLf))
|
|
|
|
$posOk = ($ctrlPos.HasFrontmatter -and $ctrlPos.FmCr -gt 0)
|
|
$negOk = ($ctrlNeg.HasFrontmatter -and $ctrlNeg.FmCr -eq 0 -and $ctrlNeg.BodyCr -eq 0)
|
|
$controlOk = ($posOk -and $negOk)
|
|
|
|
$posLabel = if ($posOk) { 'PASS' } else { 'FAIL' }
|
|
$negLabel = if ($negOk) { 'PASS' } else { 'FAIL' }
|
|
|
|
Write-Host (" (+) CRLF synthetic : fm={0} FmCr={1} BodyCr={2} expect fm=True,FmCr>0 -> {3}" -f `
|
|
$ctrlPos.HasFrontmatter, $ctrlPos.FmCr, $ctrlPos.BodyCr, $posLabel)
|
|
Write-Host (" (-) LF synthetic : fm={0} FmCr={1} BodyCr={2} expect fm=True,FmCr=0 -> {3}" -f `
|
|
$ctrlNeg.HasFrontmatter, $ctrlNeg.FmCr, $ctrlNeg.BodyCr, $negLabel)
|
|
|
|
if (-not $controlOk) {
|
|
Write-Flag 'HIGH' 'scripts/agent-frontmatter-eol-check.ps1 (self)' `
|
|
'self-broken: synthetic control FAILED - the byte-reader does not detect CR, or false-positives on LF. Every scan result below is MEANINGLESS.' `
|
|
'fix Measure-EolBytes; do NOT trust a green scan until both controls PASS'
|
|
}
|
|
else {
|
|
Write-Host ' [OK] reader detects CRLF and stays silent on LF - a green scan below is at least meaningful' -ForegroundColor Green
|
|
}
|
|
Write-Host ' (reminder: the control proves the READER works. It does NOT prove CRLF harms anything - S121 W0.6 measured that it does not.)' -ForegroundColor DarkGray
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Scoped scan - .gitattributes 'eol=lf' hygiene over the rule-set files.
|
|
# Scope per spec v2 W0.2 + R2. NOT repo-wide (see DESIGN NOTES).
|
|
# ---------------------------------------------------------------------------
|
|
Write-Section 'Scoped scan - .gitattributes eol=lf hygiene (.claude/{agents,commands,skills})'
|
|
|
|
$scopeSpecs = @(
|
|
[pscustomobject]@{ Name = '.claude/agents'; Dir = (Join-Path $RepoRoot '.claude\agents'); Filter = '*.md'; Recurse = $false },
|
|
[pscustomobject]@{ Name = '.claude/commands'; Dir = (Join-Path $RepoRoot '.claude\commands'); Filter = '*.md'; Recurse = $false },
|
|
[pscustomobject]@{ Name = '.claude/skills'; Dir = (Join-Path $RepoRoot '.claude\skills'); Filter = 'SKILL.md'; Recurse = $true }
|
|
)
|
|
|
|
$totalFiles = 0
|
|
$totalFmCrFiles = 0
|
|
$totalBodyCrFiles = 0
|
|
$totalNoFm = 0
|
|
$totalCrBytes = 0
|
|
|
|
foreach ($spec in $scopeSpecs) {
|
|
if (-not (Test-Path $spec.Dir)) {
|
|
Write-Host (" [skip] {0,-18} not present under RepoRoot" -f $spec.Name) -ForegroundColor DarkGray
|
|
continue
|
|
}
|
|
|
|
$files = @(Get-ChildItem -Path $spec.Dir -Filter $spec.Filter -File -Recurse:$spec.Recurse -ErrorAction SilentlyContinue)
|
|
$scopeScanned = 0
|
|
$scopeFmCr = 0
|
|
$scopeBodyCr = 0
|
|
$scopeCrBytes = 0
|
|
|
|
foreach ($f in $files) {
|
|
$m = Measure-EolFile $f.FullName
|
|
if ($null -eq $m) { continue }
|
|
|
|
$scopeScanned++
|
|
$totalFiles++
|
|
$scopeCrBytes += $m.TotalCr
|
|
$totalCrBytes += $m.TotalCr
|
|
if (-not $m.HasFrontmatter) { $totalNoFm++ }
|
|
|
|
if ($m.FmCr -gt 0) {
|
|
$scopeFmCr++
|
|
$totalFmCrFiles++
|
|
Write-Flag 'MED' (Rel $f.FullName) `
|
|
("eol-frontmatter-cr: {0} CR (0x0D) byte(s) inside the frontmatter block, {1} more in body - violates .gitattributes '* text=auto eol=lf'. HYGIENE only: S121 W0.6 measured a fully-CRLF agent file spawning correctly, so this is NOT a known breakage." -f $m.FmCr, $m.BodyCr) `
|
|
'rewrite the file with LF endings (git rm --cached <f> + git checkout -- <f>, or re-save as LF)'
|
|
}
|
|
elseif ($m.BodyCr -gt 0) {
|
|
$scopeBodyCr++
|
|
$totalBodyCrFiles++
|
|
Write-Flag 'LOW' (Rel $f.FullName) `
|
|
("eol-body-cr: frontmatter is clean but the body carries {0} CR (0x0D) byte(s) - same '* text=auto eol=lf' policy, further from any reader hot-path." -f $m.BodyCr) `
|
|
'rewrite the file with LF endings (hygiene / worktree-vs-blob consistency)'
|
|
}
|
|
}
|
|
|
|
Write-Host (" {0,-18} scanned={1,-3} frontmatter-CR-files={2,-3} body-only-CR-files={3,-3} CR-bytes={4}" -f `
|
|
$spec.Name, $scopeScanned, $scopeFmCr, $scopeBodyCr, $scopeCrBytes)
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Summary. Every label below is computed from a measured number (W0.4 lesson).
|
|
# ---------------------------------------------------------------------------
|
|
Write-Section 'Summary'
|
|
|
|
Write-Host ("scoped set (counted by glob, never hardcoded): {0} file(s)" -f $totalFiles)
|
|
Write-Host (" files with CR in frontmatter : {0}" -f $totalFmCrFiles)
|
|
Write-Host (" files with CR in body only : {0}" -f $totalBodyCrFiles)
|
|
Write-Host (" total CR (0x0D) bytes in set : {0}" -f $totalCrBytes)
|
|
Write-Host (" files with no parseable frontmatter block: {0} (not a flag - body CR still counted)" -f $totalNoFm)
|
|
|
|
Write-Host ''
|
|
Write-Host ("TOTAL FLAGS: {0} (scoped files={1}, CR bytes measured={2})" -f $script:FlagCount, $totalFiles, $totalCrBytes) -ForegroundColor Cyan
|
|
|
|
if (-not $controlOk) {
|
|
Write-Host 'RESULT: UNTRUSTWORTHY - the synthetic control FAILED, so the scan above proves nothing.' -ForegroundColor Red
|
|
}
|
|
elseif ($script:FlagCount -gt 0) {
|
|
Write-Host ("RESULT: {0} hygiene FLAG(s) above. Advisory only - exit code stays 0, nothing is known to be broken." -f $script:FlagCount) -ForegroundColor Yellow
|
|
}
|
|
elseif ($totalCrBytes -eq 0) {
|
|
Write-Host 'RESULT: GREEN-BUT-VACUOUS - 0 CR bytes existed in the scoped set, so this run had nothing to catch.' -ForegroundColor Yellow
|
|
Write-Host ' Do NOT bank this as an achievement: the scoped gate was ALREADY green with 0 work to do' -ForegroundColor DarkGray
|
|
Write-Host ' (spec v2 W0.2 / R2-C4). The only thing a green run demonstrates is that the reader itself' -ForegroundColor DarkGray
|
|
Write-Host ' works (synthetic control PASSED above). Its forward value is regression-catching, not this run.' -ForegroundColor DarkGray
|
|
}
|
|
else {
|
|
Write-Host 'RESULT: GREEN - CR bytes are present in the set but none in a flagged position.' -ForegroundColor Green
|
|
}
|
|
|
|
Write-Host 'NOTE: HYGIENE/INFORM net. Exit 0 always. CRLF-fatality was REFUTED by the S121 W0.6 spawn-probe (a fully-CRLF agent spawned OK through all 4 stages); this check enforces the .gitattributes eol=lf policy and nothing stronger.' -ForegroundColor DarkGray
|
|
|
|
exit 0
|