mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
238964fdd2
Workflows get the skills treatment (operator decision): canonical source in the product tree, installed by a product verb, Claude-only labeled as such. **What moves:** all seven Claude workflow scripts + README migrate from force-added exceptions inside the gitignored `.claude/` to a tracked top-level `workflows/` (sibling of `skills/`) — the four existing conveyors plus `audit-dimensions`, `verify-fixes`, `implement-wave`: three thin, args-parameterized orchestration conveyors extracted from this session's hand-rolled waves, contract-reviewed, and smoke-proven through the real Workflow runtime (the smoke caught two contract gaps static review could not: an `export default` wrapper the runtime never invokes, and args arriving as a JSON string — both fixed, string-args tolerance now built in). **New verb:** `ao workflows link` / `unlink` mirror `ao skills link` semantics — dry-run `--json`, refuse to replace real files or foreign links, unlink only checkout-owned links — targeting the project-local `.claude/workflows/` where Claude Code resolves named workflows (`--into` overrides). Checkout identity reuses the skillsapp marker discipline, fail-closed. Claude-only runtime adapter, same doctrine as the Codex-only `skills-codex/`. **Legacy surfaces repointed:** `install-workflows.sh` (user-global $HOME installer), `check-workflow-drift.sh` + gate comment, `check-bdd-foundry-markers.sh`; spine allowlist + YAML-probe excuse + go-cli.md spine region gain the workflows group; COMMANDS.md, cli-surface projections, and surface-count fixture regenerated; new tests carry per-command git-env scrubbing (test-isolation ratchet back at baseline). **Built BY the workflow being canonized** — `implement-wave` orchestrated its own canonization: two disjoint-ownership lanes plus a seam-checking verifier that ran the real binary's link → resolve → unlink cycle in the live tree (both lanes RESOLVED). The lanes correctly *refused* to self-approve their command into the spine invariants and handed integration three flagged edits instead. **Expected local gate note:** `workflow.install-drift` correctly FAILS on machines whose user-global `~/.claude/workflows` links still point at the old location — that is the transition it exists to catch. CI stays green (absent→skip). **Post-merge operator step:** `cd ~/dev/agentops && git pull && bash scripts/install-workflows.sh`. **Verified:** full suite 63/63 pkgs; golangci-lint clean; `gate check --full` over this range = 66/67 with only the documented install-drift environment finding; workflows smoke-run evidence in session logs.
78 lines
3.1 KiB
Bash
Executable File
78 lines
3.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# check-workflow-drift.sh — workflow install freshness/resolution check (ag-wi9w1).
|
|
#
|
|
# Blocking set: bdd-foundry.js
|
|
# - absent entirely -> SKIP line, exit 0 (clean machines/CI stay green)
|
|
# - dangling symlink -> exit 1 naming the offending path
|
|
# - symlink -> must realpath-resolve to the repo canonical, OR (cross-
|
|
# checkout: gate run from a worktree) resolve to a target
|
|
# byte-equal to this repo's canonical; else exit 1
|
|
# - regular file -> cmp -s against the repo canonical, else exit 1
|
|
# Report-only set: every other repo-tracked workflows/*.js
|
|
# (today: bead-crank.js, operating-loop.js) — the same comparison, but divergence
|
|
# emits 'DRIFT-REPORT: <name> ...' to stdout and NEVER affects the exit code.
|
|
#
|
|
# Repo root from cwd git; installed dir from $HOME. No hardcoded user paths.
|
|
set -euo pipefail
|
|
|
|
# shellcheck disable=SC1007,SC1091
|
|
. "$(CDPATH= cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/repo-root.sh"
|
|
repo_root="$(resolve_repo_root)"
|
|
canon_dir="$repo_root/workflows"
|
|
inst_dir="$HOME/.claude/workflows"
|
|
blocking_name="bdd-foundry.js"
|
|
|
|
resolve() { python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$1"; }
|
|
|
|
# compare_install <name>: echoes a failure detail and returns 1 on drift/dangle;
|
|
# returns 0 when absent or faithful.
|
|
compare_install() {
|
|
name="$1"
|
|
inst="$inst_dir/$name"
|
|
canon="$canon_dir/$name"
|
|
if [ ! -e "$inst" ] && [ ! -L "$inst" ]; then
|
|
return 0
|
|
fi
|
|
if [ -L "$inst" ]; then
|
|
if [ ! -e "$inst" ]; then
|
|
echo "$name installed symlink is dangling: $inst -> $(readlink "$inst")"
|
|
return 1
|
|
fi
|
|
if [ "$(resolve "$inst")" != "$(resolve "$canon")" ]; then
|
|
# Cross-checkout tolerance: the home symlink targets the MAIN checkout, so a gate run
|
|
# from a worktree sees a different realpath. Byte-equality with THIS repo's canonical
|
|
# is the real invariant — fail only when the resolved content has drifted.
|
|
if ! cmp -s "$(resolve "$inst")" "$canon"; then
|
|
echo "$name installed symlink resolves to $(resolve "$inst"), not the repo canonical $canon, and its bytes differ"
|
|
return 1
|
|
fi
|
|
fi
|
|
elif ! cmp -s "$inst" "$canon"; then
|
|
echo "$name installed copy bytes differ from the repo canonical: $inst vs $canon"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Blocking: bdd-foundry.js
|
|
if [ ! -e "$inst_dir/$blocking_name" ] && [ ! -L "$inst_dir/$blocking_name" ]; then
|
|
echo "SKIP: $blocking_name not installed ($inst_dir/$blocking_name absent)"
|
|
else
|
|
if ! detail="$(compare_install "$blocking_name")"; then
|
|
echo "FAIL: $detail"
|
|
exit 1
|
|
fi
|
|
echo "OK: $blocking_name installed and faithful to the repo canonical"
|
|
fi
|
|
|
|
# Report-only siblings: every other repo-tracked workflow.
|
|
while IFS= read -r tracked; do
|
|
name="$(basename "$tracked")"
|
|
[ "$name" = "$blocking_name" ] && continue
|
|
if ! detail="$(compare_install "$name")"; then
|
|
echo "DRIFT-REPORT: $detail"
|
|
fi
|
|
done < <(git -C "$repo_root" ls-files 'workflows/*.js')
|
|
|
|
exit 0
|