mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
5d61bf8a3f
Fixes the three gremlins observed 2026-07-18 (bead age-gate-scripts-worktree-gitdir-p62wo): 1. **Gate scripts broke under hook env from linked worktrees** (#919 regression): new scripts/lib/repo-root.sh (GIT_*-scrubbed resolution, BASH_SOURCE anchor, worktree-correct) swept across 32 scripts. Proof: this PR's own push ran the pre-push gates from a linked worktree and passed. 2. **core.bare=true recurrence**: mechanism proven (leaked GIT_DIR + no-arg git init --bare rewrites the SHARED config); writer traced with high confidence to a live Gas City city process (concrete suspect: gascity cmd_rig_test.go runGitInTest, unscrubbed env + no-arg init --bare — fork-side fix filed separately). Defense: new always.git-config-hygiene gate fails fast on core.bare/test identities with the repair command inline (--self-test proves fail-closed). 3. **Test/test@test.com identity leak**: writer convicted — our own pre-push chain leaked GIT_DIR into go test, whose helpers wrote the shared config via git -C (mechanism proven). Cut at both ends: validate-go-fast scrubs env for children; every test git-helper set-site scrubs; check-test-isolation gains a WARN-ratchet rule (baseline 3 → FAIL at 0). Gates: shellcheck rc=0 across 36 scripts; go build/vet clean; 655+379+149 tests; hygiene self-test; both originally-failing validators pass under the repro env.
102 lines
4.3 KiB
Bash
Executable File
102 lines
4.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# skill-usage-report.sh — count skill invocations from local Claude Code
|
|
# transcripts (age-skills-audit-fable-l6ic.5; automates the 2026-06-10 hand count).
|
|
#
|
|
# Counts two persisted shapes per transcript line:
|
|
# Skill-tool calls: "skill":"<name>" (compact) / "skill": "<name>"
|
|
# slash commands: <command-name>/<name></command-name>
|
|
#
|
|
# Segments library / read-not-invoked skills (metadata.internal or the known
|
|
# JIT set) so a zero on them is not misread as death. STATED CAVEATS: this
|
|
# undercounts — Codex-side sessions, other hosts, and skills consumed by being
|
|
# READ as references never appear here. A high count proves life; a zero is
|
|
# weak evidence, only meaningful for user-invocable skills.
|
|
#
|
|
# Usage: scripts/skill-usage-report.sh [--since <days>] [--dir <transcripts-dir>] [--json]
|
|
# --since lookback in days (default 30)
|
|
# --dir transcripts dir (default ~/.claude/projects, recursive)
|
|
set -euo pipefail
|
|
|
|
usage() { sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'; }
|
|
|
|
SINCE_DAYS=30
|
|
TDIR="${HOME}/.claude/projects"
|
|
AS_JSON=0
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--since) [ $# -ge 2 ] || { echo "skill-usage: --since needs a value" >&2; exit 2; }
|
|
SINCE_DAYS="$2"; shift 2 ;;
|
|
--dir) [ $# -ge 2 ] || { echo "skill-usage: --dir needs a value" >&2; exit 2; }
|
|
TDIR="$2"; shift 2 ;;
|
|
--json) AS_JSON=1; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "skill-usage: unknown arg $1 (see --help)" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
[ -d "$TDIR" ] || { echo "skill-usage: FAIL — no transcripts dir at $TDIR (remedy: pass --dir)" >&2; exit 1; }
|
|
command -v jq >/dev/null 2>&1 || { echo "skill-usage: FAIL — jq required" >&2; exit 1; }
|
|
|
|
# shellcheck disable=SC1007,SC1091
|
|
. "$(CDPATH= cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/repo-root.sh"
|
|
REPO_ROOT="$(resolve_repo_root)"
|
|
|
|
# Library / read-not-invoked set: metadata.internal skills plus the known JIT
|
|
# libraries — invocation counts do not apply to them.
|
|
lib_set() {
|
|
local d
|
|
for d in "$REPO_ROOT"/skills/*/; do
|
|
[ -f "$d/SKILL.md" ] || continue
|
|
if /usr/bin/grep -q '^ internal: true' "$d/SKILL.md" 2>/dev/null \
|
|
|| /usr/bin/grep -q '^internal: true' "$d/SKILL.md" 2>/dev/null; then
|
|
basename "$d"
|
|
fi
|
|
done
|
|
printf '%s\n' standards shared domain
|
|
}
|
|
|
|
# Count both persisted shapes across transcripts newer than the window.
|
|
counts() {
|
|
# find -mtime is day-granular; matches the hand-count method.
|
|
/usr/bin/find "$TDIR" -name '*.jsonl' -mtime "-${SINCE_DAYS}" -size +0c 2>/dev/null \
|
|
| while IFS= read -r f; do
|
|
/usr/bin/grep -ho '"skill": *"[a-zA-Z0-9:_-]*"' "$f" 2>/dev/null \
|
|
| sed -E 's/.*"skill": *"([^"]*)".*/\1/'
|
|
/usr/bin/grep -ho '<command-name>/[a-zA-Z0-9:_-]*</command-name>' "$f" 2>/dev/null \
|
|
| sed -E 's#<command-name>/([^<]*)</command-name>#\1#'
|
|
done | sed 's#^.*:##' | sort | uniq -c | sort -rn
|
|
}
|
|
|
|
raw="$(counts)"
|
|
libs="$(lib_set | sort -u)"
|
|
|
|
if [ "$AS_JSON" -eq 1 ]; then
|
|
printf '%s\n' "$raw" | awk 'NF==2 {printf "{\"skill\":\"%s\",\"count\":%s}\n", $2, $1}' \
|
|
| jq -s --arg since "$SINCE_DAYS" --arg dir "$TDIR" --argjson libs "$(printf '%s\n' "$libs" | jq -R . | jq -s .)" '
|
|
{ since_days: ($since|tonumber), transcripts_dir: $dir,
|
|
caveats: ["undercounts codex-side sessions","undercounts other hosts","read-as-reference consumption invisible","zero is weak evidence; high count is conclusive"],
|
|
library_read_not_invoked: $libs,
|
|
invocations: (map(select(.skill as $s | $libs | index($s) | not))),
|
|
library_hits: (map(select(.skill as $s | $libs | index($s)))) }'
|
|
exit 0
|
|
fi
|
|
|
|
echo "Skill usage — last ${SINCE_DAYS} days ($TDIR)"
|
|
echo
|
|
echo "CAVEATS: undercounts Codex-side, other hosts, and read-as-reference use."
|
|
echo "A high count is conclusive life; a zero is weak evidence (library skills segmented below)."
|
|
echo
|
|
echo "user-invocable invocations:"
|
|
printf '%s\n' "$raw" | while read -r c s; do
|
|
[ -n "${s:-}" ] || continue
|
|
if printf '%s\n' "$libs" | /usr/bin/grep -qx "$s"; then continue; fi
|
|
printf ' %6s %s\n' "$c" "$s"
|
|
done
|
|
echo
|
|
echo "library / read-not-invoked (counts not meaningful):"
|
|
printf '%s\n' "$raw" | while read -r c s; do
|
|
[ -n "${s:-}" ] || continue
|
|
if printf '%s\n' "$libs" | /usr/bin/grep -qx "$s"; then printf ' %6s %s\n' "$c" "$s"; fi
|
|
done
|
|
echo
|
|
echo "usage is advisory evidence; skills/*/SKILL.md metadata remains the inventory source."
|