Compare commits
8
Commits
3fcc0c8211
...
902bc76a2f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
902bc76a2f | ||
|
|
54a93eabe2 | ||
|
|
bb46ee22eb | ||
|
|
c7e8d8191d | ||
|
|
7d8407ec36 | ||
|
|
cd3a745857 | ||
|
|
afa89f9cd9 | ||
|
|
c859ae256f |
@@ -43,6 +43,7 @@ rules:
|
||||
| BLK-021 | 2026-09-22 | Bash tool dead mid-session ("every command exits 1"): /tmp usrquota blown by a dead session's probe HOMEs — 2… | open |
|
||||
| BLK-022 | 2026-09-22 | `hooks/guard-bash.sh` withheld by the safety classifier; executable spec shipped instead — 2026-09-22 | open |
|
||||
| BLK-023 | 2026-09-28 | floor-guard SKIP pattern `xit(` (Jasmine) matches any `exit(` in python/JS test helpers → false ECARTS; workaround: no `exit(` in inline python, bash derives rc from output — 2026-09-28 | resolved |
|
||||
| BLK-024 | 2026-09-29 | update-all.sh re-fetched vendored skills but never re-applied the effort pins (lost until next `make plugin`); my first fix placed the re-apply BEFORE the late 21st refresh — rtk-truncated grep read as complete — 2026-09-29 | resolved |
|
||||
|
||||
---
|
||||
|
||||
@@ -268,3 +269,9 @@ rules:
|
||||
- **Real cause**: `lib/floor-guard.sh` SKIP_SUBSTRINGS holds the bare fragment `'xit('` to catch Jasmine's `xit(…)`; `skip_kind()` is a plain substring match, so `sys.exit(`, `SystemExit(`, `process.exit(` all hit.
|
||||
- **Solution**: workaround applied — the inline python prints violations only, the bash wrapper derives the return code from the captured output (no `exit(` anywhere). Root fix pending: word-bound the pattern (`(^|[^a-zA-Z_.])xit\(`) or match `xit(` only in JS/TS test files; hotfix-sized.
|
||||
- **Status**: resolved 2026-09-28 — hotfix 0deb559 (bugfix/floor-guard-xit-boundary): the four bare Jasmine identifiers moved into `SKIP_IDENT_RE` with lookbehind `(?<![A-Za-z0-9_.])`, dotted/decorator forms stay substrings; fixtures SKIP_EXIT_CLEAN (RED before, GREEN after) + xit/fit/fdescribe flags. Residual `shortcut:` in the guard: `def fit(` / `function xit(` still match, `xit (` / `xit.each(` still do not (as before). Links [[BDR-105]], [[BDR-102]] (floor-guard origin), [[EVAL-034]].
|
||||
|
||||
## BLK-024 — resync dropped the vendored effort pins, twice — 2026-09-29
|
||||
- **Friction**: [[BDR-107]] re-applied brainstorming/writing-plans xhigh only in install-plugins.sh STEP 8e; update-all.sh §7.3 re-fetches at the same commit → SKILL.md overwritten, `effort:` gone until the next `make plugin`. Latent since 2026-09-28.
|
||||
- **Real cause (second instance)**: my re-apply call landed after the superpowers refresh; update-all.sh §7.4 (21st pack) runs LATER and `rm -rf` + `mv` every 21st-* SKILL.md. My grep of update-all.sh was truncated by rtk ("+28 more hidden") and I read the partial listing as the whole file. Fresh verifier caught it (ECARTS).
|
||||
- **Solution**: `lib/effort-pins.txt` + `lib/effort-pins.sh` called ONCE after the LAST vendoring step of both scripts; census locks the order by line number (`ln_last`). Rule: a truncated tool listing is not a census; re-run without the pager or grep the anchor directly.
|
||||
- **Status**: resolved 2026-09-29 (feature/effort-round, [[BDR-108]]).
|
||||
|
||||
@@ -129,6 +129,7 @@ rules:
|
||||
| BDR-105 | 2026-09-28 | skill-catalog prune: 9 gstack out via GSTACK_REMOVED, full ⊇ every profile, max = everything, brightdata + frontend-design plugin off, security-guidance Stop review off, design gate asks `21st login` and waits | accepted |
|
||||
| BDR-106 | 2026-09-28 | superpowers: 7 wired skills vendored at v6.4.1 via lib/vendor-skills.sh (always_on lock class), plugin + marketplace dropped, citers by bare name, doctrine map for the 4 non-vendored refs | accepted |
|
||||
| BDR-107 | 2026-09-28 | Effort tiering: session high, effort pins on 20 agents (BDR-077 second axis), entry level on 30 skills, five paired shifter skills, max at loop caps + ship-feature 4b | accepted |
|
||||
| BDR-108 | 2026-09-29 | Effort round: level on every skill next to its model pin (3 repo + 25 vendored via `lib/effort-pins.txt` re-applied after the LAST vendoring step of install + resync), design stack ONE level (high), model pins stay tier aliases: quality/price trade-off = tier × effort, never version | accepted |
|
||||
|
||||
---
|
||||
|
||||
@@ -1340,3 +1341,10 @@ Branch feature/user-writing-web-rules, UNMERGED (human gate).
|
||||
- **Caveats**: shifts inert headless; a prose gate ending the turn resets to session level (re-assert wired in bugfix and ship-feature 4b); mode-based agents pin their judgment mode; a shift paired with a built-in judgment dispatch would downgrade it (pair with Read/Bash instead); `lib/gitflow-test.sh` T16a red on this machine = gitleaks not installed, unrelated.
|
||||
- **Refs**: spec `docs/superpowers/specs/2026-09-28-effort-tiering-design.md`, plan `docs/superpowers/plans/2026-09-28-effort-tiering.md`, [[LRN-179]], [[EVAL-035]], [[EVAL-036]], [[BDR-077]].
|
||||
- **Correction (2026-09-28)**: EVAL-035 counted one record per content block (~2.8× on request counts); deduped figures in [[EVAL-037]]: main-loop thinking 99.9% of total thinking (was 96.6%), thinking 5.6% of weighted cost (was 8.4%), sonnet think/request 26→0.2 tok. Conclusions hold, sharper: main loop still carries almost all thinking, executors stay cheap.
|
||||
|
||||
## BDR-108 — Effort round: every skill carries a level next to its model pin; model pins stay tier aliases [accepted] (2026-09-29)
|
||||
- **Decision**: 3 repo skills pinned (skills-perso low, pdf-translate medium, site-motion high). 25 vendored externals (superpowers 7, agent-skills 3, design stack 9, 21st pack 6) get level from `lib/effort-pins.txt`, applied by `lib/effort-pins.sh` after LAST vendoring step of install-plugins.sh (21st pack, STEP 8.7) AND update-all.sh (§7.4). Design stack = ONE level, high. hotfix stays high. Model pins stay aliases (`sonnet` `opus` `haiku` `fable`). Doctrine: design stack loads paired with first Read; census order-locked by line number; `lib/effort-audit.py` prints thinking coverage.
|
||||
- **Why**: user rungs (low fix-a-line · medium day-to-day · high refactor/resisting bug · xhigh architecture/audit · max stuck). Latest version of each tier = cheapest or same price (Sonnet 5.5 = Sonnet 5, Opus 5.5 < Opus 5, Haiku 4.5 alone, Fable 5.1 = Fable 5) → no version arbitration, only tier × effort. Stacked skills: last loaded wins → two levels in a stack = effort depends on load order. Aliases track generation free (transcripts: `sonnet` → sonnet-5 then sonnet-5-5).
|
||||
- **Alternatives rejected**: full model IDs in frontmatter (maintenance, Agent-tool call site enum cannot pin a version, older gen never cheaper); hotfix → medium (no A/B on a reflection skill yet, [[EVAL-036]]); design stack medium; untrack design-motion-principles (only tracked external, gated in contract instead); pins on gstack / impeccable / graphify / find-docs / darwin (machine-owned, [[BDR-107]]).
|
||||
- **Gates**: GATE 0 MET; verifier ECARTS(3): resync re-apply sat BEFORE the 21st refresh (real, fixed by fresh executor), tracked file out of scope (gated), shellcheck directive unauthorized (clarified) → CONFORME 7/7; security PASS + 4 LOW hardened (criteria 8-9); `make test` 44 suites rc 0.
|
||||
- **Refs**: contract `.claude/tasks/contracts/2026-09-29-effort-round-1315.md`, [[BDR-107]], [[BDR-077]], [[LRN-181]], [[LRN-182]], [[BLK-024]], [[EVAL-038]].
|
||||
|
||||
@@ -58,6 +58,7 @@ rules:
|
||||
| EVAL-035 | 2026-09-28 | thinking-share measurement, 6 days of transcripts (10,955 requests): thinking = 8 % of weighted spend, 97 % of it in the main loop; sonnet subagents at xhigh think 26 tok/request; cache reads = 53 % | pins = explicitness not savings; main-loop effort + context size are the levers; A/B after rollout |
|
||||
| EVAL-036 | 2026-09-28 | A/B `/reconcile` headless, session high vs skill entry low: requests 18→15, output 12374→9038 (−27 %), thinking 3135→2248 (−28 %), time 96.5→78.4 s (−19 %), n=1 | keep low on bookkeeping skills; repeat on a reflection skill before touching the medium/high split |
|
||||
| EVAL-037 | 2026-09-28 | correction of EVAL-035/036 counts: transcript records are per content block; deduped by message.id → main-loop thinking share 99.9%, thinking share of weighted cost 5.6%, sonnet think/msg 26→0.2, A/B requests 9→8 | conclusions hold (sharper: main-loop thinking 96.6%→99.9%, weighted-cost thinking corrected 8.4%→5.6%); effort-audit.py dedupes from a3b479e+ |
|
||||
| EVAL-038 | 2026-09-29 | correction of EVAL-037: 94 % of sub-agent usage records carry no `output_tokens_details` (Fable subs at xhigh read 0 thinking, impossible with always-on thinking) → sub-agent thinking UNMEASURED, not ≈0; main loop 100 % counted; weighted-cost split (61/39) still holds | `effort-audit.py` prints coverage + CAVEAT; cite the cost split only; agent effort pins stay unmeasured; a tier move on a price argument = judgment, not figure |
|
||||
|
||||
---
|
||||
|
||||
@@ -354,3 +355,10 @@ Dogfood: 3 blind lenses attacked the v1 plan for the plan-challenge feature itse
|
||||
- **Result (deduped)**: main weighted-cost 61.4 %, thinking share 99.9 % (was 96.6 %); sub weighted-cost 38.6 %, thinking share 0.1 %; thinking = 5.6 % of weighted cost (was 8.4 %, inflated by duplicate counting); sonnet think/request 26→0.2 tok (sub, xhigh); A/B `/reconcile` (EVAL-036 rerun, deduped) requests 9→8, output 6129→4706, thinking 1550→1104 — the raw undeduped counts on the same transcripts are 18→15, matching EVAL-036 exactly (the bug, not the finding).
|
||||
- **Anomaly**: the main-loop-carries-almost-all-thinking split got SHARPER after dedup (96.6→99.9 %), not weaker — duplication was near-uniform across content blocks, so ratios among scopes barely moved; only the absolute request/token counts and the overall thinking-share-of-cost figure were inflated (~2.2-2.8× depending on transcript mix).
|
||||
- **Action**: `lib/effort-audit.py` dedupes by `message.id` from this commit; cite EVAL-037, not EVAL-035, for the split.
|
||||
|
||||
## EVAL-038 — correction of EVAL-037: sub-agent thinking is unmeasured, not ≈0
|
||||
- **Date**: 2026-09-29
|
||||
- **Output checked**: [[EVAL-037]] "sonnet think/request 26→0.2 tok, executors stay cheap; main loop carries 99.9 % of thinking".
|
||||
- **Method**: scan of the last 400 transcripts, dedup by message.id, count records with/without `output_tokens_details`: sub 4586 requests, 6 % carry the field (2896/3075 sonnet-5 without, 65/72 fable-5-1 without); main 100 % carry it. A Fable 5.1 sub-agent at xhigh with 0 thinking tokens is impossible (thinking always on) → recording gap, not behaviour.
|
||||
- **Anomaly**: "main loop = 99.9 % of thinking" is a coverage artefact. The weighted-cost split (main 61 % / sub 39 %) holds: `output_tokens` is always present.
|
||||
- **Action**: `lib/effort-audit.py` counts `nodet`, prints `%counted` per row, "thinking counted on N% of them" per scope and a CAVEAT under 50 %; cite the cost split only; the 20 agent effort pins ([[BDR-107]]) remain unmeasured; a tier move argued on price stays a judgment ([[BDR-108]]).
|
||||
|
||||
@@ -545,3 +545,7 @@ rules:
|
||||
- effort tiering built on feature/effort-tiering (BDR-107): session high, 20 agent pins, 28+2 skill entry levels, 5 paired shifters, max at caps + 4b, census 129+ locks green, A/B −27 % output on /reconcile; finish awaits human signal.
|
||||
- User go "ok merge le": feature/effort-tiering merged into develop (94ede35) via gitflow finish, spec + plan purged (BDR-065), branch removed local + origin; leftovers for the user: .claude/skills/effort-probe-* and .superpowers/sdd/ scratch (deletes refused), gitleaks install (T16a), statusline visual check.
|
||||
- From dotfiles repo (config): commit blocked, pre-commit ran `gitleaks git --staged`, Ubuntu apt gitleaks 8.16 has no `git` subcmd → exit 1 read as leak, every commit blocked. bugfix/gitleaks-protect-fallback 347073a: generator probes `gitleaks git --help`, falls back `protect --staged`; hooks regenerated; T16c symlink farm /usr/bin minus gitleaks (short PATH no longer hid an apt binary). make test rc 0, 170/0. User go "merge les deux": merged into develop 55b77e3 via gitflow finish, branch removed local + origin. Learning captured in config repo LRN-013.
|
||||
|
||||
## 2026-09-29
|
||||
- Effort round on feature/effort-round ([[BDR-108]]): user table re-applied to all 88 linked skills; 30 existing levels hold, 3 repo skills pinned, 25 vendored externals pinned from `lib/effort-pins.txt` via `lib/effort-pins.sh` after the LAST vendoring step of install + resync (resync had dropped the BDR-107 pins, [[BLK-024]]); design stack ONE level high ([[LRN-181]]); model pins stay aliases, trade-off = tier × effort ([[LRN-182]]). Verifier ECARTS(3) caught my re-apply placed before the late 21st refresh (rtk-truncated grep read as complete) → fresh executor, CONFORME 7/7 then 9/9 after the 4-LOW hardening; security PASS ×2; `make test` 44 suites rc 0. Sub-agent thinking found unmeasured, not ≈0 ([[EVAL-038]]). 5 residual LOW parked in TODO. UNMERGED — human gate.
|
||||
|
||||
|
||||
@@ -200,6 +200,8 @@ rules:
|
||||
| LRN-178 | 2026-09-28 | a top-level `source` added to a lib breaks every hermetic suite that copies that lib alone into a fixture; grep the `cp` lists before adding one, or source lazily inside the branch that needs it | adding `source` to profile.sh / toggle-external.sh / any lib the suites copy |
|
||||
| LRN-179 | 2026-09-28 | Skill `effort:` frontmatter shifts the MAIN LOOP for the rest of the turn on user slash invocation AND on interactive Skill-tool loads (last loaded wins, both directions, prompt cache kept); NOT applied in `-p`/headless; agent pins always honoured, unpinned agents inherit session | effort tiering; any skill or agent that must think more or less than the session |
|
||||
| LRN-180 | 2026-09-28 | Skill-tool effort override needs a paired tool call: a lone Skill(effort-*) call is a no-op; a load in the same message as another tool call applies (the paired call already sees it); re-load re-applies (text deduped); skills Claude loads alone (brainstorming, writing-plans) apply nothing | every orchestrator shift; amends LRN-179 |
|
||||
| LRN-181 | 2026-09-29 | Stacked skills share ONE effort level: skill `effort:` = last loaded wins, so a stack loaded in one build (design toolchain) with two levels gets an effort that depends on load order; a skill Claude loads alone applies nothing (LRN-180) | one level per stack in `lib/effort-pins.txt`; load the stack paired with the first Read; copy the stack level when vendoring a new design skill |
|
||||
| LRN-182 | 2026-09-29 | Effort/thinking baselines are generation-bound and aliases move silently: `sonnet` resolved sonnet-5 then sonnet-5-5 mid-period, Sonnet 5.5 recalibrated its effort levels; EVAL-036 measured one generation | re-run `lib/effort-audit.py` after an alias moves; cite the generation in any effort measurement; never pin a version for it (older gen never cheaper) |
|
||||
|
||||
---
|
||||
|
||||
@@ -1650,3 +1652,11 @@ Rule: when editing a doctrine file under structure locks, grep the test's lock s
|
||||
## LRN-180 — Skill-tool effort override needs a paired tool call; a lone Skill call is a no-op (2.1.283)
|
||||
- **Context**: effort-tiering smoke. Six lone `Skill(effort-*)` / probe loads left `$CLAUDE_EFFORT` unchanged; every load issued in the same assistant message as another tool call applied, and the paired Bash already saw the new level. Re-loading an already-loaded shifter re-applies (text deduped: "already loaded above"). Final review: `brainstorming` / `writing-plans` loaded alone by ship-feature and init-project → their vendored xhigh pin inert. Amends [[LRN-179]].
|
||||
- **Apply**: `Skill(effort-<level>)` always travels with the step's first tool call, shift first; pair a downward shift with a pinned executor or a Read/Bash, never with a built-in judgment dispatch; before any built-in judgment dispatch, pair the own-level shift with it; skills Claude loads alone do not apply their pin → re-assert with a paired shift at the resumed planning step ([[BDR-107]]).
|
||||
|
||||
## LRN-181 — Stacked skills share one effort level; a lone load applies none
|
||||
- **Context**: design toolchain loads 5-8 skills in one build. Skill `effort:` frontmatter = last loaded wins, both directions ([[LRN-179]]). Two levels inside the stack → effort depends on load order, invisible. Plus [[LRN-180]]: a Skill call Claude issues alone is a no-op.
|
||||
- **Apply**: one level per stack (`lib/effort-pins.txt` design section, census `stack_levels` lock, site-motion frontmatter matches); doctrine "load the stack paired with the first Read of the target file"; new vendored design skill → copy the stack level. [[BDR-108]]
|
||||
|
||||
## LRN-182 — Effort baselines are generation-bound; model aliases move silently
|
||||
- **Context**: transcripts of the last weeks show `sonnet` → claude-sonnet-5 (3069 msgs) then claude-sonnet-5-5 (recent), `opus` → opus-5 then opus-5-5, `fable` → fable-5 then fable-5-1. API reference: Sonnet 5.5 recalibrated effort levels ("start at medium for agentic coding"). [[EVAL-036]] A/B ran on one generation.
|
||||
- **Apply**: after an alias moves (new model in a tier) re-run `python3 lib/effort-audit.py` and re-read the pins; write the generation next to any effort figure; keep aliases (latest = cheapest or same price, never pin a version for a measurement). [[BDR-108]]
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# TODO
|
||||
|
||||
## 2026-09-29 — effort round: every skill carries a level next to its model pin (feature/effort-round)
|
||||
User table: low fix-a-line/run-a-script · medium day-to-day · high refactor/resisting bug ·
|
||||
xhigh architecture/audit before validation · max stuck. Approved 2026-09-29: design stack
|
||||
high uniform, hotfix stays high, all vendored externals of the table, docs in the same branch.
|
||||
Model pins stay aliases (latest of each tier is also the cheapest or same price); the
|
||||
quality/price trade-off is tier × effort, never version.
|
||||
- [x] S1 `lib/effort-pins.txt` (map) + `lib/effort-pins.sh` (idempotent re-apply) replacing the
|
||||
hardcoded brainstorming/writing-plans loop; called after the last vendoring step of
|
||||
install-plugins.sh AND update-all.sh (resync dropped the pins until the next make plugin)
|
||||
- [x] S2 repo skills: skills-perso low, pdf-translate medium, site-motion high
|
||||
- [x] S3 tests: `lib/tests/effort-pins.test.sh` (fixture: insert, keep, replace, skip, reject)
|
||||
+ effort-routing census map-driven + design-stack uniformity lock
|
||||
- [x] S4 `lib/effort-audit.py`: count records without output_tokens_details, print coverage
|
||||
(sub-agent thinking was read as 0 on ~90 % of records: a gap, not a finding)
|
||||
- [x] S5 doctrine: Design work paired load + one level per stack (CLAUDE.global.md, lib/effort-shift.md)
|
||||
- [x] S6 docs: README effort section, USAGE niveau d'effort, CHANGELOG
|
||||
- [x] S7 contract + GATE 0 + fresh verifier + security gate, make test, shellcheck — GATE 0 MET, verifier ECARTS(3) → executor moved the resync re-apply after the 21st refresh (real gap), scope gated, directive authorized → CONFORME 7/7; security PASS (4 LOW on the helper, see journal); make test 44 suites rc 0
|
||||
- [x] S8 registries BDR-108, LRN-181, LRN-182, BLK-024, EVAL-038 (user go) + journal
|
||||
- [x] S9 hardening of lib/effort-pins.sh (4 LOW, user go): fresh executor, T11-T14, verifier CONFORME 9/9, security PASS
|
||||
- [ ] parked LOW (security re-gate 2026-09-29, none exploitable): no RETURN trap on the mktemp sibling (SIGINT during awk leaves `SKILL.md.XXXXXX`); T13 never reaches the post-write re-read branch (CRLF opener fails `_effort_pin_closed` first, fixture with LF delimiters + CRLF `name:` line would); T14 fails under root (chmod ignored); `WORK="$(mktemp -d)"` unguarded in the suite (`|| exit 1`); install-plugins.sh `err()` uses `echo -e` on the rejected map line
|
||||
- UNMERGED — human gate ("merge it")
|
||||
|
||||
## 2026-09-28 — effort tiering: session high, agent pins, skill levels, phase shifts (feature/effort-tiering)
|
||||
Spec `docs/superpowers/specs/2026-09-28-effort-tiering-design.md`, plan
|
||||
`docs/superpowers/plans/2026-09-28-effort-tiering.md`. Approved 2026-09-28: session
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# CONTRACT — effort-round
|
||||
- date: 2026-09-29 | flow: feat by hand (feature/* off develop) | branch: feature/effort-round
|
||||
- status: active
|
||||
|
||||
## REQUEST (verbatim — IMMUTABLE)
|
||||
> en se basant sur le meme tableau que la derniere fois [low: corriger une ligne, renommer un fichier, lancer un script · medium: le travail courant · high: un refactor, un bug qui resiste · xhigh: architecture, audit avant validation · max: quand une erreur coince, une erreur ne se rattrape pas, ou qu'on juge avoir besoin de beaucoup de reflexion], quand on a pin les orchestrateurs et leur sous agent a des efforts, j'aimerais que tu fasse une ronde de tout les skill et que tu mete un niveau d'effort en plus du model pin. D'ailleurs les model pin, c'est du par exemple Sonnet ou du Sonnet 5.5 (version du model pinned) ? Car il faudrait utiliser les versions qui vont bien avec la tache qu'ils ont a acomplir.
|
||||
> [answered: model pins stay tier aliases; the latest version of a tier is also the cheapest or same-priced, the quality/price trade-off is tier × effort]
|
||||
|
||||
## CLARIFICATIONS
|
||||
- User choices 2026-09-29 (AskUserQuestion): design stack high uniform; hotfix stays high; every vendored external of the proposed table gets a pin; README/USAGE docs in the same branch.
|
||||
- Round result: 30 existing entry levels hold against the table; 3 repo skills had none (skills-perso low, pdf-translate medium, site-motion high); vendored externals get theirs from `lib/effort-pins.txt` re-applied by `lib/effort-pins.sh`; impeccable, graphify, find-docs, gstack, darwin-skill and the five shifters stay unpinned (machine-owned, BDR-107).
|
||||
- Defect found in passing, fixed here: `update-all.sh` re-fetched the vendored skills but never re-applied the pins (lost until the next `make plugin`).
|
||||
- Defect found in passing, surfaced not fixed: ~94 % of sub-agent usage records carry no `output_tokens_details`, so `lib/effort-audit.py` read zero thinking on sub-agents; the script now prints coverage and a CAVEAT; EVAL-037's "executors stay cheap" is a measurement gap (registry correction pending user approval).
|
||||
- lib/tests/effort-routing.test.sh line 4 widens its shellcheck directive from SC2015 to SC2015,SC2016: the new `has … '$REPO'` locks are literal source text, the `$REPO` must NOT expand (authorized; a test file, informational). [verifier 2026-09-29 gap 3]
|
||||
- Hardening round (criteria 8-9) added after the security gate on user go; the fixture suite may `chmod` its own mktemp directory (555 then back to 755 for the trap cleanup), never `-R`, never outside the fixture.
|
||||
- Frontmatter placement of the inserted `effort:` line (after `name:`, else before the closing `---`) has no harness effect; locked by the fixture suite only.
|
||||
|
||||
## ACCEPTANCE CRITERIA
|
||||
1. Map + helper: `lib/effort-pins.sh` inserts, keeps, replaces (frontmatter only), skips a missing skill, is idempotent, rejects a bad level / traversal name / three-field line before writing, parses the real map.
|
||||
CHECK: out=$(make test suite=lib/tests/effort-pins.test.sh 2>&1); echo "$out" | grep -q 'effort-pins: [0-9]* pass, 0 fail' || { echo "$out" | grep FAIL; exit 1; }; echo PINS_GREEN
|
||||
EXPECT: PINS_GREEN
|
||||
EVIDENCE: MET exit=0 marker-found :: PINS_GREEN
|
||||
2. Census: the effort-routing suite is green and locks the three new repo levels, the map-driven vendored check, the design-stack single level, both re-apply call sites and the doctrine pointer.
|
||||
CHECK: out=$(make test suite=lib/tests/effort-routing.test.sh 2>&1); echo "$out" | grep -q 'census: [0-9]* pass, 0 fail' || { echo "$out" | grep FAIL; exit 1; }; for k in skills-perso pdf-translate site-motion effort-pins.txt 'stack_levels' 'apply_effort_pins'; do grep -q "$k" lib/tests/effort-routing.test.sh || { echo "census lacks $k"; exit 1; }; done; echo CENSUS_GREEN
|
||||
EXPECT: CENSUS_GREEN
|
||||
EVIDENCE: MET exit=0 marker-found :: CENSUS_GREEN
|
||||
3. Re-apply wired after the LAST vendoring step of both scripts, hardcoded loop gone: in install-plugins.sh the call follows the 21st pack staging block; in update-all.sh it follows the 21st pack refresh (§7.4, the last step that rewrites a SKILL.md), which itself follows the superpowers refresh. [verifier 2026-09-29: the first placement sat after the superpowers refresh only, the 21st refresh ran later and dropped seven pins]
|
||||
CHECK: a=$(grep -n 'apply_effort_pins "$REPO"' install-plugins.sh | cut -d: -f1); b=$(grep -n 'rm -rf "$TFD_STAGE"' install-plugins.sh | tail -1 | cut -d: -f1); c=$(grep -n 'apply_effort_pins "$REPO"' update-all.sh | cut -d: -f1); d=$(grep -n 'skills-external/$_tfd_name' update-all.sh | tail -1 | cut -d: -f1); e=$(grep -n 'vendor_pinned_skills superpowers refresh' update-all.sh | cut -d: -f1); [ "$(echo "$a" | wc -l)" -eq 1 ] && [ "$a" -gt "$b" ] && [ "$(echo "$c" | wc -l)" -eq 1 ] && [ -n "$d" ] && [ "$c" -gt "$d" ] && [ "$c" -gt "$e" ] && ! grep -q 'for _s in brainstorming writing-plans' install-plugins.sh && bash -n install-plugins.sh && bash -n update-all.sh && echo RESYNC_OK
|
||||
EXPECT: RESYNC_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: RESYNC_OK
|
||||
4. Live tree: every map entry whose skill is vendored on this machine carries that level in its frontmatter (idempotent re-run applies 0).
|
||||
CHECK: out=$(bash lib/effort-pins.sh 2>&1) && echo "$out" | grep -q ' 0 applied, [0-9]* already at level' && echo LIVE_AT_LEVEL
|
||||
EXPECT: LIVE_AT_LEVEL
|
||||
EVIDENCE: MET exit=0 marker-found :: LIVE_AT_LEVEL
|
||||
5. Audit script: compiles, runs on a fixture with two records lacking `output_tokens_details` and one carrying it, reports 33 % coverage for that scope and the CAVEAT line (below 50 %).
|
||||
CHECK: python3 -m py_compile lib/effort-audit.py && D=$(mktemp -d) && mkdir -p "$D/p" && printf '%s\n%s\n%s\n' '{"type":"assistant","message":{"id":"m1","model":"claude-sonnet-5-5","usage":{"input_tokens":1,"output_tokens":10}}}' '{"type":"assistant","message":{"id":"m2","model":"claude-sonnet-5-5","usage":{"input_tokens":1,"output_tokens":10}}}' '{"type":"assistant","message":{"id":"m3","model":"claude-sonnet-5-5","usage":{"input_tokens":1,"output_tokens":10,"output_tokens_details":{"thinking_tokens":4}}}}' > "$D/p/s.jsonl" && out=$(python3 lib/effort-audit.py "$D") && echo "$out" | grep -q 'thinking counted on 33% of them' && echo "$out" | grep -q 'CAVEAT: main' && echo AUDIT_OK
|
||||
EXPECT: AUDIT_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: AUDIT_OK
|
||||
6. Doctrine + docs: CLAUDE.global.md ≤ 320 lines with the paired-load line; README "## Effort routing"; USAGE "### Niveau d'effort"; CHANGELOG Added + Fixed entries; doctrine-citers census green.
|
||||
CHECK: [ "$(wc -l < CLAUDE.global.md)" -le 320 ] && grep -q 'a lone Skill call applies no effort' CLAUDE.global.md && grep -q '^## Effort routing' README.md && grep -q "^### Niveau d'effort" USAGE.md && grep -q 'Effort round (BDR-108)' CHANGELOG.md && grep -q 'never re-applied the effort pins' CHANGELOG.md && make test suite=lib/tests/doctrine-citers.test.sh 2>&1 | grep -q 'FAIL=0' && echo DOCS_OK
|
||||
EXPECT: DOCS_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: DOCS_OK
|
||||
7. Health stack: shellcheck clean on the touched shell files and the Health Stack set; no-vacuous-locks green.
|
||||
CHECK: shellcheck lib/effort-pins.sh lib/tests/effort-pins.test.sh lib/tests/effort-routing.test.sh install-plugins.sh update-all.sh *.sh hooks/*.sh lib/*.sh && make test suite=lib/tests/no-vacuous-locks.test.sh >/dev/null 2>&1 && echo LINT_OK
|
||||
EXPECT: LINT_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: LINT_OK
|
||||
|
||||
8. Hardening (security gate 2026-09-29, 4 LOW, user go): (a) a map whose last line has no trailing newline still applies that line; (b) a SKILL.md whose frontmatter has no closing `---` is skipped with an err line, file byte-identical; (c) a CRLF SKILL.md (`---\r`) is never counted as applied: the helper re-reads the level after the write and reports a mismatch as err, counted as failed (rc 1); (d) a write failure (read-only skill directory) is reported as err, counted as failed, and leaves no temporary file behind. Cases T11-T14 in lib/tests/effort-pins.test.sh, header comment of the helper updated.
|
||||
CHECK: out=$(make test suite=lib/tests/effort-pins.test.sh 2>&1); echo "$out" | grep -q 'effort-pins: [0-9]* pass, 0 fail' || { echo "$out" | grep FAIL; exit 1; }; for k in T11-last-line-no-newline T12-unterminated-frontmatter-skipped T13-crlf-not-counted-applied T14-write-failure-no-temp; do echo "$out" | grep -q "PASS $k" || { echo "missing PASS $k"; exit 1; }; done; echo HARDEN_GREEN
|
||||
EXPECT: HARDEN_GREEN
|
||||
EVIDENCE: MET exit=0 marker-found :: HARDEN_GREEN
|
||||
9. Hardening keeps everything else green: shellcheck clean on the helper and its suite, effort-routing census green, live tree still idempotent (0 applied).
|
||||
CHECK: shellcheck lib/effort-pins.sh lib/tests/effort-pins.test.sh && make test suite=lib/tests/effort-routing.test.sh 2>&1 | grep -q 'census: [0-9]* pass, 0 fail' && bash lib/effort-pins.sh 2>&1 | grep -q ' 0 applied, [0-9]* already at level' && echo HARDEN_STABLE
|
||||
EXPECT: HARDEN_STABLE
|
||||
EVIDENCE: MET exit=0 marker-found :: HARDEN_STABLE
|
||||
|
||||
## FILE SCOPE
|
||||
- lib/effort-pins.txt, lib/effort-pins.sh (new); lib/tests/effort-pins.test.sh (new); lib/tests/effort-routing.test.sh
|
||||
- install-plugins.sh, update-all.sh (re-apply call), lib/effort-audit.py (coverage)
|
||||
- skills/skills-perso/SKILL.md, skills/pdf-translate/SKILL.md, skills/site-motion/SKILL.md (effort line)
|
||||
- lib/effort-shift.md, CLAUDE.global.md (doctrine), README.md, USAGE.md, CHANGELOG.md
|
||||
- .claude/tasks/TODO.md, .claude/tasks/contracts/ (this file)
|
||||
- skills-external/design-motion-principles/SKILL.md [gated 2026-09-29] — the only vendored external tracked in git; its copy carries the `effort: high` line the resync re-applies (user choice: gate, not untrack)
|
||||
@@ -7,6 +7,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **Effort round (BDR-108)**: every skill carries an entry level next to its model pin. `lib/effort-pins.txt` (map) + `lib/effort-pins.sh` (idempotent re-apply after the last vendoring step of `install-plugins.sh` and `update-all.sh`) replace the hardcoded brainstorming/writing-plans loop and extend the pins to the design stack (high, one level per stack since the last loaded wins), superpowers, agent-skills and the 21st pack; `skills-perso` low, `pdf-translate` medium, `site-motion` high; doctrine: the design stack loads paired with the first Read (a lone Skill call applies nothing). Model pins stay tier aliases: the latest version of a tier is also the cheapest or same-priced, so the quality/price trade-off is tier × effort, never version. `lib/effort-audit.py` prints thinking coverage per scope (sub-agent records carry no thinking count on ~90 % of requests: EVAL-037's "executors stay cheap" was a measurement gap, not a finding).
|
||||
- **Effort tiering (BDR-107)**: reasoning effort routed per role and per phase. Session default `high`; `effort:` pins on the 20 repo-authored agents; entry level on 28 tracked user-invoked skills plus the two vendored superpowers skills (re-applied by `install-plugins.sh` after resync); five shifter skills `effort-low` … `effort-max` loaded at phase boundaries per `lib/effort-shift.md`, always sent with the step's first tool call (a lone Skill call is a no-op on 2.1.283), with `max` at the verify-secure caps and ship-feature 4b; `/effort-max` as the turn-scoped relaunch lever; statusline shows the live level; session banner warns when `CLAUDE_CODE_EFFORT_LEVEL` silences the pins; census `lib/tests/effort-routing.test.sh`; transcript audit `lib/effort-audit.py`.
|
||||
- **Design gate asks the user to sign in to 21st instead of skipping it**:
|
||||
`lib/design-tool-gate.sh` adds a three-state 21st auth predicate
|
||||
@@ -462,6 +463,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
||||
plugin cache or `claude plugin list`.
|
||||
|
||||
### Fixed
|
||||
- `update-all.sh` re-fetched the vendored skills at every run but never re-applied the effort pins: brainstorming/writing-plans lost their xhigh until the next `make plugin` (BDR-107 gap, closed by `lib/effort-pins.sh`).
|
||||
- **gitflow pre-commit blocked every commit with gitleaks 8.16** (Ubuntu's apt
|
||||
package): the hook ran `gitleaks git --staged`, a subcommand that exists from
|
||||
8.19 only, so the "unknown command" exit 1 read as a leak. The generator now
|
||||
|
||||
@@ -283,6 +283,10 @@ design routing; the design-toolchain hook reinforces it.
|
||||
- Design system / brand → design-consultation first, then the build tools.
|
||||
- Review / audit → design-review + emil-design-eng + design-motion-principles
|
||||
+ /impeccable audit|critique + `impeccable detect` floor.
|
||||
- Load the stack paired with the first Read of the target file, never
|
||||
alone (a lone Skill call applies no effort, `lib/effort-shift.md`); every
|
||||
vendored member pins `high`, one level per stack (`lib/effort-pins.txt`);
|
||||
plugin and gstack members run at the level in force.
|
||||
Scope doubt → ask or default to Build, never silently skip. Gate: light
|
||||
skills run `~/.claude/lib/design-gate.md`, orchestrators plugin-check. 21st =
|
||||
CLI (`npm i -g @21st-dev/cli`, `21st login`), no MCP, no key; search free,
|
||||
|
||||
@@ -93,6 +93,23 @@ was split like `/feat` (reflection inline + gate, `hotfixer` executor) and so
|
||||
joins the gated group (13th); `/client-handover`'s nested skill-runner
|
||||
children are dispatched `model:"fable"` (they carry reflection).
|
||||
|
||||
## Effort routing (BDR-107, BDR-108)
|
||||
|
||||
Second axis of the same table: how hard each phase thinks. Session default
|
||||
`high`. Every typed agent carries an `effort:` pin next to its `model:` (low
|
||||
appliers, medium executors, high judgment, xhigh challengers and gates; none
|
||||
on haiku, which rejects the parameter). Every user-invoked skill carries an
|
||||
entry level (`/status` low … `/ship-feature` xhigh); the vendored externals
|
||||
(design stack, superpowers, agent-skills, 21st) get theirs from
|
||||
`lib/effort-pins.txt`, re-applied by `lib/effort-pins.sh` after every
|
||||
vendoring step. Orchestrators shift per phase through the `effort-low` …
|
||||
`effort-max` skills (`lib/effort-shift.md`, always sent with another tool
|
||||
call: a lone Skill call applies nothing). Model pins stay tier aliases
|
||||
(`sonnet`, `opus`, `haiku`, `fable`): the latest version of a tier is also
|
||||
the cheapest or same-priced, so the quality/price trade-off is tier × effort,
|
||||
never version. Census `lib/tests/effort-routing.test.sh`; transcript audit
|
||||
`python3 lib/effort-audit.py`.
|
||||
|
||||
---
|
||||
|
||||
## Install notes
|
||||
|
||||
@@ -171,6 +171,20 @@ Tu veux...
|
||||
|
||||
---
|
||||
|
||||
### Niveau d'effort
|
||||
|
||||
Chaque commande démarre à un niveau de réflexion fixé dans son frontmatter
|
||||
(`effort:`) : low pour la tenue de registre (`/status`, `/close`,
|
||||
`/commit-change`), medium pour le courant (`/gitflow`, `/prune-memory`),
|
||||
high pour un fix ou un refactor (`/feat`, `/hotfix`, `/bugfix`, `/refactor`,
|
||||
audits avec fix), xhigh pour l'architecture et l'audit avant validation
|
||||
(`/ship-feature`, `/onboard`, `/analyze`). Les orchestrateurs décalent
|
||||
ensuite le niveau par phase (`lib/effort-shift.md`), et `/effort-max` tapé à
|
||||
la main relance un tour bloqué au maximum. Les skills externes vendorés
|
||||
(pile design, superpowers, 21st) reçoivent leur niveau de
|
||||
`lib/effort-pins.txt`. Un skill chargé seul par Claude n'applique pas son
|
||||
niveau : il doit partir avec un autre appel d'outil dans le même message.
|
||||
|
||||
## Les plugins — décision rapide
|
||||
|
||||
```
|
||||
|
||||
+10
-10
@@ -934,16 +934,9 @@ for _ext_skill in "${EXT_SKILL_NAMES[@]}"; do
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Effort tiering (BDR-107): the vendored brainstorming/writing-plans carry an
|
||||
# effort pin upstream lacks; re-apply after every resync (census lock in
|
||||
# lib/tests/effort-routing.test.sh alarms if this ever stops working).
|
||||
for _s in brainstorming writing-plans; do
|
||||
_f="$(cd "$(dirname "$0")" && pwd)/skills-external/$_s/SKILL.md"
|
||||
if [ -f "$_f" ] && ! grep -q '^effort:' "$_f"; then
|
||||
sed -i "0,/^name: $_s\$/s//&\neffort: xhigh/" "$_f"
|
||||
fi
|
||||
done
|
||||
unset _s _f
|
||||
# Effort pins (BDR-107, BDR-108): every vendored external gets its entry
|
||||
# level from lib/effort-pins.txt, re-applied ONCE after the last vendoring
|
||||
# step (the 21st pack, STEP 8.7) — see apply_effort_pins there.
|
||||
|
||||
# ============================================================
|
||||
# STEP 8.5 — EXTERNAL SKILLS (npx skills add …)
|
||||
@@ -1062,6 +1055,13 @@ if command -v 21st &>/dev/null; then
|
||||
rm -rf "$TFD_STAGE"
|
||||
fi
|
||||
|
||||
# Effort pins (BDR-107, BDR-108): the vendored externals carry no `effort:`
|
||||
# upstream and every vendoring step above rewrites SKILL.md. Re-apply the
|
||||
# entry levels from lib/effort-pins.txt once, after the LAST such step.
|
||||
# shellcheck source=lib/effort-pins.sh disable=SC1091
|
||||
source "$REPO/lib/effort-pins.sh"
|
||||
apply_effort_pins "$REPO" || warn "effort pins: map lines rejected — fix lib/effort-pins.txt"
|
||||
|
||||
# Auth — detect, then offer login ONLY in an interactive TTY. A non-interactive
|
||||
# run (CI / headless / re-run) must never open a browser or block on OAuth.
|
||||
# Search and logo lookup are free; retrieving component code and 21st AI need
|
||||
|
||||
+39
-14
@@ -10,18 +10,21 @@ import sys
|
||||
|
||||
# Weights relative to input price.
|
||||
WEIGHTS = {"in": 1.0, "cc": 1.25, "cr": 0.1, "out": 5.0}
|
||||
FIELDS = ("in", "cc", "cr", "out", "think")
|
||||
FIELDS = ("in", "cc", "cr", "out", "think", "nodet")
|
||||
|
||||
|
||||
def usage_row(usage):
|
||||
"""Map one API usage block to the five counted fields."""
|
||||
details = usage.get("output_tokens_details") or {}
|
||||
"""Map one API usage block to the counted fields. `nodet` marks a
|
||||
record whose usage carries no output_tokens_details at all: no thinking
|
||||
count was recorded (most sub-agent records), so `think` understates."""
|
||||
details = usage.get("output_tokens_details")
|
||||
return {
|
||||
"in": usage.get("input_tokens", 0) or 0,
|
||||
"cc": usage.get("cache_creation_input_tokens", 0) or 0,
|
||||
"cr": usage.get("cache_read_input_tokens", 0) or 0,
|
||||
"out": usage.get("output_tokens", 0) or 0,
|
||||
"think": details.get("thinking_tokens", 0) or 0,
|
||||
"think": (details or {}).get("thinking_tokens", 0) or 0,
|
||||
"nodet": 0 if details else 1,
|
||||
}
|
||||
|
||||
|
||||
@@ -57,21 +60,27 @@ def weighted(counter):
|
||||
return sum(counter[f] * WEIGHTS[f] for f in WEIGHTS)
|
||||
|
||||
|
||||
def report(agg):
|
||||
"""Print the per-key table, then the main/sub split and the thinking
|
||||
share."""
|
||||
total = collections.Counter()
|
||||
for counter in agg.values():
|
||||
total.update(counter)
|
||||
total_w = weighted(total) or 1
|
||||
def coverage(counter):
|
||||
"""Share of requests whose usage carries a thinking count."""
|
||||
return 100 * (1 - counter["nodet"] / max(counter["msgs"], 1))
|
||||
|
||||
|
||||
def print_rows(agg, total_w):
|
||||
"""One line per (scope, model, effort), costliest first."""
|
||||
print(f"{'scope':5} {'model':22} {'effort':7} {'msgs':>6} {'think/msg':>9} "
|
||||
f"{'think_tok':>10} {'out_tok':>10} {'cache_read':>12} {'%wcost':>7}")
|
||||
f"{'think_tok':>10} {'out_tok':>10} {'cache_read':>12} {'%wcost':>7} "
|
||||
f"{'%counted':>8}")
|
||||
ranked = sorted(agg.items(), key=lambda kv: -weighted(kv[1]))
|
||||
for (scope, model, effort), c in ranked:
|
||||
per_msg = c["think"] / max(c["msgs"], 1)
|
||||
print(f"{scope:5} {model:22} {effort:7} {c['msgs']:6d} "
|
||||
f"{per_msg:9.0f} {c['think']:10d} {c['out']:10d} "
|
||||
f"{c['cr']:12d} {100 * weighted(c) / total_w:6.1f}%")
|
||||
f"{c['cr']:12d} {100 * weighted(c) / total_w:6.1f}% "
|
||||
f"{coverage(c):7.0f}%")
|
||||
|
||||
|
||||
def print_scopes(agg, total, total_w):
|
||||
"""Main/sub split, thinking share and the coverage caveat."""
|
||||
by_scope = collections.defaultdict(collections.Counter)
|
||||
for (scope, _, _), c in agg.items():
|
||||
by_scope[scope].update(c)
|
||||
@@ -79,11 +88,27 @@ def report(agg):
|
||||
print(f" {scope:5} weighted-cost "
|
||||
f"{100 * weighted(c) / total_w:5.1f}% thinking "
|
||||
f"{100 * c['think'] / max(total['think'], 1):5.1f}% "
|
||||
f"requests {c['msgs']}")
|
||||
f"requests {c['msgs']} thinking counted on "
|
||||
f"{coverage(c):.0f}% of them")
|
||||
print(f" thinking = "
|
||||
f"{100 * total['think'] * WEIGHTS['out'] / total_w:.1f}% "
|
||||
f"of weighted cost; cache reads = "
|
||||
f"{100 * total['cr'] * WEIGHTS['cr'] / total_w:.1f}%")
|
||||
low = [s for s, c in by_scope.items() if coverage(c) < 50]
|
||||
if low:
|
||||
print(f" CAVEAT: {', '.join(low)} records mostly carry no thinking "
|
||||
f"count — their think columns are a floor, not a measure")
|
||||
|
||||
|
||||
def report(agg):
|
||||
"""Print the per-key table, then the main/sub split and the thinking
|
||||
share."""
|
||||
total = collections.Counter()
|
||||
for counter in agg.values():
|
||||
total.update(counter)
|
||||
total_w = weighted(total) or 1
|
||||
print_rows(agg, total_w)
|
||||
print_scopes(agg, total, total_w)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
Executable
+108
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/effort-pins.sh — re-apply the entry effort level on vendored skills
|
||||
# (BDR-107 second axis, extended to every vendored external by BDR-108).
|
||||
# Upstream copies carry no `effort:` and every vendoring step rewrites
|
||||
# SKILL.md, so the level lives in lib/effort-pins.txt and this helper puts
|
||||
# it back after the last vendoring step of install-plugins.sh and
|
||||
# update-all.sh. Idempotent: same level → untouched, other level →
|
||||
# replaced inside the frontmatter only, skill not vendored → skipped,
|
||||
# malformed map line → rejected loudly, never applied. Four hardenings:
|
||||
# a map whose last line lacks a newline is still read; a SKILL.md whose
|
||||
# frontmatter never closes is skipped untouched; the level is re-read after
|
||||
# every write and a mismatch (CRLF, malformed) counts as failed; the write
|
||||
# goes through a mktemp sibling removed on any failure. Placement inside the
|
||||
# frontmatter has no effect on the harness, which reads the key anywhere.
|
||||
#
|
||||
# Usage: source it, then `apply_effort_pins [repo-root]`
|
||||
# or standalone: bash lib/effort-pins.sh [repo-root]
|
||||
# Exit 1 when at least one map line was rejected or a skill failed.
|
||||
|
||||
EFFORT_PINS_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
EFFORT_PIN_LEVEL_RE='^(low|medium|high|xhigh|max)$'
|
||||
EFFORT_PIN_NAME_RE='^[A-Za-z0-9][A-Za-z0-9._-]*$'
|
||||
|
||||
# Callers (install-plugins.sh, update-all.sh) define these; standalone
|
||||
# runs get plain fallbacks.
|
||||
declare -F ok >/dev/null || ok() { printf ' ok %s\n' "$*"; }
|
||||
declare -F info >/dev/null || info() { printf ' info %s\n' "$*"; }
|
||||
declare -F err >/dev/null || err() { printf ' ERR %s\n' "$*" >&2; }
|
||||
|
||||
# _effort_pin_current <skill-file> → prints the frontmatter effort, if any
|
||||
_effort_pin_current() {
|
||||
awk 'NR==1&&/^---$/{p=1;next} p&&/^---$/{exit} p' "$1" \
|
||||
| sed -n 's/^effort: //p' | head -1
|
||||
}
|
||||
|
||||
# _effort_pin_closed <skill-file> → rc 0 when the frontmatter has a closing ---
|
||||
_effort_pin_closed() {
|
||||
awk 'NR==1&&/^---$/{p=1;next} p&&/^---$/{f=1;exit} END{exit !f}' "$1"
|
||||
}
|
||||
|
||||
# _effort_pin_write <skill-file> <name> <level> — replace the frontmatter
|
||||
# `effort:` line, or insert one after `name: <name>` (before the closing
|
||||
# `---` when the frontmatter has no name line). Body lines never change.
|
||||
# Writes a mktemp sibling then renames; any failure leaves no temp behind.
|
||||
_effort_pin_write() {
|
||||
local file="$1" name="$2" level="$3" tmp
|
||||
tmp="$(mktemp "$file.XXXXXX")" || return 1
|
||||
cp -p "$file" "$tmp" && awk -v n="$name" -v lvl="$level" '
|
||||
NR==1 && /^---$/ { fm=1; print; next }
|
||||
fm && /^---$/ {
|
||||
if (!done) { print "effort: " lvl; done=1 }
|
||||
fm=0; print; next
|
||||
}
|
||||
fm && /^effort: / { if (!done) { print "effort: " lvl; done=1 }; next }
|
||||
fm && $0 == "name: " n { print; if (!done) { print "effort: " lvl; done=1 }; next }
|
||||
{ print }
|
||||
' "$file" > "$tmp" && mv "$tmp" "$file" && return 0
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
}
|
||||
|
||||
# _effort_pin_apply_one <file> <name> <level> → rc 0 applied, 2 already at
|
||||
# level, 1 failed (err line printed, file untouched or write rolled back)
|
||||
_effort_pin_apply_one() {
|
||||
local file="$1" name="$2" level="$3"
|
||||
if ! _effort_pin_closed "$file"; then
|
||||
err "effort-pins: $file: frontmatter never closed — skipped"; return 1
|
||||
fi
|
||||
[ "$(_effort_pin_current "$file")" = "$level" ] && return 2
|
||||
if ! _effort_pin_write "$file" "$name" "$level"; then
|
||||
err "effort-pins: $file: write failed"; return 1
|
||||
fi
|
||||
if [ "$(_effort_pin_current "$file")" != "$level" ]; then
|
||||
err "effort-pins: $file: level not applied (CRLF or malformed frontmatter?)"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# apply_effort_pins [repo-root] — walk the map, pin every vendored skill
|
||||
apply_effort_pins() {
|
||||
local repo="${1:-$EFFORT_PINS_REPO}" map name level rest file rc
|
||||
local applied=0 kept=0 rejected=0 failed=0
|
||||
map="$repo/lib/effort-pins.txt"
|
||||
[ -f "$map" ] || { err "effort-pins: map missing: $map"; return 1; }
|
||||
while read -r name level rest || [ -n "$name" ]; do
|
||||
case "$name" in ''|'#'*) continue ;; esac
|
||||
if [ -n "$rest" ] || ! [[ "$name" =~ $EFFORT_PIN_NAME_RE ]] \
|
||||
|| ! [[ "$level" =~ $EFFORT_PIN_LEVEL_RE ]]; then
|
||||
err "effort-pins: rejected map line '$name $level $rest'"
|
||||
rejected=$((rejected + 1)); continue
|
||||
fi
|
||||
file="$repo/skills-external/$name/SKILL.md"
|
||||
[ -f "$file" ] || continue
|
||||
_effort_pin_apply_one "$file" "$name" "$level"; rc=$?
|
||||
case "$rc" in
|
||||
0) applied=$((applied + 1)) ;;
|
||||
2) kept=$((kept + 1)) ;;
|
||||
*) failed=$((failed + 1)) ;;
|
||||
esac
|
||||
done < "$map"
|
||||
ok "effort-pins: $applied applied, $kept already at level, $failed failed"
|
||||
[ "$rejected" -eq 0 ] && [ "$failed" -eq 0 ]
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
apply_effort_pins "$@"
|
||||
fi
|
||||
@@ -0,0 +1,46 @@
|
||||
# lib/effort-pins.txt — entry effort level of the vendored skills
|
||||
# (skills-external/<name>/SKILL.md). Upstream copies carry no `effort:` and
|
||||
# every resync rewrites SKILL.md, so the pin lives here and
|
||||
# lib/effort-pins.sh re-applies it after the last vendoring step of
|
||||
# install-plugins.sh and update-all.sh. One line = `<skill> <level>`,
|
||||
# level in low|medium|high|xhigh|max. The census
|
||||
# lib/tests/effort-routing.test.sh checks every vendored file against this
|
||||
# map. Rungs (BDR-107, BDR-108): low = fix a line, run a script · medium =
|
||||
# day-to-day · high = refactor, resisting bug · xhigh = architecture, audit
|
||||
# before validation · max = stuck.
|
||||
#
|
||||
# superpowers (obra/superpowers, plugins.lock.json "superpowers")
|
||||
brainstorming xhigh
|
||||
writing-plans xhigh
|
||||
requesting-code-review xhigh
|
||||
subagent-driven-development high
|
||||
writing-skills high
|
||||
test-driven-development medium
|
||||
using-git-worktrees low
|
||||
#
|
||||
# agent-skills (addyosmani/agent-skills, plugins.lock.json "agent-skills")
|
||||
deprecation-and-migration high
|
||||
ci-cd-and-automation medium
|
||||
observability-and-instrumentation medium
|
||||
#
|
||||
# design stack — ONE level for every member: these skills load stacked in a
|
||||
# single UI build and the last loaded wins (lib/effort-shift.md), so two
|
||||
# levels in the stack would make the effort depend on load order.
|
||||
# skills/site-motion (repo-authored) pins the same level in its frontmatter.
|
||||
frontend-design high
|
||||
emil-design-eng high
|
||||
design-motion-principles high
|
||||
21st-ui-build high
|
||||
scroll-world-storytelling high
|
||||
build-threejs-scroll-worlds high
|
||||
scroll-scrubbed-visual-sequence high
|
||||
scroll-scrubbed-word-reveal high
|
||||
scroll-progress-timeline high
|
||||
#
|
||||
# 21st pack (`21st skills install`): tooling low, generation high, critique xhigh
|
||||
21st-cli-use low
|
||||
21st-registry low
|
||||
21st-design-sync low
|
||||
21st-ai high
|
||||
21st-ui-explore high
|
||||
21st-ui-review xhigh
|
||||
@@ -24,6 +24,11 @@ max (stuck error, judged need).
|
||||
Claude loads alone, such as `brainstorming` or `writing-plans`, applies
|
||||
nothing). Last loaded wins, both directions. The prompt cache survives a
|
||||
shift.
|
||||
- **Stacked skills share one level**: skills that load together in one
|
||||
build (the design stack) all pin the same level, since the last loaded
|
||||
wins. Vendored externals get their level from `lib/effort-pins.txt`,
|
||||
re-applied by `lib/effort-pins.sh` after every vendoring step; repo
|
||||
skills carry it in their frontmatter.
|
||||
- Dispatched agents run on their own `effort:` pin, never on a shift.
|
||||
Unpinned agents inherit the level in force at dispatch.
|
||||
- Headless sessions (`-p`, `claude agents`, SDK) ignore skill-level effort:
|
||||
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/tests/effort-pins.test.sh — lib/effort-pins.sh's apply_effort_pins():
|
||||
# insert after `name:`, keep an equal level untouched, replace a different
|
||||
# level inside the frontmatter only (a prose `effort:` in the body stays),
|
||||
# skip a skill not vendored, insert before the closing `---` when the
|
||||
# frontmatter has no name line, run idempotently, reject a bad level, a
|
||||
# traversal name and a three-field line before writing anything, and
|
||||
# parse the real map without error; hardening: last map line without a
|
||||
# newline, unterminated frontmatter, CRLF file and read-only directory. All on a throwaway fixture repo.
|
||||
set -u
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
LIB="$ROOT/lib/effort-pins.sh"
|
||||
pass=0; fail=0
|
||||
check() { if [ "$2" = "$3" ]; then pass=$((pass+1)); echo "PASS $1"
|
||||
else fail=$((fail+1)); echo "FAIL $1: got[$2] want[$3]"; fi; }
|
||||
fm_effort() { awk 'NR==1&&/^---$/{p=1;next} p&&/^---$/{exit} p' "$1" \
|
||||
| sed -n 's/^effort: //p' | head -1; }
|
||||
|
||||
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
|
||||
REPO="$WORK/repo"; EXT="$REPO/skills-external"
|
||||
mkdir -p "$REPO/lib" "$EXT/alpha" "$EXT/beta" "$EXT/gamma" "$EXT/noname"
|
||||
printf -- '---\nname: alpha\ndescription: a\n---\nbody\n' > "$EXT/alpha/SKILL.md"
|
||||
printf -- '---\nname: beta\neffort: low\n---\nprose says effort: max here\n' > "$EXT/beta/SKILL.md"
|
||||
printf -- '---\nname: gamma\neffort: low\n---\nbody\n' > "$EXT/gamma/SKILL.md"
|
||||
printf -- '---\ndescription: no name line\n---\nbody\n' > "$EXT/noname/SKILL.md"
|
||||
printf '# map\nalpha high\nbeta medium\ngamma low\nghost xhigh\nnoname low\n' > "$REPO/lib/effort-pins.txt"
|
||||
gamma_before="$(cat "$EXT/gamma/SKILL.md")"
|
||||
|
||||
bash "$LIB" "$REPO" >/dev/null 2>&1; check T1-rc-clean "$?" 0
|
||||
check T2-insert-after-name "$(sed -n '3p' "$EXT/alpha/SKILL.md")" "effort: high"
|
||||
check T3-replace-in-frontmatter "$(fm_effort "$EXT/beta/SKILL.md")" "medium"
|
||||
check T3b-body-prose-untouched "$(grep -c 'effort: max' "$EXT/beta/SKILL.md")" 1
|
||||
check T3c-single-effort-line "$(grep -c '^effort:' "$EXT/beta/SKILL.md")" 1
|
||||
check T4-equal-level-untouched "$(cat "$EXT/gamma/SKILL.md")" "$gamma_before"
|
||||
check T5-missing-skill-skipped "$([ -e "$EXT/ghost" ] && echo created || echo absent)" absent
|
||||
check T6-no-name-inserts-before-closing "$(sed -n '3p' "$EXT/noname/SKILL.md")" "effort: low"
|
||||
check T6b-no-name-still-frontmatter "$(fm_effort "$EXT/noname/SKILL.md")" "low"
|
||||
snap="$(cat "$EXT"/*/SKILL.md)"
|
||||
bash "$LIB" "$REPO" >/dev/null 2>&1
|
||||
check T7-idempotent "$(cat "$EXT"/*/SKILL.md)" "$snap"
|
||||
check T7b-no-tmp-left "$(find "$EXT" -name '*.tmp' | wc -l)" 0
|
||||
|
||||
# rejections: nothing written, rc 1
|
||||
for bad in 'alpha turbo' '../evil high' 'alpha high extra'; do
|
||||
printf '%s\n' "$bad" > "$REPO/lib/effort-pins.txt"
|
||||
out="$(bash "$LIB" "$REPO" 2>&1)"; rc=$?
|
||||
check "T8-rejected[$bad]-rc" "$rc" 1
|
||||
check "T8-rejected[$bad]-named" "$(printf '%s' "$out" | grep -c 'rejected map line')" 1
|
||||
done
|
||||
check T8b-tree-unchanged-after-rejections "$(cat "$EXT"/*/SKILL.md)" "$snap"
|
||||
check T8c-no-evil-dir "$([ -e "$WORK/evil" ] && echo created || echo absent)" absent
|
||||
|
||||
# the real map parses: fixture repo with the real map and no vendored skill
|
||||
mkdir -p "$WORK/real/lib" "$WORK/real/skills-external"
|
||||
cp "$ROOT/lib/effort-pins.txt" "$WORK/real/lib/"
|
||||
out="$(bash "$LIB" "$WORK/real" 2>&1)"; check T9-real-map-parses "$?" 0
|
||||
check T9b-real-map-nothing-applied "$(printf '%s' "$out" | grep -c '0 applied, 0 already')" 1
|
||||
check T10-missing-map-rc "$(bash "$LIB" "$WORK/nowhere" >/dev/null 2>&1; echo $?)" 1
|
||||
|
||||
# hardening: each case in its own fixture repo
|
||||
mkrepo() { R="$WORK/$1"; mkdir -p "$R/lib" "$R/skills-external/$2"; }
|
||||
mkrepo h11 alpha; mkdir "$WORK/h11/skills-external/beta"
|
||||
printf -- '---\nname: alpha\n---\nb\n' > "$WORK/h11/skills-external/alpha/SKILL.md"
|
||||
printf -- '---\nname: beta\n---\nb\n' > "$WORK/h11/skills-external/beta/SKILL.md"
|
||||
printf 'alpha high\nbeta low' > "$WORK/h11/lib/effort-pins.txt"
|
||||
bash "$LIB" "$WORK/h11" >/dev/null 2>&1
|
||||
check T11-last-line-no-newline "$(fm_effort "$WORK/h11/skills-external/beta/SKILL.md")" low
|
||||
|
||||
mkrepo h12 open; f12="$WORK/h12/skills-external/open/SKILL.md"
|
||||
printf -- '---\nname: open\nbody effort: max\n' > "$f12"; b12="$(cat "$f12")"
|
||||
printf 'open high\n' > "$WORK/h12/lib/effort-pins.txt"
|
||||
out="$(bash "$LIB" "$WORK/h12" 2>&1)"; rc=$?
|
||||
check T12-unterminated-frontmatter-skipped \
|
||||
"$rc|$(cat "$f12" | cmp -s - <(printf '%s\n' "$b12") && echo same)|$(printf '%s' "$out" | grep -c "ERR .*$f12")" "1|same|1"
|
||||
|
||||
mkrepo h13 crlf
|
||||
printf -- '---\r\nname: crlf\r\n---\r\nbody\r\n' > "$WORK/h13/skills-external/crlf/SKILL.md"
|
||||
printf 'crlf high\n' > "$WORK/h13/lib/effort-pins.txt"
|
||||
out="$(bash "$LIB" "$WORK/h13" 2>&1)"; rc=$?
|
||||
check T13-crlf-not-counted-applied \
|
||||
"$rc|$(printf '%s' "$out" | grep -c 'ERR ')|$(printf '%s' "$out" | grep -c ' 0 applied, ')" "1|1|1"
|
||||
|
||||
mkrepo h14 ro; d14="$WORK/h14/skills-external/ro"
|
||||
printf -- '---\nname: ro\n---\nb\n' > "$d14/SKILL.md"
|
||||
printf 'ro high\n' > "$WORK/h14/lib/effort-pins.txt"
|
||||
chmod 555 "$d14"; out="$(bash "$LIB" "$WORK/h14" 2>&1)"; rc=$?; chmod 755 "$d14"
|
||||
check T14-write-failure-no-temp \
|
||||
"$rc|$(printf '%s' "$out" | grep -c 'ERR ')|$(find "$d14" -name 'SKILL.md.*' | wc -l)" "1|1|0"
|
||||
|
||||
echo "effort-pins: $pass pass, $fail fail"
|
||||
[ "$fail" -eq 0 ]
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/tests/effort-routing.test.sh — census: effort tiering (BDR-107)
|
||||
# agent pins, skill entry levels, shifter skills, orchestrator wiring, settings.
|
||||
# shellcheck disable=SC2015 # A && ok || ko is deliberate here: ok/ko never fail, so C never masks a true A
|
||||
# shellcheck disable=SC2015,SC2016 # A && ok || ko is deliberate (ok/ko never fail); '$REPO' locks are literal source text
|
||||
set -u
|
||||
R="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
pass=0; fail=0
|
||||
@@ -46,15 +46,37 @@ for s in status commit-change release-candidate doc capitalize close reconcile d
|
||||
for s in gitflow prune-memory; do fm_has_effort "skills/$s/SKILL.md" medium; done
|
||||
for s in feat hotfix bugfix refactor web-validate harden seo geo; do fm_has_effort "skills/$s/SKILL.md" high; done
|
||||
for s in ship-feature init-project onboard tour audit-delta analyze code-clean client-handover; do fm_has_effort "skills/$s/SKILL.md" xhigh; done
|
||||
# BDR-108 round: the three repo skills that had no level
|
||||
fm_has_effort "skills/skills-perso/SKILL.md" low
|
||||
fm_has_effort "skills/pdf-translate/SKILL.md" medium
|
||||
fm_has_effort "skills/site-motion/SKILL.md" high
|
||||
|
||||
# ── 9) vendored superpowers carry xhigh (spec D3). The files live in skills-external/ (gitignored,
|
||||
# machine-owned), so the durable artifact is the install-plugins.sh re-apply; the frontmatter
|
||||
# check skips VISIBLY when the skill is not vendored yet (fresh clone before make plugin).
|
||||
for s in brainstorming writing-plans; do
|
||||
if [ -f "$R/skills-external/$s/SKILL.md" ]; then fm_has_effort "skills-external/$s/SKILL.md" xhigh
|
||||
# ── 9) vendored externals carry the level of lib/effort-pins.txt (BDR-108). The files live in
|
||||
# skills-external/ (gitignored, machine-owned): the durable artifact is the map + the re-apply
|
||||
# after the last vendoring step of install-plugins.sh AND update-all.sh; a skill not vendored
|
||||
# yet SKIPs visibly (fresh clone before make plugin).
|
||||
while read -r s lvl _; do
|
||||
case "$s" in ''|'#'*) continue ;; esac
|
||||
if [ -f "$R/skills-external/$s/SKILL.md" ]; then fm_has_effort "skills-external/$s/SKILL.md" "$lvl"
|
||||
else printf 'SKIP skills-external/%s/SKILL.md not vendored yet (run make plugin)\n' "$s"; fi
|
||||
done
|
||||
has "install-plugins.sh" 'effort: xhigh'
|
||||
done < "$R/lib/effort-pins.txt"
|
||||
has "lib/effort-pins.txt" 'brainstorming xhigh'; has "lib/effort-pins.txt" 'writing-plans xhigh'
|
||||
has "install-plugins.sh" 'apply_effort_pins "$REPO"'; has "update-all.sh" 'apply_effort_pins "$REPO"'
|
||||
lacks "install-plugins.sh" 'for _s in brainstorming writing-plans; do'
|
||||
ln_last() { grep -n "$2" "$R/$1" | tail -1 | cut -d: -f1; }
|
||||
[ "$(ln_last install-plugins.sh 'apply_effort_pins "$REPO"')" -gt "$(ln_last install-plugins.sh 'rm -rf "$TFD_STAGE"')" ] \
|
||||
&& ok || ko "install-plugins.sh: effort pins must be re-applied after the 21st pack refresh"
|
||||
pins_ln=$(ln_last update-all.sh 'apply_effort_pins "$REPO"')
|
||||
[ "$pins_ln" -gt "$(ln_last update-all.sh 'skills-external/$_tfd_name')" ] \
|
||||
&& [ "$pins_ln" -gt "$(ln_last update-all.sh 'vendor_pinned_skills superpowers refresh')" ] \
|
||||
&& ok || ko "update-all.sh: effort pins must be re-applied after the last vendoring step (21st pack)"
|
||||
[ -x "$R/lib/effort-pins.sh" ] && ok || ko "lib/effort-pins.sh missing or not executable"
|
||||
# 9b) design stack = ONE level (last loaded wins); site-motion (repo skill) pins the same one
|
||||
stack_levels() { awk '/^# design stack/{f=1;next} f&&/^#$/{f=0} f&&!/^#/&&NF==2{print $2}' "$R/lib/effort-pins.txt" | sort -u; }
|
||||
[ "$(stack_levels | wc -l)" -eq 1 ] && ok || ko "design stack must share ONE level in lib/effort-pins.txt (got: $(stack_levels | tr '\n' ' '))"
|
||||
[ "$(stack_levels | wc -l)" -ge 1 ] && fm_has_effort "skills/site-motion/SKILL.md" "$(stack_levels | head -1)"
|
||||
has "lib/effort-shift.md" 'Stacked skills share one level'
|
||||
has "CLAUDE.global.md" 'lib/effort-pins.txt'
|
||||
|
||||
# ── 5) shifter skills + include (spec D4)
|
||||
for l in low medium high xhigh max; do fm_has_effort "skills/effort-$l/SKILL.md" "$l"; has "skills/effort-$l/SKILL.md" "name: effort-$l"; done
|
||||
@@ -101,7 +123,7 @@ has "lib/effort-shift.md" 'Before any built-in or unpinned dispatch'
|
||||
has "lib/model-gate.md" 'built-ins inherit the effort in force'
|
||||
has "skills/ship-feature/SKILL.md" 'effort-shift: error recovery'
|
||||
for s in feat hotfix bugfix seo geo harden web-validate ship-feature init-project onboard code-clean audit-delta; do has "skills/$s/SKILL.md" 'effort-shift: own level before the challenge'; done
|
||||
has "install-plugins.sh" 'for _s in brainstorming writing-plans; do'
|
||||
has "update-all.sh" 'source "$REPO/lib/effort-pins.sh"'
|
||||
|
||||
# ── summary (later tasks insert their locks ABOVE this line)
|
||||
printf 'effort-routing census: %d pass, %d fail\n' "$pass" "$fail"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
name: design-motion-principles
|
||||
effort: high
|
||||
description: "Motion and interaction design expert based on Emil Kowalski, Jakub Krehel, and Jhey Tompkins' techniques. Two modes — build interactive components with purposeful motion, or audit existing animations to catch AI-slop motion patterns (audit emits a branded HTML report with looping demos). Use when creating, adding, animating, or reviewing UI motion: transitions, hover states, micro-interactions, enter/exit animations, or any motion design work in React, Framer Motion, CSS, or HTML. Provides per-designer perspectives with context-aware weighting."
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
name: pdf-translate
|
||||
effort: medium
|
||||
description: Use when translating a PDF (especially OCR or image-based) to another language and producing faithful HTML output. Handles image extraction, layout preservation, contextual translation, and style-matched reconstruction. Triggers on "translate this PDF", "PDF en francais", "convert PDF to HTML translated", "traduire ce document".
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
name: site-motion
|
||||
effort: high
|
||||
description: |
|
||||
Site-level motion choreography: scroll engine choice, page-transition
|
||||
rules, and pin/scrub sequencing across a whole page or Astro route —
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
name: skills-perso
|
||||
effort: low
|
||||
description: |
|
||||
List personal (user-created) skills from ~/.claude/skills/.
|
||||
Excludes framework/gstack skills and symlinked/external skills.
|
||||
|
||||
@@ -513,6 +513,15 @@ print(d.get('21st',{}).get('version','latest'))
|
||||
rm -rf "$TFD_STAGE"
|
||||
fi
|
||||
|
||||
# Effort pins (BDR-107, BDR-108): every refresh above rewrites SKILL.md and
|
||||
# drops the `effort:` line; the 21st pack refresh is the last step that rewrites
|
||||
# a SKILL.md, so the entry levels of lib/effort-pins.txt go back here.
|
||||
echo ""
|
||||
echo "── Re-applying effort pins on the vendored skills..."
|
||||
# shellcheck source=lib/effort-pins.sh disable=SC1091
|
||||
source "$REPO/lib/effort-pins.sh"
|
||||
apply_effort_pins "$REPO" || warn "effort pins: map lines rejected — fix lib/effort-pins.txt"
|
||||
|
||||
# ── 7.5. Update external skills (npx skills) ──
|
||||
echo ""
|
||||
echo "── Updating external skills (npx skills)..."
|
||||
|
||||
Reference in New Issue
Block a user