Files
boshu2__agentops/scripts/regen-command-surfaces.sh
T
Bo 0c5554f1bc build(factory): one-shot regenerator for ao-command-landing surfaces (ag-jy12 #command-regen) (#742)
## What

A ONE-SHOT regenerator (`scripts/regen-command-surfaces.sh`) for the
`ao`-command-landing derived surfaces. When an `ao` command is
added/removed/renamed, these surfaces must update in lockstep — the
multi-surface tax that bit #720 (ag-sz3h, ~4 extra CI rounds):

- the two cobra `expectedCmds` literals in
`cli/cmd/ao/cobra_commands_test.go`
- the `cli-command-surface` heading counts:
`evals/agentops-core/fixtures/cli-command-surface-smoke.sh` +
`evals/agentops-core/cli-command-surface-matrix.json`

## How

- **Single source of truth for the command set**: the LIVE cobra tree,
dumped by the new `TestDumpRegisteredTopLevelCommands` (set
`AO_DUMP_REGISTERED_CMDS=1`). It walks `rootCmd.Commands()` (sorted,
`help` excluded) — exactly the membership the two `expectedCmds`
literals must match. A *test*, not a subcommand, so emitting the list
does not itself add a registered command (which would be circular).
- **Single source of truth for the counts**: `cli/docs/COMMANDS.md`
(itself generated from the cobra tree). `top = ### \`ao `, `sub = ####
\`ao `, `all = #{3,4} \`ao `.
- Rewrites all three derived surfaces deterministically. The
`expectedCmds` literals are reformatted one-element-per-line
(gofmt-stable) so output is canonical forever — **membership is
identical to before (pure reformat)**.
- **Idempotent**: a no-change tree is a no-op; `--check` fails on drift
without writing.
- Wired into `regen-all.sh` (both regen + `--check`) right after the
COMMANDS.md step.

## Deletion ⊃ Addition (completeness note)

Removing/renaming a command is a SUPERSET of adding one. This
regenerator covers the ADD surfaces. A delete/rename ALSO disturbs
surfaces it does NOT auto-regenerate — these need manual/quorum
attention:

- (a) codex-contract gates that hardcode command literals
(`validate-codex-rpi-contract.sh`)
- (b) skills-codex twins/overrides (`export-claude-skills-to-codex` +
`regen-codex-hashes`)
- (c) VERBATIM-PRESERVE template SHAs (`cron-loop-mode.md` frontmatter)

A "deletion ⊃ addition" note is present in the regenerator header
comment AND the `regen-all.sh` operator reminder.

## Verification

- `scripts/regen-command-surfaces.sh` → no-op on clean tree; `--check`
green.
- `go build ./cmd/ao`, `go vet ./cmd/ao`, `go test ./cmd/ao` (full
package) all pass; `gofmt -l` clean.
- `cli-command-surface-matrix` canary: pass. Smoke fixture: pass.
- Pre-push gate's only reds are pre-existing, unrelated canaries
(`context-packet-ab-wave0`, `agentops-core.pre-push-gate-governance`
bats) — not touched by this diff.

Closes-scenario: ag-jy12#command-regen
Bounded-context: BC4-Factory
Evidence: scripts/regen-command-surfaces.sh
2026-06-05 05:15:41 +00:00

186 lines
8.5 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# regen-command-surfaces.sh — ONE-SHOT regenerator for the `ao`-command-landing
# derived surfaces (bead ag-jy12).
#
# WHY THIS EXISTS
# Adding / removing / renaming an `ao` command makes several DERIVED surfaces
# stale. regen-all.sh auto-fixes COMMANDS.md, registry.json, the skill-domain
# map, and the context-map, but it only WARNS about two manual surfaces:
# 1. the two cobra `expectedCmds` literals (cli/cmd/ao/cobra_commands_test.go)
# 2. the cli-command-surface heading counts (smoke fixture + matrix canary)
# Because they were manual, each drift was discovered ONE CI round-trip at a
# time (cost ~4 extra rounds on #720, ag-sz3h). This regenerator derives the
# canonical command set + heading counts from ONE source of truth each and
# rewrites all of them deterministically — "add an ao command" becomes one
# regen, zero hand-edits.
#
# SOURCES OF TRUTH (single, each)
# * registered command set -> the LIVE cobra tree, via the Go test
# `TestDumpRegisteredTopLevelCommands` (AO_DUMP_REGISTERED_CMDS=1). It walks
# rootCmd.Commands() — the exact set the two expectedCmds literals must
# match (sorted, `help` excluded). A test, not a subcommand, so emitting
# the list does not itself add a registered command.
# * heading counts -> cli/docs/COMMANDS.md (itself generated from the
# cobra tree by generate-cli-reference.sh). top = `### \`ao `, sub =
# `#### \`ao `, all = `#{3,4} \`ao `.
#
# DERIVED SURFACES REWRITTEN
# * cli/cmd/ao/cobra_commands_test.go — BOTH expectedCmds literals
# * evals/agentops-core/fixtures/cli-command-surface-smoke.sh — count guard
# * evals/agentops-core/cli-command-surface-matrix.json — stdout_contains
#
# IDEMPOTENT: running on a no-change tree is a no-op (no file is rewritten if it
# already matches). --check exits non-zero on drift without writing.
#
# ORDER: run AFTER generate-cli-reference.sh (COMMANDS.md must be current first).
# Wired into regen-all.sh after the "cli reference (COMMANDS.md)" step.
#
# ───────────────────────────────────────────────────────────────────────────
# DELETION ⊃ ADDITION (completeness note — RubyMoose)
# Removing or RENAMING a command is a SUPERSET of adding one. This regenerator
# covers the ADD surfaces above. A delete/rename ALSO disturbs surfaces this
# script does NOT auto-regenerate — they need manual / quorum attention:
# (a) codex-contract gates that hardcode command literals
# (scripts/validate-codex-rpi-contract.sh and siblings),
# (b) skills-codex twins / overrides
# (export-claude-skills-to-codex + scripts/regen-codex-hashes.sh),
# (c) VERBATIM-PRESERVE template SHAs in frontmatter
# (e.g. cron-loop-mode.md) — a renamed command literal changes the SHA.
# When you DELETE or RENAME a command, run this regenerator AND audit (a)-(c)
# by hand (grep the command literal across skills/ skills-codex/ overrides/
# scripts/), then re-run the full pre-push gate.
# ───────────────────────────────────────────────────────────────────────────
#
# Usage:
# scripts/regen-command-surfaces.sh # rewrite derived surfaces (idempotent)
# scripts/regen-command-surfaces.sh --check # fail on drift; write nothing
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$REPO_ROOT"
CHECK_MODE=false
[[ "${1:-}" == "--check" ]] && CHECK_MODE=true
COBRA_TEST="cli/cmd/ao/cobra_commands_test.go"
SMOKE="evals/agentops-core/fixtures/cli-command-surface-smoke.sh"
MATRIX="evals/agentops-core/cli-command-surface-matrix.json"
DOCS="cli/docs/COMMANDS.md"
for f in "$COBRA_TEST" "$SMOKE" "$MATRIX" "$DOCS"; do
[[ -f "$f" ]] || { echo "ERROR: missing $f" >&2; exit 2; }
done
drift=0
# ── 1. derive canonical command set from the live cobra tree ────────────────
mapfile -t CMDS < <(
cd "$REPO_ROOT/cli" \
&& AO_DUMP_REGISTERED_CMDS=1 env -u AGENTOPS_RPI_RUNTIME \
go test ./cmd/ao -run '^TestDumpRegisteredTopLevelCommands$' -count=1 -v 2>/dev/null \
| grep -E '^[a-z0-9][a-z0-9-]*$' \
| sort -u
)
if [[ "${#CMDS[@]}" -lt 45 ]]; then
echo "ERROR: dumper returned only ${#CMDS[@]} commands (expected >=45); build/test failure?" >&2
exit 2
fi
# ── 2. derive heading counts from COMMANDS.md ───────────────────────────────
top_count="$(grep -cE '^### `ao ' "$DOCS")"
sub_count="$(grep -cE '^#### `ao ' "$DOCS")"
all_count="$(grep -cE '^#{3,4} `ao ' "$DOCS")"
# ── helpers ────────────────────────────────────────────────────────────────
# Rewrite both `expectedCmds := []string{ ... }` blocks in the cobra test to a
# canonical one-element-per-line form (gofmt-stable). Done in Go-free Python so
# the formatting is deterministic regardless of source wrapping.
rewrite_cobra() {
local outfile="$1"
python3 - "$COBRA_TEST" "$outfile" "${CMDS[@]}" <<'PY'
import sys, re
src_path, out_path = sys.argv[1], sys.argv[2]
cmds = sys.argv[3:]
with open(src_path) as fh:
text = fh.read()
# Canonical literal body: one element per line, tab+tab indent (inside a func).
body = "".join(f'\t\t"{c}",\n' for c in cmds)
new_literal = "expectedCmds := []string{\n" + body + "\t}"
# Replace every `expectedCmds := []string{ ... }` (non-greedy to the matching
# `\n\t}` that closes the literal). The literals are indented one tab inside a
# func, so the close line is exactly "\n\t}".
pattern = re.compile(r"expectedCmds := \[\]string\{.*?\n\t\}", re.DOTALL)
new_text, n = pattern.subn(lambda _m: new_literal, text)
if n == 0:
sys.stderr.write("ERROR: no expectedCmds literal found in cobra test\n")
sys.exit(3)
with open(out_path, "w") as fh:
fh.write(new_text)
PY
}
# ── 3. cobra expectedCmds (×2) ──────────────────────────────────────────────
tmp_cobra="$(mktemp)"
rewrite_cobra "$tmp_cobra"
# gofmt to canonical form so a clean tree is a stable no-op.
gofmt "$tmp_cobra" > "${tmp_cobra}.fmt" && mv "${tmp_cobra}.fmt" "$tmp_cobra"
if ! cmp -s "$tmp_cobra" "$COBRA_TEST"; then
drift=1
if $CHECK_MODE; then
echo "DRIFT: $COBRA_TEST expectedCmds out of sync with the cobra tree" >&2
else
cp "$tmp_cobra" "$COBRA_TEST"
echo " updated $COBRA_TEST (expectedCmds ×2 -> ${#CMDS[@]} commands)"
fi
fi
rm -f "$tmp_cobra"
# ── 4. cli-command-surface heading counts (smoke fixture) ───────────────────
tmp_smoke="$(mktemp)"
sed -E \
-e "s/\\[\\[ \"\\\$top_count\" != \"[0-9]+\" \\|\\| \"\\\$sub_count\" != \"[0-9]+\" \\|\\| \"\\\$all_count\" != \"[0-9]+\" \\]\\]/[[ \"\$top_count\" != \"${top_count}\" || \"\$sub_count\" != \"${sub_count}\" || \"\$all_count\" != \"${all_count}\" ]]/" \
-e "s/-ne [0-9]+ \]\]/-ne ${all_count} ]]/" \
"$SMOKE" > "$tmp_smoke"
if ! cmp -s "$tmp_smoke" "$SMOKE"; then
drift=1
if $CHECK_MODE; then
echo "DRIFT: $SMOKE heading counts out of sync (want top=$top_count sub=$sub_count all=$all_count)" >&2
else
cp "$tmp_smoke" "$SMOKE"
echo " updated $SMOKE (top=$top_count sub=$sub_count all=$all_count)"
fi
fi
rm -f "$tmp_smoke"
# ── 5. cli-command-surface heading counts (matrix canary) ───────────────────
tmp_matrix="$(mktemp)"
sed -E "s/cli-command-headings: top=[0-9]+ sub=[0-9]+ all=[0-9]+/cli-command-headings: top=${top_count} sub=${sub_count} all=${all_count}/" \
"$MATRIX" > "$tmp_matrix"
if ! cmp -s "$tmp_matrix" "$MATRIX"; then
drift=1
if $CHECK_MODE; then
echo "DRIFT: $MATRIX heading counts out of sync (want top=$top_count sub=$sub_count all=$all_count)" >&2
else
cp "$tmp_matrix" "$MATRIX"
echo " updated $MATRIX (top=$top_count sub=$sub_count all=$all_count)"
fi
fi
rm -f "$tmp_matrix"
if $CHECK_MODE; then
if [[ $drift -ne 0 ]]; then
echo "command-surface DRIFT — run scripts/regen-command-surfaces.sh (no flag) to fix" >&2
exit 1
fi
echo "command surfaces in sync (cobra expectedCmds + heading counts)"
else
if [[ $drift -eq 0 ]]; then
echo "command surfaces already in sync — no changes"
fi
fi
exit 0