chore(memory): skill-catalog prune — contract, plan r4, oracles, TODO, journal
This commit is contained in:
@@ -535,3 +535,5 @@ rules:
|
||||
- User go: feature/mengto-site-motion merged into develop via `gitflow finish` → d3633db, no conflict (develop had not moved), pushed, local + origin copies removed by the lib. develop == origin/develop, no working branch anywhere. The whole review is on develop: cases 1-5 (yesterday) + case 7 (today). Open for the user: `make link` + `bash lib/profile.sh apply full` (8 vendored externals to symlink), `rm -rf /tmp/mengto-verify /tmp/tmp.AAyJzvufO6`.
|
||||
- User: "tout cela s'installe et se met à jour comme le reste ?" → traced: install/plugin/update/link all cover the 8 vendored skills; only `make doctor` was blind to curl-vendored externals (since emil). User go → /feat by hand: `lib/doctor-vendored.sh` + doctor section + README (6394fa7); gates MET, verifier CONFORME ×2, security PASS ×2 after one re-dispatch (MEDIUM traceback leak on malformed lock, LOW allowlists). 37 suites green minus 2 pre-existing T16a. feature/doctor-vendored-skills UNMERGED — human gate. Live: 129 skills in the census, 11 externals ✓ in doctor.
|
||||
- User go: feature/doctor-vendored-skills merged into develop via `gitflow finish` → 2c94a0c, no conflict, pushed, local + origin copies removed by the lib. develop == origin/develop, no working branch anywhere. `make doctor` now covers the 11 vendored externals.
|
||||
- Skill-catalog audit (user: "tour des skills, doublons, économiser tokens"): 5 analyzers over 150 skills / 53.5k chars desc; 78 listed name-only this session (listing budget ≈1 % ctx, least-invoked lose desc → gain = routing quality + no broken 100 KB body invoked, not listing chars). Found: frontend-design plugin byte-dup of managed copy; brightdata 21 skills keyless + hostile WebFetch routing; gstack ship trunk-based (origin/HEAD=main), land-and-deploy auto-merge+deploy, autoplan/make-pdf/diagram/careful/guard/freeze dead paths (only bin + browse/dist linked); security-guidance = Opus call per code turn + agentic commit review, 0 findings/6 days; doctor.sh undercount ×6. User go: tier 1, superpowers vendor-7 (tier 2 later), 21st trio parked (CLI `Not logged in`), rule "full ⊇ every profile, max = everything". Live: brightdata disabled, frontend-design plugin uninstalled, `set full` → 75 skills (was 89).
|
||||
- /feat by hand on feature/skill-catalog-prune: contract 18 criteria; plan r1→r4 (3 challengers, confirmation FATAL(4): nested SKILL.md in browser-skills/openclaw/node_modules, ./setup global symlink, update-all 3rd copy); 4 feater parallel DONE; GATE 0 UNMET(4) = MY heredoc CHECKs (gates.sh single-line) → oracles to `<contract>.oracles/*.py` → MET; verifier ECARTS(1) = floor-guard `xit(` false-positive on `sys.exit(` → restructure → CONFORME; security PASS. 41 suites green minus 2 pre-existing T16a, shellcheck clean. UNMERGED — human gate. Registries pending user approval.
|
||||
|
||||
@@ -1,5 +1,54 @@
|
||||
# TODO
|
||||
|
||||
## 2026-09-28 — skill-catalog prune, tier 1 (feature/skill-catalog-prune)
|
||||
User go after the 5-agent duplicate audit (150 skills, 53.5k chars of descriptions,
|
||||
78 listed name-only in session = listing budget exceeded). Contract
|
||||
`.claude/tasks/contracts/2026-09-28-skill-catalog-prune-0554.md`, plan
|
||||
`.claude/tasks/plans/2026-09-28-skill-catalog-prune-0554.md`. Live already done:
|
||||
`claude plugin disable brightdata-plugin@synced`, `claude plugin uninstall
|
||||
frontend-design@claude-plugins-official` (byte-identical to the managed copy).
|
||||
- [x] K1 profiles: the 9 broken/doctrine-breaking gstack out of every profile
|
||||
(ship trunk-based, land-and-deploy auto-merge+deploy, setup-deploy, autoplan
|
||||
dead paths, context-save orphan, learn unused, careful/guard vacuous hooks,
|
||||
design-shotgun needs OPENAI_API_KEY); make-pdf + diagram + 21st-ai/
|
||||
ui-explore/ui-review parked out of `full` (trio out of web/web-full/design
|
||||
too); user rule: full ⊇ every other profile, `max` (`# SUPERSET-OF: full`)
|
||||
= full + parked; profile docs (SKILL.md, README, USAGE); hermetic
|
||||
`lib/tests/profile-census.test.sh` (removed / parked / union invariants).
|
||||
- [x] K2 wiring: link.sh helper links make-pdf/dist + lib/diagram-render/dist
|
||||
(+ freeze/bin if gated); doctor.sh counts symlinked skills + block-scalar
|
||||
descriptions + synced bucket info line, plugin constants re-based.
|
||||
- [x] K3 docs/config: settings.json env `ENABLE_STOP_REVIEW=0` (security-guidance
|
||||
Stop LLM review off, commit/push review kept); CLAUDE.global.md routing
|
||||
(Ship/PR → ship-feature, gstack-off list); deploy/SKILL.md rows;
|
||||
install-plugins.sh notes + summary "0 tokens" fix; plugin-advisor.md cost
|
||||
text; CHANGELOG.
|
||||
- [x] K4 plan r1→r4 (3 challengers + 1 confirmation pass, FATAL(4) closed by
|
||||
named changes), 4 feater parallel DONE, GATE 0 MET after moving 4 heredoc
|
||||
oracles to `<contract>.oracles/*.py` (gates.sh CHECK is single-line),
|
||||
verifier CONFORME at iteration 2 (floor-guard `xit(` false positive on
|
||||
`sys.exit(` → restructure), security PASS, make test 41 suites green minus
|
||||
2 pre-existing T16a, shellcheck clean. Live: `set full` applied, 75 skills
|
||||
listed (was 89), 16 parked, doctor 75 / ~5.4k t.
|
||||
- [ ] K5 registries on user approval (BDR prune + full/max rule, LRN listing
|
||||
budget, LRN gates.sh single-line CHECK, LRN gstack helper-tree class, BLK
|
||||
floor-guard `xit(` pattern, EVAL challenge round), journal. UNMERGED — human
|
||||
gate. After merge on any other machine: `make link` + `bash lib/profile.sh
|
||||
set full` (NOT `apply`: additive). Follow-ups: floor-guard `xit(` → word
|
||||
boundary (hotfix); gates.sh could refuse a CHECK holding `<<`; optional
|
||||
doctor info line for the claude.ai synced bucket; `21st login`; claude.ai
|
||||
skills useless in CLI off (built-in-browser, chrome-browser, computer-use,
|
||||
skill-creator, import-memory). Tier 2 superpowers vendoring next.
|
||||
Tier 2 (decided, not started): vendor brainstorming, writing-plans,
|
||||
subagent-driven-development, test-driven-development, requesting-code-review,
|
||||
using-git-worktrees, writing-skills from obra/superpowers at 5bf4e78 via
|
||||
lib/vendor-skills.sh; drop the plugin (PROTECTED_PLUGINS, STEP 5, detect, banner,
|
||||
doctor constants); rename `superpowers:` citers (ship-feature ×4, init-project ×4,
|
||||
tour, deploy, audit-delta, lib/analyze-before-plan, lib/capitalize-commit,
|
||||
plugin-advisor). User side: `21st login` (CLI reports Not logged in); claude.ai
|
||||
skills useless in CLI (built-in-browser, chrome-browser, computer-use,
|
||||
skill-creator, import-memory) to switch off in claude.ai settings.
|
||||
|
||||
## 2026-09-28 — make doctor checks the vendored externals (feature/doctor-vendored-skills)
|
||||
User go "ok ajoute le check doctor" after the install/update/link trace: doctor.sh only
|
||||
checked the gstack submodule; emil, frontend-design, motion and the 8 curl-vendored
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# CONTRACT — skill-catalog-prune
|
||||
- date: 2026-09-28 | flow: feat (ad-hoc dispatch, /feat gates replayed by the orchestrator, 3 parallel feater executors) | branch: feature/skill-catalog-prune
|
||||
- status: active
|
||||
|
||||
## REQUEST (verbatim — IMMUTABLE)
|
||||
> j'aimerias que tu fasse le tour des skills perso et installe, gstack compris, superpowers compris, que tu vois si il y a des doublons, S'il y en as, supprime le moins performant de l'installation auto, update etc (on supprimera le skill / agent en question apres installation du plugin si necesaire) on va faire en sorte d'economiser le plus de token possible comme ca. Fais le tour d'analyse, vois les doublons, vois les quels supprimer et retirer de la config car inutile
|
||||
|
||||
User answers to the decision batch (verbatim):
|
||||
> Tier 1 → "Go, tout le tier 1 (Recommended)"
|
||||
> Superpowers → "Vendoriser 7, retirer le plugin (Recommended)" [tier 2, separate branch, NOT this contract]
|
||||
> 21st → "21st est en cli, et j'ai connecté le cli, n'est-ce pas ? On a retiré le mcp sinon 1" [CLI answers `Not logged in` → option 1: park 21st-ai, 21st-ui-explore, 21st-ui-review]
|
||||
> gstack reste → "Parquer les 10 redondants hors full, Réparer make-pdf et diagram dans link.sh, Parquer make-pdf et diagram aussi, Avoir la possibilité de choisir un profil avec si on en a besoin, du style full + parked"
|
||||
|
||||
Tier 1 as presented and approved: disable brightdata (synced), remove the duplicate
|
||||
frontend-design plugin, take the 9 broken/doctrine-breaking gstack skills (ship,
|
||||
land-and-deploy, setup-deploy, autoplan, context-save, learn, careful, guard,
|
||||
design-shotgun) out of the profiles with the routing lines corrected, switch the
|
||||
security-guidance Stop layer off, fix doctor.sh and the "0 tokens" claims.
|
||||
|
||||
## CLARIFICATIONS
|
||||
- Pass A: none — request complete (outcome, scope and constraints derivable from the audit).
|
||||
- Live state already changed by the orchestrator before dispatch (user go): `claude plugin disable brightdata-plugin@synced` wrote `"brightdata-plugin@synced": false` into settings.json; `claude plugin uninstall frontend-design@claude-plugins-official` removed its enabledPlugins entry and cache. The executor keeps both states.
|
||||
- The superset profile carries a header line `# SUPERSET-OF: full` so oracles find it by content, not by name (internal choice).
|
||||
- "Parked" = kept installed, out of the default `full` profile, listed only in the superset profile (and in the specialized profiles that already carry them, see pass B). The 9 removed gstack skills go in NO profile, superset included: they are broken (autoplan, careful/guard hooks exit 127, context-save without restore) or break doctrine (ship base = origin/HEAD = main, land-and-deploy auto-merges and deploys), or need an absent key (design-shotgun → OPENAI_API_KEY).
|
||||
- security-guidance stays installed and enabled (PROTECTED); only the Stop-hook LLM review is switched off through the plugin's own env switch `ENABLE_STOP_REVIEW=0` in settings.json `env`; commit/push agentic review and the regex layer stay on.
|
||||
- [gated 2026-09-28] Q: superset profile name? / A: `max`.
|
||||
- [gated 2026-09-28] Q: 11 redundant gstack out of the specialized profiles too? / A: NO — user rule: "full must already carry what every other profile has; max has everything; these gstack matter in full because full must do what each profile does". So the 11 redundant gstack STAY in full (and in their specialized profiles). Parked = only what no specialized profile carries: make-pdf, diagram, and the 21st trio. `full` ⊇ union of every non-max profile, minus the 9 removed names and the 21st trio, minus an explicit exception allowlist written in the census test with its reason (pr-review-toolkit: plugin deliberately out of full, audit 2026-07-02 #12, ~2.2k tokens; measured 2026-09-28: it is the ONLY name any specialized profile carries that full lacks).
|
||||
- [gated 2026-09-28] Q: 21st-ai / 21st-ui-explore / 21st-ui-review scope? / A: out of all four design-bearing profiles (full, web, web-full, design), kept in `max`; CLAUDE.global.md "Review / audit" line drops "+ 21st-ui-review"; GATE-BLOCK untouched.
|
||||
- [gated 2026-09-28] Q: add the `freeze/bin` helper link too? / A: yes, same fix as make-pdf/diagram.
|
||||
- [challenge 2026-09-28, 3 blind challengers, no BLOCKER, 8 MAJOR adopted] (a) the broken-wiring class is every `~/.claude/skills/gstack/<path>` the gstack skills hardcode (bin, scripts, ETHOS.md, lib, design/dist, extension, */sections, review/checklist+specialists, make-pdf/dist, freeze/bin…), fixed by one shared `lib/gstack-links.sh` used by link.sh AND install-plugins.sh, exposing no SKILL.md; (b) `profile.sh apply` is additive, only `set`/`reset` park: the live tree is proven by criterion 16 (`set full`); (c) `gstack on` / `toggle-external enable gstack` honor the `GSTACK_REMOVED` denylist (`lib/gstack-removed.sh`, single source); (d) `max` ⊇ union of every profile − GSTACK_REMOVED (so it carries pr-review-toolkit); (e) census test = passing baseline then one mutant per invariant; (f) doctor reuses lib/skill-routing-census.py's parser through `lib/doctor-skills.sh`; (g) no synced-bucket line in doctor (scope). These refine criteria 2, 3, 8, 9 and add 16-18 below; they are reported to the human at the merge gate.
|
||||
- [confirmation pass 2026-09-28, robustness FATAL(4), every finding closed by a named plan change, r4] helper lib skips nested-SKILL.md dirs and removes/refuses a dst symlink; update-all.sh joins the shared lib (criterion 18); the three existing profile/toggle suites copy lib/gstack-removed.sh into their fixtures; `gstack on` reports the real restored count; doctor stats warn on fallback.
|
||||
- Functions ≤ 25 logic lines, 80-char lines, ≤ 5 params, ≤ 5 locals (CLAUDE.global.md). Shell edits shellcheck-clean.
|
||||
- Executors never run `gitflow`, never commit, never run `claude plugin …`, never touch `skills/`, `skills-external/`, `~/.claude` outside link.sh's own effect, never delete anything.
|
||||
|
||||
## ACCEPTANCE CRITERIA
|
||||
1. The 9 removed gstack skills appear as an entry in no profile (positive control first).
|
||||
CHECK: pat='^(ship|land-and-deploy|setup-deploy|autoplan|context-save|learn|careful|guard|design-shotgun)([[:space:]]|$)'; printf 'ship\n' | grep -qE "$pat" || { echo control-failed; exit 1; }; if grep -lE "$pat" lib/profiles/*.profile; then echo listed-somewhere; exit 1; fi; echo PROFILES_CLEAN
|
||||
EXPECT: PROFILES_CLEAN
|
||||
EVIDENCE: MET exit=0 marker-found :: PROFILES_CLEAN
|
||||
2. `full` lists none of the 5 parked names; exactly one profile carries `# SUPERSET-OF: full` (it is `max`), it lists every entry of full, every parked name, and every name any profile carries (max ⊇ union − removed). [gated 2026-09-28: parked set = 5; challenge: union]
|
||||
CHECK: python3 .claude/tasks/contracts/2026-09-28-skill-catalog-prune-0554.oracles/c2.py
|
||||
EXPECT: SUPERSET_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: SUPERSET_OK
|
||||
3. After link.sh, every helper path the gstack skills hardcode under `~/.claude/skills/gstack/` and that exists in the submodule resolves (recomputed from a grep census of the skills; `<skill>/SKILL.md` cross-reads and `.git` excluded), and no SKILL.md is exposed anywhere under the helper tree. [challenge 2026-09-28: whole class, was 3 paths]
|
||||
CHECK: bash link.sh >/dev/null 2>&1; python3 .claude/tasks/contracts/2026-09-28-skill-catalog-prune-0554.oracles/c3.py
|
||||
EXPECT: LINKS_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: LINKS_OK
|
||||
4. doctor.sh counts every skill reachable through `~/.claude/skills/*/SKILL.md` (symlinks included) and sums block-scalar descriptions too (recomputed independently, ±5 %).
|
||||
CHECK: python3 .claude/tasks/contracts/2026-09-28-skill-catalog-prune-0554.oracles/c4.py
|
||||
EXPECT: DOCTOR_COUNTS
|
||||
EVIDENCE: MET exit=0 marker-found :: DOCTOR_COUNTS
|
||||
5. settings.json: Stop review off, brightdata off, frontend-design plugin gone.
|
||||
CHECK: python3 -c "import json;d=json.load(open('settings.json'));e=d['enabledPlugins'];assert d['env']['ENABLE_STOP_REVIEW']=='0';assert e['brightdata-plugin@synced'] is False;assert 'frontend-design@claude-plugins-official' not in e;print('SETTINGS_OK')"
|
||||
EXPECT: SETTINGS_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: SETTINGS_OK
|
||||
6. No repo doc still claims security-guidance costs 0 tokens (positive control first).
|
||||
CHECK: pat='security-guidance.*0 tokens|0 tokens.*security-guidance'; echo 'security-guidance (0 tokens)' | grep -qE "$pat" || exit 1; if grep -rnE "$pat" install-plugins.sh agents/plugin-advisor.md; then exit 1; fi; echo DOCS_OK
|
||||
EXPECT: DOCS_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: DOCS_OK
|
||||
7. Routing: Ship/PR routes to ship-feature, the gstack-off list no longer names ship/context-save, deploy's table no longer routes to land-and-deploy/setup-deploy; the 21st trio is out of full/web/web-full/design and off the Design Review line. [gated 2026-09-28]
|
||||
CHECK: grep -qE '^- Ship / PR → ship-feature' CLAUDE.global.md && ! grep -qE 'Ship / PR → ship \(' CLAUDE.global.md && ! grep -q 'context-save' CLAUDE.global.md && ! grep -qE 'land-and-deploy|setup-deploy' skills/deploy/SKILL.md && ! grep -q '21st-ui-review' CLAUDE.global.md && ! grep -lE '^21st-(ai|ui-explore|ui-review)([[:space:]]|$)' lib/profiles/full.profile lib/profiles/web.profile lib/profiles/web-full.profile lib/profiles/design.profile && echo ROUTING_OK
|
||||
EXPECT: ROUTING_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: ROUTING_OK
|
||||
8. Hermetic profile census suite green: baseline fixture passes, each of the three mutants is detected for its own reason. [challenge 2026-09-28]
|
||||
CHECK: out=$(make test suite=lib/tests/profile-census.test.sh 2>&1); echo "$out" | grep -qE 'FAIL=[1-9]' && { echo "$out" | tail -15; exit 1; }; for k in FIXTURE_BASELINE_OK FIXTURE_REMOVED_DETECTED FIXTURE_SUPERSET_DETECTED FIXTURE_FULLGAP_DETECTED; do echo "$out" | grep -q "$k" || { echo "missing $k"; exit 1; }; done; echo "$out" | grep -qE 'PASS=[1-9]' && echo SUITE_GREEN
|
||||
EXPECT: SUITE_GREEN
|
||||
EVIDENCE: MET exit=0 marker-found :: SUITE_GREEN
|
||||
9. shellcheck clean on every touched shell file; doctrine-citers census and the four new hermetic suites green, existing profile/toggle suites still green. [challenge 2026-09-28]
|
||||
CHECK: shellcheck link.sh doctor.sh install-plugins.sh update-all.sh lib/profile.sh lib/toggle-external.sh lib/gstack-links.sh lib/gstack-removed.sh lib/doctor-skills.sh lib/tests/profile-census.test.sh lib/tests/gstack-removed.test.sh lib/tests/gstack-links.test.sh lib/tests/doctor-skills.test.sh && for s in doctrine-citers gstack-removed gstack-links doctor-skills profile-default profile-set-managed toggle-external-repo-resolution; do out=$(make test suite=lib/tests/$s.test.sh 2>&1) || { echo "$s rc"; exit 1; }; echo "$out" | grep -qE 'FAIL=[1-9]' && { echo "$s FAIL"; exit 1; }; done; echo SHELL_DOCTRINE_OK
|
||||
EXPECT: SHELL_DOCTRINE_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: SHELL_DOCTRINE_OK
|
||||
10. Profile docs name the superset profile and full's new meaning: skills/profile/SKILL.md table, README.md (`/profile` row and `make profile` lines), USAGE.md `/profile` row.
|
||||
11. install-plugins.sh STEP 5 carries two notes (frontend-design@claude-plugins-official never installed: byte-identical duplicate of the managed skills-external copy; brightdata-plugin@synced kept disabled: account-synced, keyless-useless, its bright-data-mcp skill would hijack WebFetch/WebSearch) and the summary line describes security-guidance truthfully (hooks + out-of-band LLM reviews, quota not context).
|
||||
12. agents/plugin-advisor.md describes security-guidance's real mechanics (regex on Edit/Write, agentic review on commit/push, Stop review disabled by env) and drops the "Hook-only / 0 tokens" wording.
|
||||
13. CHANGELOG.md `[Unreleased]` entry describing the prune (Removed / Changed / Fixed as fits Keep a Changelog).
|
||||
14. The 9 removed gstack skills are removed from every profile that listed them (dev, backend, web, web-full, design, full), not only from full.
|
||||
15. `full` carries every entry of every other non-max profile, minus the 9 removed names, the 21st trio and the allowlisted exceptions (each with a reason in the census test). [gated 2026-09-28 — user rule "full does what each profile does"]
|
||||
CHECK: python3 .claude/tasks/contracts/2026-09-28-skill-catalog-prune-0554.oracles/c15.py
|
||||
EXPECT: FULL_UNION_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: FULL_UNION_OK
|
||||
16. Live tree after `bash lib/profile.sh set full`: none of the 9 removed nor the 5 parked names resolves under `~/.claude/skills/`, and `profile current` names full. [challenge 2026-09-28: apply is additive, set parks]
|
||||
CHECK: bash lib/profile.sh set full >/dev/null 2>&1; bad=""; for n in ship land-and-deploy setup-deploy autoplan context-save learn careful guard design-shotgun make-pdf diagram 21st-ai 21st-ui-explore 21st-ui-review; do [ -e "$HOME/.claude/skills/$n" ] && bad="$bad $n"; done; [ -z "$bad" ] || { echo "live:$bad"; exit 1; }; [ -e "$HOME/.claude/skills/browse" ] || { echo browse-missing; exit 1; }; [ "$(bash lib/profile.sh current 2>/dev/null | awk '{print $1}')" = full ] && echo LIVE_CLEAN
|
||||
EXPECT: LIVE_CLEAN
|
||||
EVIDENCE: MET exit=0 marker-found :: LIVE_CLEAN
|
||||
17. `gstack on` and `toggle-external.sh enable gstack` skip the removed names (hermetic suite, see criterion 9), and both scripts source lib/gstack-removed.sh.
|
||||
CHECK: grep -q 'gstack-removed.sh' lib/profile.sh && grep -q 'gstack-removed.sh' lib/toggle-external.sh && grep -q 'gstack_is_removed' lib/profile.sh && grep -q 'gstack_is_removed' lib/toggle-external.sh && echo DENYLIST_WIRED
|
||||
EXPECT: DENYLIST_WIRED
|
||||
EVIDENCE: MET exit=0 marker-found :: DENYLIST_WIRED
|
||||
18. Neither install-plugins.sh nor update-all.sh carries its own copy of the helper-link block; link.sh and both installers go through lib/gstack-links.sh. [confirmation pass 2026-09-28]
|
||||
CHECK: grep -q 'gstack-links.sh' link.sh && grep -q 'gstack-links.sh' install-plugins.sh && grep -q 'gstack-links.sh' update-all.sh && ! grep -q 'ln -sf "$GSTACK_DIR/browse/dist"' install-plugins.sh && ! grep -q 'ln -sf "$GSTACK_SRC/browse/dist"' link.sh && ! grep -q 'ln -sf "$GSTACK_DIR/bin"' update-all.sh && echo LINKS_SHARED
|
||||
EXPECT: LINKS_SHARED
|
||||
EVIDENCE: MET exit=0 marker-found :: LINKS_SHARED
|
||||
|
||||
## FILE SCOPE
|
||||
- lib/profiles/full.profile, lib/profiles/max.profile (new), lib/profiles/{dev,backend,web,web-full,design}.profile
|
||||
- lib/tests/profile-census.test.sh, lib/tests/gstack-removed.test.sh, lib/tests/gstack-links.test.sh, lib/tests/doctor-skills.test.sh (new)
|
||||
- lib/gstack-removed.sh (orchestrator-written), lib/gstack-links.sh, lib/doctor-skills.sh (new); lib/profile.sh, lib/toggle-external.sh (denylist)
|
||||
- link.sh, doctor.sh, update-all.sh (helper-link block), install-plugins.sh (STEP 2 helper-link block, STEP 5 comments, summary lines)
|
||||
- lib/tests/{profile-default,profile-set-managed,toggle-external-repo-resolution}.test.sh (fixture copy of lib/gstack-removed.sh only)
|
||||
- settings.json (env block + enabledPlugins only)
|
||||
- agents/plugin-advisor.md
|
||||
- CLAUDE.global.md (Skill routing lines + Design work Review line only), skills/deploy/SKILL.md (routing table rows only)
|
||||
- skills/profile/SKILL.md, README.md, USAGE.md, CHANGELOG.md
|
||||
- Orchestrator-only: .claude/tasks/**, .claude/memory/**
|
||||
@@ -0,0 +1,16 @@
|
||||
import glob,os,re
|
||||
def entries(p): return {l.split()[0] for l in open(p) if l.strip() and not l.lstrip().startswith('#')}
|
||||
full=entries('lib/profiles/full.profile')
|
||||
removed={'ship','land-and-deploy','setup-deploy','autoplan','context-save','learn','careful','guard','design-shotgun'}
|
||||
trio={'21st-ai','21st-ui-explore','21st-ui-review'}
|
||||
test=open('lib/tests/profile-census.test.sh').read()
|
||||
m=re.search(r'^\s*FULL_EXCEPTIONS=\(([^)]*)\)',test,re.M)
|
||||
allow=set(m.group(1).split()) if m else set()
|
||||
gap=set()
|
||||
for p in glob.glob('lib/profiles/*.profile'):
|
||||
if os.path.basename(p) in ('full.profile','max.profile'): continue
|
||||
gap|=entries(p)-full
|
||||
gap-=removed|trio|allow
|
||||
assert not gap, sorted(gap)
|
||||
assert 'pr-review-toolkit' in allow, 'allowlist must name pr-review-toolkit'
|
||||
print('FULL_UNION_OK')
|
||||
@@ -0,0 +1,15 @@
|
||||
import glob,re,sys
|
||||
def entries(p): return {l.split()[0] for l in open(p) if l.strip() and not l.lstrip().startswith('#')}
|
||||
sup=[p for p in glob.glob('lib/profiles/*.profile') if re.search(r'^# SUPERSET-OF: full\s*$',open(p).read(),re.M)]
|
||||
assert len(sup)==1, sup
|
||||
full=entries('lib/profiles/full.profile'); S=entries(sup[0])
|
||||
parked={'make-pdf','diagram','21st-ai','21st-ui-explore','21st-ui-review'}
|
||||
assert not (parked & full), parked & full
|
||||
assert full <= S, full - S
|
||||
assert parked <= S, parked - S
|
||||
removed={'ship','land-and-deploy','setup-deploy','autoplan','context-save','learn','careful','guard','design-shotgun'}
|
||||
U=set()
|
||||
for p in glob.glob('lib/profiles/*.profile'): U|=entries(p)
|
||||
assert not (removed & U), removed & U
|
||||
assert (U-removed) <= S, (U-removed)-S
|
||||
print('SUPERSET_OK')
|
||||
@@ -0,0 +1,14 @@
|
||||
import glob,os,re,subprocess
|
||||
H=os.path.expanduser('~'); SRC='skills-external/gstack'; DST=H+'/.claude/skills/gstack'
|
||||
txt=''.join(open(f,errors='ignore').read() for f in glob.glob(SRC+'/*/SKILL.md'))
|
||||
paths=set(re.findall(r'(?:~|\$HOME)/\.claude/skills/gstack/([A-Za-z0-9_./-]+)',txt))
|
||||
paths={p.rstrip('.') for p in paths}
|
||||
want=[p for p in sorted(paths) if os.path.exists(os.path.join(SRC,p)) and not p.endswith('SKILL.md') and not p.startswith('.git') and not p.startswith('.feature-prompted')]
|
||||
assert len(want)>=20, want
|
||||
missing=[p for p in want if not os.path.exists(os.path.join(DST,p))]
|
||||
assert not missing, missing
|
||||
for p in ('make-pdf/dist/pdf','lib/diagram-render/dist/diagram-render.html','freeze/bin/check-freeze.sh','scripts/jargon-list.json','ETHOS.md'):
|
||||
assert os.path.exists(os.path.join(DST,p)), p
|
||||
out=subprocess.run(['find','-L',DST,'-name','SKILL.md'],capture_output=True,text=True).stdout.strip()
|
||||
assert out=='', out
|
||||
print('LINKS_OK')
|
||||
@@ -0,0 +1,15 @@
|
||||
import glob,re,subprocess,os
|
||||
H=os.path.expanduser('~')
|
||||
files=glob.glob(H+'/.claude/skills/*/SKILL.md')
|
||||
def desc(p):
|
||||
t=open(p,encoding='utf-8',errors='ignore').read(); m=re.match(r'^---\n(.*?)\n---',t,re.S)
|
||||
if not m: return 0
|
||||
d=re.search(r'^description:\s*(\|[-+]?|>[-+]?)?\s*(.*?)(?=^\S|\Z)',m.group(1),re.S|re.M)
|
||||
return len(d.group(2).strip()) if d else 0
|
||||
exp_chars=sum(desc(p) for p in files); exp_n=len(files)
|
||||
out=subprocess.run(['bash','doctor.sh'],capture_output=True,text=True).stdout
|
||||
m=re.search(r'Skill descriptions:\s+~(\d+)t\s+\((\d+) skills\)',out); assert m, 'no skill line'
|
||||
tok,n=int(m.group(1)),int(m.group(2))
|
||||
assert n==exp_n,(n,exp_n)
|
||||
assert abs(tok*4-exp_chars)<=exp_chars*0.05,(tok*4,exp_chars)
|
||||
print('DOCTOR_COUNTS')
|
||||
@@ -0,0 +1,375 @@
|
||||
# PLAN — skill-catalog-prune (feat, ad-hoc dispatch) — r4 (after confirmation pass)
|
||||
- r4 closes the confirmation pass (robustness FATAL(4)): BLOCKER 1 — the
|
||||
helper lib skips any non-skill dir holding a nested SKILL.md
|
||||
(browser-skills/, node_modules/, openclaw/) and `.git*`/`node_modules`
|
||||
by name, fixture `other/deep/SKILL.md`; MAJOR 2 — the three existing
|
||||
profile/toggle suites copy lib/gstack-removed.sh into their fixture
|
||||
(E1b scope); MAJOR 3 — the lib removes a stale `dst` symlink (gstack
|
||||
./setup plants `~/.claude/skills/gstack -> submodule`) and refuses a dst
|
||||
that resolves inside src, fixture case added; MAJOR 4 — update-all.sh's
|
||||
third copy of the block goes through the lib too (criterion 18); MINOR
|
||||
5-8 — real restored count after the skip, policy message instead of the
|
||||
setup hint, `len(x or '')` + stderr warn on fallback, E2 owns every
|
||||
install-plugins.sh edit (E3 no longer touches it).
|
||||
- date: 2026-09-28 | contract: contracts/2026-09-28-skill-catalog-prune-0554.md
|
||||
- branch: feature/skill-catalog-prune
|
||||
- executors: 4 feater (sonnet-pinned), parallel, disjoint file sets, same tree
|
||||
- r3 closes: correctness MAJOR 1-3 / MINOR 4-9, robustness MAJOR 1-4 / MINOR
|
||||
5-9, simplicity MAJOR 1 / MINOR 2-5. Named changes: whole-class gstack
|
||||
helper links through one shared lib (E2), `GSTACK_REMOVED` denylist that
|
||||
`gstack on` / `enable gstack` honor (E1b), `max` = union of every profile
|
||||
(E1a), live `set full` as an oracle (criterion 16), census test with a
|
||||
passing baseline then one mutant per invariant (E1a), description parser
|
||||
reused from lib/skill-routing-census.py (E2), synced info line dropped,
|
||||
stale r1 wording swept.
|
||||
- r2: superset = `max`; full keeps the 11 redundant gstack (user rule: full
|
||||
⊇ every other profile, max = everything); parked = make-pdf, diagram,
|
||||
21st trio; 21st trio out of full/web/web-full/design; freeze/bin link.
|
||||
|
||||
## Ground truth (verified on the live tree, 2026-09-28)
|
||||
|
||||
- Catalog: 150 skills, 53.5k chars of descriptions; the harness lists only
|
||||
~19k chars of them with a description (least-invoked skills lose theirs).
|
||||
78 skills are name-only in the current session.
|
||||
- gstack skills are symlinked per profile (BDR-030), `full` is the default
|
||||
(BDR-101). `profile.sh apply` is ADDITIVE (enables only); `set` and `reset`
|
||||
park what the profile does not list (`disable_gstack_not_in`,
|
||||
`disable_externals_not_in`, `disable_plugins_not_in`) then apply. So a
|
||||
name dropped from full leaves the live tree only at the next `set full`
|
||||
or `reset`; nothing runs that automatically. `gstack on`
|
||||
(`enable_all_gstack`) and `lib/toggle-external.sh enable gstack` move
|
||||
EVERY `skills-disabled/gstack__*` back, with no denylist.
|
||||
- gstack skills hardcode `~/.claude/skills/gstack/<path>` for shared
|
||||
assets, but link.sh (and a duplicate block in install-plugins.sh
|
||||
STEP 2) only create `gstack/bin` and `gstack/browse/dist`. Census of the
|
||||
hardcoded paths (`grep -rhoE '(~|\$HOME)/\.claude/skills/gstack/[A-Za-z0-9_./-]+'
|
||||
skills-external/gstack/*/SKILL.md`): bin/* (dozens), scripts/jargon-list.json,
|
||||
ETHOS.md, browse/bin/remote-slug, browse/dist/browse, design/dist/design,
|
||||
extension/, lib/diagram-render/dist/diagram-render.html, make-pdf/dist/pdf,
|
||||
freeze/bin/check-freeze.sh, careful/bin/check-careful.sh, */sections/*.md
|
||||
(plan-*-review, cso, office-hours, design-consultation, document-release),
|
||||
review/checklist.md, review/specialists/*.md, and other skills' SKILL.md
|
||||
(office-hours/SKILL.md, gstack-upgrade/SKILL.md). Everything but `bin` and
|
||||
`browse/dist` is unreachable today: make-pdf returns MAKE_PDF_NOT_AVAILABLE,
|
||||
diagram BUNDLE_MISSING, the careful/guard/freeze hooks exit 127 and never
|
||||
fire (LRN-096 class), cso and plan-*-review cannot read their sections.
|
||||
- A global `skills/gstack -> skills-external/gstack` symlink is forbidden
|
||||
(link.sh comment): the top-level gstack SKILL.md then lists as a duplicate
|
||||
skill. Skill discovery reads `~/.claude/skills/<name>/SKILL.md`; a
|
||||
`SKILL.md` must therefore never sit at `~/.claude/skills/gstack/SKILL.md`,
|
||||
and no `<skill>/SKILL.md` is linked below `gstack/` either (only their
|
||||
non-SKILL.md children), so the helper tree exposes no skill file.
|
||||
- gstack `ship` resolves its base from `origin/HEAD` on Gitea → main; it
|
||||
diffs and PRs against main, skipping develop. `land-and-deploy` runs
|
||||
`gh pr merge --squash --delete-branch` then waits for the deploy.
|
||||
- security-guidance 2.0.0: SessionStart venv, UserPromptSubmit baseline,
|
||||
PostToolUse regex, Stop = direct POST /v1/messages (opus-4-7, thinking
|
||||
10000) on every turn that changed source, commit/push = Agent SDK review
|
||||
up to 18 turns. Log 2026-09-22→28: 0 findings, 1 recorded false positive
|
||||
(EVAL-024). `ENABLE_STOP_REVIEW=0` is the plugin's own switch.
|
||||
- settings.json has NO top-level `env` key today (create it). It is the
|
||||
live `~/.claude/settings.json`: validate JSON right after the edit.
|
||||
- doctor.sh: `find -maxdepth 2` without `-L` → 34 skills; `grep
|
||||
'^description:' | head -1` → block scalars count 0 chars. doctor.sh runs
|
||||
under `set -euo pipefail`. `lib/skill-routing-census.py` already has a
|
||||
tested `extract_description()` handling `|`/`>` blocks (hyphenated file
|
||||
name → load with importlib, not `import`).
|
||||
- Specialized profiles carry exactly ONE name full lacks: pr-review-toolkit
|
||||
(audit.profile; plugin deliberately out of full, audit 2026-07-02 #12,
|
||||
~2.2k tokens when enabled; MANAGED_PLUGINS so `set` toggles it).
|
||||
- Already done live (user go): brightdata disabled (`false` in
|
||||
settings.json), frontend-design@claude-plugins-official uninstalled
|
||||
(entry removed). `lib/gstack-removed.sh` written by the orchestrator:
|
||||
`GSTACK_REMOVED=(…9…)` + `gstack_is_removed <name>`.
|
||||
|
||||
## Approach
|
||||
|
||||
### E1a — profiles + census suite + profile docs
|
||||
Files: lib/profiles/{full,dev,backend,web,web-full,design}.profile,
|
||||
lib/profiles/max.profile (new), lib/tests/profile-census.test.sh (new),
|
||||
skills/profile/SKILL.md, README.md, USAGE.md.
|
||||
|
||||
1. Remove the 9 `GSTACK_REMOVED` entries (`ship land-and-deploy setup-deploy
|
||||
autoplan context-save learn careful guard design-shotgun`) from EVERY
|
||||
profile that lists them (dev, backend, web, web-full, design, full).
|
||||
Comment lines describing only them go too. Keep `freeze` and `unfreeze`.
|
||||
2. `full.profile`: additionally remove the 5 parked entries `make-pdf
|
||||
diagram 21st-ai 21st-ui-explore 21st-ui-review`. The 11 redundant gstack
|
||||
(plan-ceo/design/devex-review, spec, review, retro, investigate, canary,
|
||||
qa, open-gstack-browser, setup-browser-cookies) STAY. Rewrite `# DESC:`:
|
||||
default profile; carries everything every other profile carries (user
|
||||
rule 2026-09-28, one exception: pr-review-toolkit); no broken or
|
||||
doctrine-breaking gstack (see lib/gstack-removed.sh); parked tools live
|
||||
in `max`. The pr-review-toolkit comment block stays.
|
||||
3. `web`, `web-full`, `design`: also remove the 21st trio lines.
|
||||
4. New `lib/profiles/max.profile`: header `# DESC: Everything — full + the
|
||||
parked tools (make-pdf, diagram, 21st-ai/ui-explore/ui-review) +
|
||||
pr-review-toolkit; switch here when one of them is wanted` and the marker
|
||||
line `# SUPERSET-OF: full`. Content = new full + the 5 parked names in
|
||||
their original sections + `pr-review-toolkit plugin@claude-code-plugins`
|
||||
(the audit.profile line). NEVER a `GSTACK_REMOVED` name. Invariant: max ⊇
|
||||
union(every profile) − GSTACK_REMOVED.
|
||||
5. `lib/tests/profile-census.test.sh` (hermetic, `set -u`, `check` helper
|
||||
and `trap 'rm -rf "$WORK"' EXIT` like lib/tests/skill-routing-census.test.sh):
|
||||
- Top of file: `source "$ROOT/lib/gstack-removed.sh"` (REMOVED comes
|
||||
from the single source), `PARKED=(make-pdf diagram 21st-ai
|
||||
21st-ui-explore 21st-ui-review)`, and `FULL_EXCEPTIONS=(pr-review-toolkit)`
|
||||
with the reason on comment lines ABOVE the array (names only inside it:
|
||||
contract criterion 15 parses the parentheses).
|
||||
- ONE assertion function `census_check <profiles_dir>` that runs an
|
||||
inline python3 heredoc taking the dir and the three lists as argv.
|
||||
Entry = first whitespace token of a non-blank line whose first non-blank
|
||||
char is not `#` (what `read_profile` links). It prints ONE reason code
|
||||
per violation on stdout — `REMOVED_LISTED:<profile>:<name>`,
|
||||
`NO_SUPERSET` / `MANY_SUPERSETS`, `SUPERSET_GAP:<name>` (full or a
|
||||
parked/exception name missing from the `# SUPERSET-OF: full` profile),
|
||||
`MAX_GAP:<name>` (a name some profile carries that max lacks),
|
||||
`FULL_GAP:<name>` (a name a non-max profile carries that full lacks,
|
||||
minus REMOVED, the 21st trio and FULL_EXCEPTIONS) — and returns 0 iff
|
||||
no violation.
|
||||
- Live part: `census_check "$ROOT/lib/profiles"` must return 0
|
||||
(`check T1-live-clean`).
|
||||
- Fixture part under `mktemp -d`: `baseline/` = a minimal profiles dir
|
||||
(full.profile with `alpha`, `beta`; qa.profile with `alpha`;
|
||||
max.profile with the marker + `alpha`, `beta`, `parked-x`; PARKED and
|
||||
FULL_EXCEPTIONS overridden for the fixture through the argv lists).
|
||||
The baseline MUST return 0 → print `FIXTURE_BASELINE_OK`. Then three
|
||||
mutants, each a copy of baseline with ONE change, each MUST return
|
||||
non-zero AND print its own code: `ship` added to qa.profile →
|
||||
`REMOVED_LISTED:qa:ship` → `FIXTURE_REMOVED_DETECTED`; `beta` deleted
|
||||
from max.profile → `SUPERSET_GAP:beta` → `FIXTURE_SUPERSET_DETECTED`;
|
||||
`gamma` added to qa.profile only → `FULL_GAP:gamma` →
|
||||
`FIXTURE_FULLGAP_DETECTED`. A detected mutant counts as a PASS of the
|
||||
test (it is the positive control); a mutant that returns 0 is a FAIL.
|
||||
- Summary `PASS=n FAIL=m`, rc 1 on any FAIL. Shell helpers ≤ 25 logic
|
||||
lines; the python heredoc is one function per invariant.
|
||||
6. Docs: skills/profile/SKILL.md table gains the `max` row ("Everything —
|
||||
full + parked tools (make-pdf, diagram, 21st generation trio) +
|
||||
pr-review-toolkit") and full's row reads "Default — everything the other
|
||||
profiles carry, minus broken or doctrine-breaking gstack"; README line
|
||||
175 and 357-360 list `max`; README line 179 drops `context-save,
|
||||
context-restore` from its example list; USAGE line 166 lists `max`.
|
||||
|
||||
### E1b — denylist honored by every "gstack back" path
|
||||
Files: lib/profile.sh, lib/toggle-external.sh, lib/tests/gstack-removed.test.sh (new),
|
||||
lib/tests/{profile-default,profile-set-managed,toggle-external-repo-resolution}.test.sh
|
||||
(fixture copy line only).
|
||||
|
||||
1. lib/profile.sh: `source "$(dirname "${BASH_SOURCE[0]}")/gstack-removed.sh"`
|
||||
next to the other top-level definitions. `enable_all_gstack`: skip a
|
||||
parked entry whose name `gstack_is_removed` (leave it parked, `info
|
||||
"skipped (removed by policy, lib/gstack-removed.sh): $name"`).
|
||||
`enable_skill` gstack branch: refuse a removed name with `warn` and
|
||||
return 0 (a profile listing one is a census failure, not a crash).
|
||||
2. lib/toggle-external.sh `enable gstack` loop (the `for entry in
|
||||
"$DISABLED_DIR"/gstack__*` block): same skip, same source line
|
||||
(`source "$(dirname "$0")/gstack-removed.sh"`; note toggle-external
|
||||
resolves REPO from `$0`, keep that idiom).
|
||||
3. `enable_all_gstack` echoes the REAL restored count on stdout (skipped
|
||||
names excluded); `cmd_gstack on` prints that count, not the pre-computed
|
||||
`parked_gstack_count` (profile.sh ~651-655). toggle-external.sh: when
|
||||
the loop skipped ≥ 1 removed name and moved 0, print "only policy-removed
|
||||
skills remain parked (lib/gstack-removed.sh)" and NOT the "re-run gstack
|
||||
setup" hint (setup would relink the 9 removed skills).
|
||||
4. The three existing suites copy only profile.sh / toggle-external.sh into
|
||||
their fixture `lib/` and would die on the new `source`: add
|
||||
`cp "$ROOT/lib/gstack-removed.sh" "$FX/lib/"` (same for `$SANDBOX/repo/lib/`)
|
||||
in lib/tests/profile-default.test.sh (:27), lib/tests/profile-set-managed.test.sh
|
||||
(:22), lib/tests/toggle-external-repo-resolution.test.sh (:19). No other
|
||||
change to those suites.
|
||||
5. `lib/tests/gstack-removed.test.sh`: fixture repo under mktemp (the
|
||||
`PROFILE_REPO_OVERRIDE` / `TOGGLE_EXTERNAL_REPO_OVERRIDE` harness as
|
||||
lib/tests/profile-default.test.sh and toggle-external-repo-resolution.test.sh
|
||||
use it — read them first): `skills-disabled/gstack__ship` and
|
||||
`gstack__browse` present → `profile.sh gstack on` restores browse only,
|
||||
ship stays parked, output names the skip; same for `toggle-external.sh
|
||||
enable gstack`; `gstack_is_removed` positive + negative. `PASS=n FAIL=m`.
|
||||
|
||||
### E2 — wiring: shared gstack helper links + doctor catalog stats
|
||||
Files: lib/gstack-links.sh (new), link.sh, install-plugins.sh (STEP 2 helper
|
||||
block, STEP 5 comment blocks, summary lines — E2 owns EVERY edit of this
|
||||
file), update-all.sh (its helper-link block), lib/doctor-skills.sh (new),
|
||||
doctor.sh, lib/tests/gstack-links.test.sh (new), lib/tests/doctor-skills.test.sh (new).
|
||||
|
||||
1. `lib/gstack-links.sh` — `link_gstack_helpers <src> <dst>` (split into
|
||||
small helpers, each ≤ 25 logic lines, no `set -e`, fallback ok/warn/info
|
||||
like lib/vendor-skills.sh):
|
||||
a. Guard: if `$dst` is a symlink → `rm -f "$dst"` + info (gstack ./setup
|
||||
plants `~/.claude/skills/gstack -> skills-external/gstack` when the
|
||||
dir is absent; link.sh's old stale-link removal moves here). Then if
|
||||
`realpath -m "$dst"` is inside `realpath "$src"` → warn, return 1
|
||||
(never write into the submodule). `mkdir -p "$dst"`.
|
||||
b. For every top-level entry E of `$src`, skip by name `.git*`,
|
||||
`node_modules`, `SKILL.md`. If E is a dir holding its own `SKILL.md`
|
||||
(a gstack skill) → `mkdir -p "$dst/E"` and `ln -sfn` each child of E
|
||||
except `SKILL.md`. Else if E is a dir and `find -L "$src/E" -name
|
||||
SKILL.md -print -quit` is non-empty (browser-skills/, openclaw/ …) →
|
||||
skip with info (would expose a nested skill). Else (file, or a clean
|
||||
non-skill dir such as bin, scripts, lib, design, extension, ETHOS.md)
|
||||
→ `ln -sfn "$src/E" "$dst/E"`.
|
||||
c. Idempotent (`ln -sfn`), removes nothing but the stale dst symlink,
|
||||
echoes the number of links created THIS run on stdout (callers add it
|
||||
to CHANGED) and prints one `ok` summary on stderr. Rationale comment:
|
||||
the census above + why no SKILL.md is ever exposed under `<dst>`.
|
||||
2. link.sh: replace the stale-global-link removal AND the `bin` /
|
||||
`browse/dist` blocks (lines ~55-91) with `source "$REPO/lib/gstack-links.sh"`
|
||||
+ one call `n=$(link_gstack_helpers "$REPO/skills-external/gstack"
|
||||
"$CLAUDE/skills/gstack")` guarded by `[ -d "$REPO/skills-external/gstack" ]`,
|
||||
`CHANGED=$((CHANGED + n))`; keep the "submodule not found" warning; the
|
||||
comment says the helper tree replaces the hand-made links and exposes no
|
||||
SKILL.md.
|
||||
3. install-plugins.sh STEP 2, the duplicate `GSTACK_DST` block (lines
|
||||
~375-390): replace with the same source + call (after ./setup, so the
|
||||
lib's guard removes the global link setup may have planted).
|
||||
update-all.sh, its helper-link block (~106-116, `ln -sf` without `-n`
|
||||
→ would nest `src/bin/bin` on a re-run): same source + call, right after
|
||||
the submodule update. STEP 5 comment blocks + summary lines: E2 does
|
||||
them (moved from E3, see E3.2 text below — same wording).
|
||||
4. `lib/doctor-skills.sh` — `skill_catalog_stats <skills_dir>`: prints
|
||||
`<count> <desc_chars>` on stdout, ALWAYS exits 0 (prints `0 0` when the
|
||||
dir is absent or python fails). Implementation: one `python3 -` call that
|
||||
loads `lib/skill-routing-census.py` via `importlib.util.spec_from_file_location`
|
||||
(hyphenated name), globs `<dir>/*/SKILL.md` (glob follows symlinks),
|
||||
sums `len(extract_description(path) or '')` (the function takes a PATH
|
||||
and returns None when there is no description). On any python failure
|
||||
print `0 0` to stdout AND one `warn` line to stderr (doctor shows the
|
||||
failure instead of a healthy-looking zero). No awk parser.
|
||||
5. doctor.sh token block: replace the loop + `find` with
|
||||
`read -r SKILL_COUNT SKILL_DESC_CHARS < <(skill_catalog_stats "$HOME/.claude/skills")`
|
||||
after `source "$REPO/lib/doctor-skills.sh"`. Constants: DELETE the
|
||||
gstack (2750), context7 (200) and graphifyy (300) lines — their skills
|
||||
sit in `~/.claude/skills` and are counted by the stats (one comment line
|
||||
says so); superpowers 800 → 1500 (~900 t session-start injection + ~600 t
|
||||
of 15 descriptions, measured 2026-09-28); ui-ux-pro-max 400 → 670 (7
|
||||
descriptions, 2 669 chars, measured 2026-09-28). The "measured ~11.4k
|
||||
post-audit, LRN-088" note → "(re-measure after a catalog change;
|
||||
LRN-088)". NO synced-bucket line (dropped: out of the request's scope;
|
||||
noted as a TODO follow-up by the orchestrator).
|
||||
6. Tests. `lib/tests/gstack-links.test.sh`: fixture src under mktemp with
|
||||
`bin/x`, `ETHOS.md`, `SKILL.md`, `browse/{SKILL.md,dist/browse}`,
|
||||
`review/{SKILL.md,checklist.md,specialists/a.md}`, `.git/HEAD`,
|
||||
`other/deep/SKILL.md`, `node_modules/pkg/SKILL.md` → after
|
||||
`link_gstack_helpers`, `dst/bin`, `dst/ETHOS.md`, `dst/browse/dist`,
|
||||
`dst/review/checklist.md`, `dst/review/specialists` resolve;
|
||||
`find -L dst -name SKILL.md` is EMPTY (so no `dst/SKILL.md`,
|
||||
`dst/browse/SKILL.md`, `dst/other`, `dst/node_modules`), `dst/.git`
|
||||
absent; second run echoes 0 and changes nothing (idempotent); a dst
|
||||
that is a symlink to src → the symlink is removed, dst becomes a real
|
||||
dir, and `find src -type l` stays EMPTY (nothing written into src); a
|
||||
dst path inside src (`src/helpers`) → warn + rc 1, nothing created. `lib/tests/doctor-skills.test.sh`:
|
||||
fixture skills dir with an inline description, a `|` block scalar, a
|
||||
`>-` block, a SKILL.md with no description, a symlinked skill dir →
|
||||
count and chars equal the hand-computed sum; absent dir → `0 0` rc 0.
|
||||
`PASS=n FAIL=m` summaries.
|
||||
|
||||
### E3 — config + doctrine + docs
|
||||
Files: settings.json, agents/plugin-advisor.md, CLAUDE.global.md,
|
||||
skills/deploy/SKILL.md, CHANGELOG.md. (install-plugins.sh is E2's: the
|
||||
E3.2 wording below is what E2 writes there.)
|
||||
|
||||
1. settings.json: CREATE the top-level key `"env": {"ENABLE_STOP_REVIEW": "0"}`
|
||||
(it does not exist), keep the two enabledPlugins states already written
|
||||
live. Immediately validate: `python3 -c 'import json;json.load(open("settings.json"))'`.
|
||||
2. [DONE BY E2, wording kept here] install-plugins.sh STEP 5: after the ui-ux-pro-max block, a comment block
|
||||
"frontend-design@claude-plugins-official — NEVER installed: byte-identical
|
||||
to the skills-external copy Step 8b syncs from the example-skills cache;
|
||||
uninstalled 2026-09-28 (skill-catalog prune)" and "brightdata-plugin@synced
|
||||
— account-synced from claude.ai, kept `false` in settings.json: every skill
|
||||
needs a Bright Data account and its bright-data-mcp skill orders WebFetch/
|
||||
WebSearch replaced 'no exceptions' (would hijack /seo /geo /harden)".
|
||||
Summary line "security-guidance — PreToolUse security hook (0 tokens)" →
|
||||
"security-guidance — regex hints on Edit/Write + out-of-band LLM reviews
|
||||
on commit/push (Stop review off via ENABLE_STOP_REVIEW=0; quota, not
|
||||
context) [claude-code-plugins]". The frontend-design summary line stays
|
||||
(the managed copy stays).
|
||||
3. agents/plugin-advisor.md: the `security-guidance ↔ any` row → "Hooks +
|
||||
out-of-band LLM reviews (agentic review on commit/push; Stop diff review
|
||||
disabled by ENABLE_STOP_REVIEW=0). No context injection unless a regex
|
||||
hits."; the "> security-guidance and rtk are ALWAYS ON (0 tokens)" note →
|
||||
"> rtk is always on at 0 context tokens; security-guidance is always on
|
||||
and costs quota out of band (LLM reviews), not context — both omitted
|
||||
from the estimates".
|
||||
4. CLAUDE.global.md Skill routing: "- Ship / PR → ship (ship-feature if
|
||||
gstack off); deploy → deploy (runbook, the user runs it)" → "- Ship / PR →
|
||||
ship-feature (never gstack ship: it takes `origin/HEAD` = main as base
|
||||
and skips develop); deploy → deploy (runbook, the user runs it)". The
|
||||
gstack-OFF line lists "(investigate, qa, review, health, retro,
|
||||
office-hours…)". Design work "Review / audit" line: drop "+ 21st-ui-review"
|
||||
and add, at the end of the 21st sentence in that section, "21st-ai /
|
||||
ui-explore / ui-review are `max`-profile only." Keep every line ≤ 80 chars.
|
||||
5. skills/deploy/SKILL.md table: drop the `/land-and-deploy` and
|
||||
`/setup-deploy` rows; add one row "Merge a finished branch | `gitflow
|
||||
finish` on an explicit human signal (skills/gitflow)".
|
||||
6. CHANGELOG `[Unreleased]`: Removed (brightdata synced plugin disabled,
|
||||
frontend-design official plugin uninstalled, 9 gstack skills out of every
|
||||
profile with reasons, `GSTACK_REMOVED` denylist honored by `gstack on` /
|
||||
`enable gstack`), Changed (full = everything the other profiles carry,
|
||||
`max` = full + parked + pr-review-toolkit, 21st trio max-only,
|
||||
security-guidance Stop review off, doctor constants), Fixed (gstack helper
|
||||
tree: make-pdf, diagram, sections, jargon list, ETHOS, freeze hook now
|
||||
fires — `/unfreeze` clears `~/.gstack/freeze-dir.txt`; doctor.sh
|
||||
undercount; "0 tokens" claims; Ship/PR routing), Known residual (kept
|
||||
gstack skills still carry upstream prose routing to /ship,
|
||||
/land-and-deploy, /context-save, /autoplan, /design-shotgun; 21st-ui-build
|
||||
and 21st-cli-use point to the max-only trio — a Skill call on a parked
|
||||
name fails and the doctrine routing applies).
|
||||
|
||||
## Orchestrator steps after the executors
|
||||
- Criterion 16 runs `bash lib/profile.sh set full` live (parks the 14 names,
|
||||
re-applies full) and checks none of them resolves under `~/.claude/skills`.
|
||||
- Post-merge (user): `make link` (helper tree) and `bash lib/profile.sh set
|
||||
full` on any other machine.
|
||||
|
||||
## Edge cases
|
||||
- A profile line may carry a trailing label column (`personal`, `external`,
|
||||
`# gstack`); entries match on the first token only.
|
||||
- `design.profile` `# GATE-BLOCK:` lines name frontend-design, ui-ux-pro-max,
|
||||
emil-design-eng, design-html, design-motion-principles, design-review,
|
||||
design-consultation, the 21st CLI, 21st-ui-build: none of the removed or
|
||||
parked names → no gate edit (grep before editing).
|
||||
- link.sh / install-plugins.sh run on machines without the gstack
|
||||
submodule: the call is guarded by `[ -d skills-external/gstack ]`.
|
||||
- `skill_catalog_stats` never breaks doctor.sh's `set -euo pipefail`: it
|
||||
always exits 0 and always prints two integers.
|
||||
- The helper tree never contains a `SKILL.md` at any depth: skill dirs
|
||||
expose only their non-SKILL.md children, non-skill dirs with a nested
|
||||
SKILL.md (browser-skills/, node_modules/, openclaw/) are skipped
|
||||
(asserted by the gstack-links test and criterion 3).
|
||||
- `~/.claude/skills/gstack` may be a symlink to the submodule (planted by
|
||||
gstack ./setup on a fresh machine): the lib removes it before linking and
|
||||
never writes when dst resolves inside src.
|
||||
- Kept gstack skills still name removed/parked skills in their upstream
|
||||
prose: documented as a known residual (CHANGELOG), not patched (machine-
|
||||
owned submodule files).
|
||||
|
||||
## Tests
|
||||
- lib/tests/profile-census.test.sh, gstack-removed.test.sh,
|
||||
gstack-links.test.sh, doctor-skills.test.sh (new, hermetic; SUITES glob
|
||||
picks `lib/tests/*.test.sh` up automatically).
|
||||
- make test suite=lib/tests/doctrine-citers.test.sh (routing text changed);
|
||||
existing profile-default / profile-set-managed / toggle-external suites
|
||||
must stay green (profile.sh and toggle-external.sh changed).
|
||||
- shellcheck link.sh doctor.sh install-plugins.sh update-all.sh lib/*.sh lib/tests/*.test.sh.
|
||||
- Full `make test` by the orchestrator at the end (2 pre-existing T16a
|
||||
gitleaks failures are known).
|
||||
|
||||
## Disposition (RELATED MEMORY, read-before)
|
||||
- honors BDR-030 / BDR-101 — gstack via profiles, full default: every drop
|
||||
is a profile edit; the live tree follows through `set full`.
|
||||
- honors BDR-025 — GATE-BLOCK single source untouched.
|
||||
- honors BDR-093 — 21st pack stays installed; its generation/review trio
|
||||
leaves the four design-bearing profiles and lives in max; BDR amendment
|
||||
noted in registries.
|
||||
- honors BDR-023 — close alias untouched.
|
||||
- honors BDR-080 — investigate stays explicit-only and stays in full/dev/
|
||||
backend (user rule).
|
||||
- honors BDR-095 — static deny beats `ask`: careful/guard removal loses no
|
||||
live protection (their hooks never fired).
|
||||
- honors LRN-088 — measured before cutting: the gain is routing quality and
|
||||
no broken 100 KB body invoked, not listing chars.
|
||||
- honors LRN-022 / BLK-005 — profiles audited with the skill change (census).
|
||||
- honors LRN-096 — vacuous guard class: helper tree makes the freeze hook
|
||||
real; careful/guard are removed rather than left vacuous.
|
||||
- honors BDR-070 — no rival SEO tooling: brightdata seo-audit stays off.
|
||||
- does NOT touch BDR-104 (MengTo pack) nor the 2026-07-05 frontend-design +
|
||||
impeccable "both" decision (the managed copy stays).
|
||||
Reference in New Issue
Block a user