mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
e7acfd9fa9
Dual-support increment 3 (the user-facing completion). AgentOps ships to users who mostly track with bd; operating-loop skills hardcoded `br`, breaking them. Repoint bead CRUD (ready/update/close/create/dep/show/list, non-jq) and the children-extraction pattern to the tracker-agnostic `ao beads exec` (bd-or-br) across skills/**/SKILL.md + references. In THIS repo (br) there is no behavior change; the win is bd users. - children pattern `br show <epic> --json | jq '…parent-child….id'` → `ao beads exec children <epic>` (post-mortem closure-integrity-audit + the latent invalid `br children` in rpi phase-data-contracts). - probe blocks re-gated `command -v br` → `command -v ao` (bd users have ao). - parity_only codex twins refreshed via codex-sync + hashes; bespoke twins are hand-maintained (codex-sync skips them by design) and left for a deliberate follow-up. - jq-shape `br … --json | jq '<shape>'` sites left on br (increment 4 will normalize the --json shape across trackers). - rpi/SKILL.md trimmed 280→249 lines (meta-tier ≤250): receipt JSON folded into the existing phase-data-contracts.md, rpi_state block compacted, redundant Reference-Documents entries dropped. - snippet validators (ao_snippet_resolve.py lib + validate-skill-body-refs.sh) taught that `ao beads exec` is a flag-forwarding passthrough, so tracker flags after it are not validated against ao's help.
126 lines
4.6 KiB
Bash
Executable File
126 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
# Shared `ao` snippet resolution machinery (build + resolver core), so this gate
|
|
# and the docs.cli-snippets gate resolve against the SAME cobra tree from one
|
|
# place. Resolve via the pre-cd absolutized $SCRIPT_DIR (a relative
|
|
# ${BASH_SOURCE[0]} would resolve wrongly if a caller cd'd first — the .1 lane's
|
|
# round-1 pawl catch).
|
|
# shellcheck source=scripts/lib/ao-snippet-resolve.sh
|
|
. "$SCRIPT_DIR/lib/ao-snippet-resolve.sh"
|
|
|
|
# Build (or reuse) the archive-tagged ao binary; sets + exports AO_BIN.
|
|
AO_BIN="$(ao_snippet_resolve_bin "$REPO_ROOT")"
|
|
# Clean up a lib-built temp binary on exit (no-op when AGENTOPS_AO_BIN was set).
|
|
if [[ -n "${AO_SNIPPET_TMP_DIR:-}" ]]; then
|
|
trap 'rm -rf "$AO_SNIPPET_TMP_DIR"' EXIT
|
|
fi
|
|
|
|
export AO_BIN
|
|
export REPO_ROOT
|
|
# SOUND resolution semantics for the skills gate: `ao <chain> --help`, reject
|
|
# cobra's "unknown command" / "Unknown help topic" (see ao_snippet_resolve.py).
|
|
# The original `help`-mode predicate (`ao help <chain>` trusting rc==0) was
|
|
# UNSOUND — `ao help <anything>` always exits 0, so the gate could not detect a
|
|
# removed command at all (age-zggp). The env seam stays for tests / A-B.
|
|
export AO_RESOLVE_MODE="${AO_RESOLVE_MODE:-strict}"
|
|
|
|
python3 - <<'PY'
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import shlex
|
|
import sys
|
|
|
|
# Import the shared resolution core (extracted to scripts/lib/).
|
|
sys.path.insert(0, os.environ["AO_SNIPPET_LIB_DIR"])
|
|
from ao_snippet_resolve import iter_snippets, make_resolver_from_env
|
|
|
|
repo_root = pathlib.Path(os.environ["REPO_ROOT"])
|
|
roots = [repo_root / "skills", repo_root / "skills-codex"]
|
|
allowed_suffixes = {".md", ".sh"}
|
|
stale_beads_resolver = re.compile(r"BEADS_DIR=\$PWD/_beads|git -C _beads|git add \.beads|git add _beads")
|
|
stale_beads_allowed = re.compile(r"\b(anti-pattern|do not|don't|must not|never|reject|fails?|historical|retired)\b", re.IGNORECASE)
|
|
|
|
resolver = make_resolver_from_env()
|
|
failures = []
|
|
|
|
def validate_snippet(path: pathlib.Path, lineno: int, snippet: str):
|
|
try:
|
|
tokens = shlex.split(snippet)
|
|
except ValueError:
|
|
return
|
|
|
|
tokens = resolver.trim_shell_tokens(tokens)
|
|
if not tokens or tokens[0] != "ao":
|
|
return
|
|
|
|
if resolver.is_regex_like(tokens):
|
|
return
|
|
|
|
command, help_text = resolver.resolve_command(tokens)
|
|
if not command:
|
|
if len(tokens) == 1:
|
|
return
|
|
if all(token.startswith("-") for token in tokens[1:]):
|
|
help_text = resolver.global_help()
|
|
for flag in tokens[1:]:
|
|
normalized = resolver.normalize_flag(flag)
|
|
if normalized not in help_text:
|
|
failures.append(
|
|
f"{path.relative_to(repo_root)}:{lineno}: flag {normalized} not found in help for ao"
|
|
)
|
|
return
|
|
failures.append(f"{path.relative_to(repo_root)}:{lineno}: unknown ao command in snippet: {snippet}")
|
|
return
|
|
|
|
# `ao beads exec …` forwards flags verbatim to the tracker (bd/br); they are
|
|
# never in ao's help, so do not flag-check a passthrough command.
|
|
if resolver.is_passthrough(command):
|
|
return
|
|
|
|
flags = []
|
|
for token in tokens[1 + len(command):]:
|
|
if not token.startswith("-"):
|
|
continue
|
|
if len(token) > 1 and token[1:].isdigit():
|
|
continue
|
|
flags.append(resolver.normalize_flag(token))
|
|
for flag in flags:
|
|
if flag not in help_text:
|
|
failures.append(
|
|
f"{path.relative_to(repo_root)}:{lineno}: flag {flag} not found in help for {' '.join(['ao', *command])}"
|
|
)
|
|
|
|
for root in roots:
|
|
if not root.exists():
|
|
continue
|
|
for path in sorted(root.rglob("*")):
|
|
if not path.is_file():
|
|
continue
|
|
if path.suffix not in allowed_suffixes:
|
|
continue
|
|
try:
|
|
text = path.read_text(encoding="utf-8")
|
|
except UnicodeDecodeError:
|
|
continue
|
|
for lineno, line in enumerate(text.splitlines(), start=1):
|
|
if stale_beads_resolver.search(line) and not stale_beads_allowed.search(line):
|
|
failures.append(
|
|
f"{path.relative_to(repo_root)}:{lineno}: stale beads resolver; use BEADS_DIR=\"$(ao beads dir)\" and git -C \"$(ao beads dir)\""
|
|
)
|
|
for lineno, snippet in iter_snippets(text):
|
|
validate_snippet(path, lineno, snippet)
|
|
|
|
if failures:
|
|
print("Skill CLI snippet validation FAILED:", file=sys.stderr)
|
|
for failure in failures:
|
|
print(f" {failure}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
print("Skill CLI snippet validation passed.")
|
|
PY
|