mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
118 lines
4.2 KiB
Bash
Executable File
118 lines
4.2 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
|
|
# Byte-identical resolution semantics for the skills gate: `ao help <chain>`,
|
|
# trust returncode==0 (the original validator's predicate).
|
|
export AO_RESOLVE_MODE=help
|
|
|
|
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
|
|
|
|
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
|