From b9c0b6979138f1dbf8206a71d1ee3b8abeff8f89 Mon Sep 17 00:00:00 2001 From: Alex Ivantsov Date: Thu, 5 Nov 2026 12:17:52 -0500 Subject: [PATCH 1/2] fix(skills): keep synced org skills out of the OPS repo --- .claude-config/bin/verify-ops-selftest.sh | 15 +++++++++++++++ .claude-config/bin/verify-ops.sh | 7 ++++++- .gitignore | 4 ++++ CHANGELOG.md | 10 ++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/.claude-config/bin/verify-ops-selftest.sh b/.claude-config/bin/verify-ops-selftest.sh index b6f7fb3..df30513 100755 --- a/.claude-config/bin/verify-ops-selftest.sh +++ b/.claude-config/bin/verify-ops-selftest.sh @@ -422,6 +422,21 @@ ln -s "$HOME/pool-removed" "$HOME/.claude/projects/-gone/memory" run check_memory_links chk "dangling memory link -> WARN naming memory-init" "$([ "$WARNS" = 1 ] && printf '%s' "$OUT" | grep -q -- '-gone' && printf '%s' "$OUT" | grep -q 'memory-init'; echo $?)" +# 26. SKILLS/synced (Claude Code's claude.ai skill sync) is gitignored and not an OPS skill: the +# index check skips it, and a copy already committed WARNs with the untrack command. +new_case skills-synced +mkdir -p "$OPS/SKILLS/synced/org-skill" +printf -- '---\nname: org-skill\n---\n' > "$OPS/SKILLS/synced/org-skill/SKILL.md" +cp "$SRC_OPS/.gitignore" "$OPS/.gitignore" +git -C "$OPS" init -q +run check_skills_index +chk "untracked synced dir -> no WARN" "$([ "$WARNS" = 0 ]; echo $?)" +chk "the shipped .gitignore ignores SKILLS/synced" "$(git -C "$OPS" check-ignore -q SKILLS/synced/org-skill/SKILL.md; echo $?)" +git -C "$OPS" add -f SKILLS/synced/org-skill/SKILL.md +git -C "$OPS" -c user.name=selftest -c user.email=selftest@example.invalid commit -q -m fixture +run check_skills_index +chk "committed synced dir -> WARN with the untrack command" "$([ "$WARNS" = 1 ] && printf '%s' "$OUT" | grep -q 'rm -r --cached --quiet SKILLS/synced'; echo $?)" + if [ "$fails" -eq 0 ]; then echo "verify-ops-selftest: all cases pass" exit 0 diff --git a/.claude-config/bin/verify-ops.sh b/.claude-config/bin/verify-ops.sh index 9f6e568..b9364d3 100755 --- a/.claude-config/bin/verify-ops.sh +++ b/.claude-config/bin/verify-ops.sh @@ -151,14 +151,19 @@ check_secrets() { fi } -# 9. SKILLS/README index covers every skill dir. +# 9. SKILLS/README index covers every skill dir. SKILLS/synced is Claude Code's claude.ai skill +# sync (gitignored), not an OPS skill; a copy already committed is flagged so the owner untracks it. check_skills_index() { local missing="" for d in "$OPS"/SKILLS/*/; do local b; b="$(basename "$d")" + [ "$b" = synced ] && continue grep -q "$b" "$OPS/SKILLS/README.md" || missing="$missing $b" done [ -z "$missing" ] && ok "SKILLS README indexes all entries" || warn "skills absent from SKILLS/README.md index:$missing" + if [ -n "$(git -C "$OPS" ls-files -- SKILLS/synced 2>/dev/null | head -1)" ]; then + warn "SKILLS/synced is tracked: claude.ai org skills are committed to this repo; untrack them with: git -C \"$OPS\" rm -r --cached --quiet SKILLS/synced && git -C \"$OPS\" commit -m 'chore(skills): stop tracking synced org skills'" + fi } # 10. projects-map covers every repo dir under every org cluster. diff --git a/.gitignore b/.gitignore index 4b8014d..f9ba0cc 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,10 @@ desktop.ini # skills-vendor.sh staging dirs: an interrupted sync leaves one, and the next # sync cleans it; until then the daily backup must not commit a mirror copy. SKILLS/.vendor.*/ +# Claude Code syncs claude.ai org and Anthropic skills into ~/.claude/skills/synced, and +# deploy.sh links ~/.claude/skills to SKILLS/. Those skills belong to the org and refresh at +# every session start; the daily backup's `git add -A` would commit them into this repo. +SKILLS/synced/ # --- PROJECTS: repo subdirs (2nd-level) are their own repos (ignored). # Cluster folders (1st-level: ExampleOrg/, AnotherOrg/, etc.) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea18c49..db723fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ Notable changes to OPS, newest first. Format: the date, then what changed and wh matters. This file starts fresh at the public release; the harness's private prehistory is deliberately not part of it. +## 2026-11-05: synced org skills stay out of your OPS repo + +Claude Code syncs claude.ai org and Anthropic skills into `~/.claude/skills/synced`, and +`deploy.sh` links `~/.claude/skills` to `SKILLS/`, so those skills landed in `SKILLS/synced/`. +Nothing ignored them, and the daily backup's `git add -A` committed them to each owner's OPS +repo. `.gitignore` now ignores `SKILLS/synced/`. `verify-ops` check 9 skips that directory in +the README index and WARNs, with the untrack command, when a copy is already committed +(ignoring a path does not untrack files already in the repo). `verify-ops-selftest` case 26 +covers all three. + ## 2026-10-19: one always-loaded core, private-only pushes, lane-aware hooks, the sandbox, and a CI gate OPS booted through a foreman charter and an identity digest that a `claude()` From a37b1b51ad344c78649977fd38c2245f459eceec Mon Sep 17 00:00:00 2001 From: Alex Ivantsov Date: Thu, 5 Nov 2026 12:18:27 -0500 Subject: [PATCH 2/2] test(skills): make the synced-skip case independent of README wording --- .claude-config/bin/verify-ops-selftest.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.claude-config/bin/verify-ops-selftest.sh b/.claude-config/bin/verify-ops-selftest.sh index df30513..a8d42a7 100755 --- a/.claude-config/bin/verify-ops-selftest.sh +++ b/.claude-config/bin/verify-ops-selftest.sh @@ -428,6 +428,9 @@ new_case skills-synced mkdir -p "$OPS/SKILLS/synced/org-skill" printf -- '---\nname: org-skill\n---\n' > "$OPS/SKILLS/synced/org-skill/SKILL.md" cp "$SRC_OPS/.gitignore" "$OPS/.gitignore" +# The index check is a substring match and the shipped README says "un-synced"; reword it so +# only the skip, not that word, can keep the synced dir out of the index. +sed -i 's/synced/sync-ed/g' "$OPS/SKILLS/README.md" git -C "$OPS" init -q run check_skills_index chk "untracked synced dir -> no WARN" "$([ "$WARNS" = 0 ]; echo $?)"