Files
Bo 64e93c0428 refactor(cli): complete the post-Cathedral-Cut Go CLI cleanup (#919)
Completes the Go CLI migration: one composition model, no tombstones, no
dead packages, hermetic tests, and a cross-language verdict contract
corpus. Net −88k lines.

## What
- **Tombstones + prune deleted**: the 21 registered 'Removed in the
Cathedral Cut' stubs, `zzz_default_spine.go`'s runtime prune, and the
~30 registered-but-pruned legacy command families are gone. The
registered tree IS the production tree; retired verbs fail with exit 1 +
a data-driven replacement hint pointing at docs/MIGRATION.md (now
covering nested verbs). Non-runnable parents reject unknown subcommands
instead of printing help with exit 0.
- **~35 unreachable packages deleted** (cliapp, canon, pawl-era verdict
parser, pool, harvest, knowledge, wiki engine, vibecheck, cli/embedded,
…) after import analysis + script/CI consumer verification. Retained
with named consumers: archcheck (arch gate scripts), eval island (rubric
schema drift guards), drwitness/drrebuild (witness-crosscheck).
- **Composition decision recorded**: single cmd/ao root +
internal/commands/<family> modules + clicontract; the never-adopted
cliapp.BuildRoot is removed. New overview: docs/architecture/go-cli.md
(repairs two broken links).
- **Hermetic tests**: flag_matrix_test.go builds the exec'd binary from
package source — the stale cli/bin/ao failure class is closed;
TestHermeticBinaryMatchesSourceSurface pins source↔binary
correspondence.
- **Verdict contract consolidated**: verification extracted from
status.go into cli/internal/verdictcheck; 19-case golden corpus
(tests/fixtures/verdict-contract) runs through Go + Python + JSON schema
via blocking gate `contract.verdict-corpus`. One divergence closed
(empty-string context ids now rejected by all three).
- **Fossil guard chains retired**: embedded-sync (mirror had zero
importers), retired-family compat baselines, dead
go_cli_compatibility.bats, stale gate globs/grandfather entries.

## Evidence
- Full-tier deterministic gate: **66/66 PASS** on the rebased tip (fresh
source-built binary)
- cli suite 2,795 tests green; race suite green on touched packages; vet
+ pinned golangci-lint clean
- Cathedral Cut conformance PASS; CLI reference/surface projections
regenerated and checked
- Independent cross-family validation (fresh Codex context, frozen SHA):
5/6 claims CONFIRMED; 6th NOT_PROVEN only due to sandbox networking (its
cached rerun of the same suite passed)
- Test-count decrease is deliberate and carried by a Test-Removal-Reason
trailer
2026-07-18 16:55:24 -04:00

185 lines
7.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# check-honest-voice.sh — honest-voice claims gate (bead age-5qjyn / FU3).
#
# Scans user-facing string content for CLAIMS of proven / automatic knowledge
# compounding and for hookless-3.0-violating "session hooks" language, per the
# lexicon docs/contracts/forbidden-claims.yaml. Fails (exit 1) naming the
# phrase + file + line + rationale so a dev who reintroduces "knowledge compounds
# automatically" gets told exactly what, where, and why.
#
# WHY: the README's honest version demotes corpus-compounding to "still
# measuring" (ADR-0004, ADR-0011) and 3.0 is hookless (docs/3.0.md, ADR-0009),
# yet CLI strings kept regrowing the claim (#907, FU4 diet commit) because
# nothing gated it. This is that gate.
#
# SCOPE (scanned): cli/cmd + cli/internal Go source (excluding *_test.go) and the
# seed/template assets.
# NOT docs/** — narrative docs discuss the unproven hypothesis honestly.
#
# SUPPRESSION: a line with `honest-voice:allow` is skipped; per-entry
# allowed_contexts globs in the lexicon also exempt a file.
#
# Exit: 0 pass, 1 fail (offenders found), 2 structural error (missing lexicon /
# no PyYAML).
set -euo pipefail
# ROOT is the tree scanned. It defaults to the repo root (script's ../) and is
# overridable via HONEST_VOICE_ROOT so the bats test can scan a throwaway fixture
# tree while pointing HONEST_VOICE_LEXICON at the real lexicon.
ROOT="${HONEST_VOICE_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
LEXICON="${HONEST_VOICE_LEXICON:-$ROOT/docs/contracts/forbidden-claims.yaml}"
if [ ! -f "$LEXICON" ]; then
echo "check-honest-voice: ERROR — lexicon not found: $LEXICON" >&2
exit 2
fi
export ROOT LEXICON
exec python3 - <<'PY'
import fnmatch
import os
import re
import sys
from pathlib import Path
try:
import yaml
except ImportError:
print("check-honest-voice: ERROR — PyYAML not installed (pip install pyyaml)", file=sys.stderr)
sys.exit(2)
ROOT = Path(os.environ["ROOT"])
LEXICON = Path(os.environ["LEXICON"])
data = yaml.safe_load(LEXICON.read_text()) or {}
claims = data.get("claims", [])
if not claims:
print("check-honest-voice: ERROR — lexicon has no claims", file=sys.stderr)
sys.exit(2)
# Compile each claim's pattern once.
compiled = []
for c in claims:
pat = c.get("pattern")
if not pat:
print(f"check-honest-voice: ERROR — claim {c.get('id')!r} has no pattern", file=sys.stderr)
sys.exit(2)
try:
rx = re.compile(pat, re.IGNORECASE)
except re.error as e:
print(f"check-honest-voice: ERROR — bad regex for {c.get('id')!r}: {e}", file=sys.stderr)
sys.exit(2)
compiled.append({
"id": c.get("id", "?"),
"rx": rx,
"rationale": " ".join((c.get("rationale") or "").split()),
"allowed": c.get("allowed_contexts") or [],
})
# Scan set: glob roots relative to repo root. Go source excludes *_test.go;
# template assets are the seed/template YAMLs.
SCAN_GLOBS = [
("cli/cmd", "*.go"),
("cli/internal", "*.go"),
("cli/internal/extract/templates", "*.yaml"),
]
SUPPRESS = "honest-voice:allow"
def rel(p: Path) -> str:
return p.relative_to(ROOT).as_posix()
def is_allowed(relpath: str, globs) -> bool:
return any(fnmatch.fnmatch(relpath, g) for g in globs)
files = []
for subdir, pat in SCAN_GLOBS:
base = ROOT / subdir
if not base.exists():
continue
for p in sorted(base.rglob(pat)):
if not p.is_file():
continue
name = p.name
if pat == "*.go" and name.endswith("_test.go"):
continue # tests assert ABSENCE and may legitimately name a phrase
files.append(p)
offenders = []
for p in files:
relpath = rel(p)
try:
text = p.read_text(encoding="utf-8", errors="replace")
except OSError as e:
print(f"check-honest-voice: ERROR — cannot read {relpath}: {e}", file=sys.stderr)
sys.exit(2)
for lineno, line in enumerate(text.splitlines(), start=1):
if SUPPRESS in line:
continue
for c in compiled:
if c["rx"].search(line):
if is_allowed(relpath, c["allowed"]):
continue
offenders.append((relpath, lineno, c["id"], line.strip(), c["rationale"]))
# Second pass: fuse Go string concatenation so a claim split across
# adjacent literals ("Knowledge compounds " + \n "automatically") cannot
# evade the line scan. Fusing `" + ... "` (incl. across newlines) joins
# the literals; matches are attributed to the line where they start.
# Suppressed lines are honored by checking the starting line's text.
if p.suffix == ".go":
# Raw-string pass: a claim wrapped across lines inside a backtick
# literal has its newlines collapsed so phrase patterns still match.
for rawm in re.finditer(r"`[^`]*`", text, re.DOTALL):
raw = rawm.group(0)
if "\n" not in raw:
continue # single-line raw strings were covered by the line scan
collapsed = re.sub(r"\s+", " ", raw)
start_line = text.count("\n", 0, rawm.start()) + 1
src_lines_r = text.splitlines()
line_txt = src_lines_r[start_line - 1] if start_line <= len(src_lines_r) else ""
if SUPPRESS in line_txt:
continue
for c in compiled:
if c["rx"].search(collapsed) and not is_allowed(relpath, c["allowed"]):
key = (relpath, start_line, c["id"])
if not any((o[0], o[1], o[2]) == key for o in offenders):
offenders.append((relpath, start_line, c["id"],
"(multiline raw string) " + collapsed.strip()[:120],
c["rationale"]))
fused = re.sub(r'"\s*\+\s*(?:\r?\n\s*)?(?:/[/*][^\n]*\n\s*)?"', "", text)
if fused != text:
src_lines = text.splitlines()
for c in compiled:
for m in c["rx"].finditer(fused):
if is_allowed(relpath, c["allowed"]):
continue
approx = fused.count("\n", 0, m.start()) + 1
line_txt = src_lines[approx - 1] if approx <= len(src_lines) else ""
if SUPPRESS in line_txt:
continue
key = (relpath, approx, c["id"])
if any((o[0], o[1], o[2]) == key for o in offenders):
continue # already caught by the line scan
offenders.append((relpath, approx, c["id"],
m.group(0).strip() + " (fused string concatenation)",
c["rationale"]))
if offenders:
print("check-honest-voice: FAIL — forbidden claim(s) in user-facing content:", file=sys.stderr)
for relpath, lineno, cid, snippet, rationale in offenders:
print(f" {relpath}:{lineno} [{cid}]", file=sys.stderr)
print(f" > {snippet}", file=sys.stderr)
print(f" rationale: {rationale}", file=sys.stderr)
print("", file=sys.stderr)
print(" fix: rewrite to honest phrasing (context accrues in .agents/ — whether it", file=sys.stderr)
print(" compounds is still being measured; 3.0 is hookless), or add a reviewed", file=sys.stderr)
print(" `honest-voice:allow` on the line. Lexicon: docs/contracts/forbidden-claims.yaml", file=sys.stderr)
sys.exit(1)
print(f"check-honest-voice: ok ({len(files)} file(s) scanned, no forbidden claims)")
PY