From 47c9650ef863b13bb3a3928b929490980fb33b4b Mon Sep 17 00:00:00 2001 From: bastien Date: Mon, 28 Sep 2026 04:11:17 +0200 Subject: [PATCH] =?UTF-8?q?chore(memory):=20doctor=20vendored=20check=20?= =?UTF-8?q?=E2=80=94=20contract,=20CHANGELOG,=20BDR-104=20amendment,=20jou?= =?UTF-8?q?rnal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/memory/decisions.md | 1 + .claude/memory/journal.md | 1 + .claude/tasks/TODO.md | 11 +++++ .../2026-09-28-doctor-vendored-0317.md | 47 +++++++++++++++++++ CHANGELOG.md | 11 +++++ 5 files changed, 71 insertions(+) create mode 100644 .claude/tasks/contracts/2026-09-28-doctor-vendored-0317.md diff --git a/.claude/memory/decisions.md b/.claude/memory/decisions.md index f77d609..e40a186 100644 --- a/.claude/memory/decisions.md +++ b/.claude/memory/decisions.md @@ -1308,3 +1308,4 @@ Branch feature/user-writing-web-rules, UNMERGED (human gate). - **Caveats**: vendored prompts change upstream with no diff, pin = review point; GSAP-first content vs [[BDR-005]] `motion` default, site-motion states the allowance; LOW hardening open: `re.match` `$` accepts a trailing newline, `source`/`path`/`sha` lock fields not charset-checked; `make link` + `bash lib/profile.sh apply full` after merge (user); sub-agent leftovers `/tmp/mengto-verify` (user removes). - **Reference**: commits 2a1ad17 (helper + vendoring), ba14b5e (site-motion); contracts `.claude/tasks/contracts/2026-09-27-{mengto-vendor,site-motion-skill}-0002.md`; gates MET, verifiers CONFORME after 3 re-dispatches (frontmatter shape, refresh convention, security env override + traversal), security PASS ×2, `make test` 36 suites green minus 2 pre-existing T16a. Links [[BDR-103]] [[BDR-102]] [[LRN-141]] [[LRN-174]] [[EVAL-033]]. - **Amendment 2026-09-28**: the two LOW caveats closed on user ask (415b44e): `re.fullmatch` guard, `commit`/`source`/`path` validated before URL construction, 4 more hermetic cases (12). Security PASS, verifier CONFORME 9/9. +- **Amendment 2026-09-28 (doctor)**: `make doctor` now checks every vendored external (6394fa7, feature/doctor-vendored-skills): lock files present, symlink per active profile, parked ≠ failed, hints. Lib sourceable for the hermetic suite (11 cases); doctor mirrors `active_profile()` instead of sourcing profile.sh (its main runs on source). Security: lock shape-validated, allowlists on profile/item names. Closes the gap that emil/frontend-design/motion had since their install. diff --git a/.claude/memory/journal.md b/.claude/memory/journal.md index 77497cb..491841a 100644 --- a/.claude/memory/journal.md +++ b/.claude/memory/journal.md @@ -533,3 +533,4 @@ rules: - Case 7 (MengTo motion pack), user go "l'hybride" → [[BDR-104]]: `lib/vendor-skills.sh` shared helper (agent-skills moved onto it), 5 scroll skills vendored at a965851 (2a1ad17), `skills/site-motion` personal skill + routing (ba14b5e). Two analyzers read 22 skills first; 17 skipped (bugs, duplicates, covered, Codex/Xcode machinery). Gates MET, verifiers CONFORME after 3 re-dispatches (frontmatter shape, update-all refresh convention, security env override + traversal), security PASS ×2, `make test` 36 suites green minus 2 pre-existing T16a, shellcheck clean. [[LRN-174]] [[EVAL-033]]. feature/mengto-site-motion UNMERGED. Open for the user: `make link` + `bash lib/profile.sh apply full`, `rm -rf /tmp/mengto-verify`, LOW hardening (regex trailing newline, source/path/sha charset). - User: "fais les deux low, et après on merge". Hardening by fresh executor (415b44e): fullmatch guard + lock field validation, 12-case suite; verifier CONFORME 9/9, security PASS 0 findings, full make test 36 suites green minus 2 pre-existing T16a. Merge of feature/mengto-site-motion into develop follows. - 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. diff --git a/.claude/tasks/TODO.md b/.claude/tasks/TODO.md index b1295d4..90456d1 100644 --- a/.claude/tasks/TODO.md +++ b/.claude/tasks/TODO.md @@ -1,5 +1,16 @@ # TODO +## 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 +skills were invisible. Contract `.claude/tasks/contracts/2026-09-28-doctor-vendored-*`. +- [x] D1 6394fa7 `lib/doctor-vendored.sh` `check_vendored_skills`: lock expectations (list / + dict / single-path), link.sh EXTERNAL_SKILLS, profile-aware symlink check, + hints `make plugin` / `make link`; doctor.sh section; README line; hermetic suite. +- [x] D2 gates MET, verifier CONFORME ×2, security PASS ×2 (1 re-dispatch: malformed-lock + traceback → warn, allowlists), 37 suites green minus 2 T16a, CHANGELOG, BDR-104 + amendment, journal. UNMERGED — human gate. + ## 2026-09-27 — case 7: MengTo motion pack → vendor 5 + build site-motion (feature/mengto-site-motion) User go "ok pour 1, l'hybride" after two analyzers read 22 skills. Contracts under `.claude/tasks/contracts/2026-09-27-{mengto-vendor,site-motion-skill}-*`, two feater diff --git a/.claude/tasks/contracts/2026-09-28-doctor-vendored-0317.md b/.claude/tasks/contracts/2026-09-28-doctor-vendored-0317.md new file mode 100644 index 0000000..5c60aeb --- /dev/null +++ b/.claude/tasks/contracts/2026-09-28-doctor-vendored-0317.md @@ -0,0 +1,47 @@ +# CONTRACT — doctor-vendored +- date: 2026-09-28 | flow: feat (ad-hoc dispatch, /feat gates replayed by the orchestrator) | branch: feature/doctor-vendored-skills +- status: active + +## REQUEST (verbatim — IMMUTABLE) +> Add a `make doctor` check for the externally vendored skills, which doctor.sh ignores today (it only checks the gstack submodule). New `lib/doctor-vendored.sh` exposing `check_vendored_skills [profile_file]`, sourced and called by doctor.sh in a new "Vendored skills" section right after the gstack section. Expected state: (1) every plugins.lock.json entry with `managed_by: curl` has its files under `skills-external/`: `skills` as a list → `/SKILL.md` each; `skills` as a dict → every listed file; single-file `path` shape (emil-design-eng) → `/SKILL.md`; (2) every name in link.sh's `EXTERNAL_SKILLS` array has `skills-external//SKILL.md` and, when the name is listed in the active profile file (or when no profile file is given), a symlink `/skills/` → `/skills-external/`; a name absent from the active profile is reported as parked, not failed. Outcomes use doctor's helpers: `fail` ": — run: make plugin" for files, `fail` ": symlink missing/wrong — run: make link (or: bash lib/profile.sh apply )" for links, `info` ": parked by profile

", `pass` ": vendored + linked" otherwise, `warn` when the lock or link.sh cannot be read. Hermetic suite `lib/tests/doctor-vendored.test.sh`. README's doctor line names the new check. User go 2026-09-28 ("ok ajoute le check doctor"). + +## CLARIFICATIONS +- The lib defines fallback `pass/fail/warn/info` only when the caller has not (same `declare -F` guard as lib/vendor-skills.sh) so doctor.sh's counters (`ERRORS`, `WARNS`) keep working. +- Lock parsing with python3 via argv (never string-spliced); `EXTERNAL_SKILLS` parsed from link.sh with a single-purpose grep/sed of the array line(s), tolerant to the multi-line array. +- Active profile in doctor.sh: resolve the way lib/profile.sh's `active_profile()` does (read its code; the cache path it reads; no `claude` invocation); pass the profile file path `lib/profiles/.profile` to the check; if it cannot be resolved, call the check without a profile file (every external expected linked). +- A name in the profile counts whatever its label column says (external / personal), match on the first token of the line. +- Functions ≤ 25 logic lines, 80-char lines, ≤ 5 params, ≤ 5 locals. +- Suite: fixture repo under mktemp with a fake lock (list, dict with a references/ file, single-path shapes), a fake link.sh holding an `EXTERNAL_SKILLS=(...)` array, a fake `/skills` dir and a fake profile file; cases print `PASS `: ALL_PRESENT, FILE_MISSING, DICT_FILES_COMPLETE (dict entry missing one references file → fail names that file), SYMLINK_MISSING_ACTIVE, SYMLINK_WRONG_TARGET, SYMLINK_PARKED (name absent from the profile → info, no fail), NO_PROFILE_EXPECTS_LINK, LOCK_UNREADABLE (warn, rc 0). `PASS=n FAIL=m` summary. +- doctor.sh is read-only; the executor MAY run `bash doctor.sh` live for the criterion below (it inspects, never writes). + +## ACCEPTANCE CRITERIA +1. Lib exists and doctor.sh is wired. + CHECK: [ -f lib/doctor-vendored.sh ] && grep -q '^check_vendored_skills()' lib/doctor-vendored.sh && grep -q 'doctor-vendored.sh' doctor.sh && grep -q 'check_vendored_skills' doctor.sh && echo WIRED + EXPECT: WIRED + EVIDENCE: MET exit=0 marker-found :: WIRED +2. Hermetic suite green with every case. + CHECK: out=$(make test suite=lib/tests/doctor-vendored.test.sh 2>&1); echo "$out" | grep -qE "FAIL=[1-9]" && { echo "$out" | tail -15; exit 1; }; for k in ALL_PRESENT FILE_MISSING DICT_FILES_COMPLETE SYMLINK_MISSING_ACTIVE SYMLINK_WRONG_TARGET SYMLINK_PARKED NO_PROFILE_EXPECTS_LINK LOCK_UNREADABLE; do echo "$out" | grep -q "PASS $k" || { echo "missing PASS $k"; exit 1; }; done; echo SUITE_GREEN + EXPECT: SUITE_GREEN + EVIDENCE: MET exit=0 marker-found :: SUITE_GREEN +3. Live doctor on this machine: the eleven externals pass. + CHECK: out=$(bash doctor.sh 2>&1); ok=1; for s in emil-design-eng frontend-design design-motion-principles observability-and-instrumentation deprecation-and-migration ci-cd-and-automation scroll-world-storytelling build-threejs-scroll-worlds scroll-scrubbed-visual-sequence scroll-scrubbed-word-reveal scroll-progress-timeline; do echo "$out" | grep -qE "✓.*\b$s\b" || { echo "no pass line for $s"; ok=0; }; done; [ "$ok" -eq 1 ] && echo LIVE_PASS + EXPECT: LIVE_PASS + EVIDENCE: MET exit=0 marker-found :: LIVE_PASS +4. shellcheck clean. + CHECK: shellcheck doctor.sh lib/doctor-vendored.sh lib/tests/doctor-vendored.test.sh && echo SHELLCHECK_OK + EXPECT: SHELLCHECK_OK + EVIDENCE: MET exit=0 marker-found :: SHELLCHECK_OK +5. README names the check; doctrine citations resolve. + CHECK: grep -qi "vendored" README.md && out=$(make test suite=lib/tests/doctrine-citers.test.sh 2>&1) && ! echo "$out" | grep -qE "FAIL=[1-9]" && echo DOC_OK + EXPECT: DOC_OK + EVIDENCE: MET exit=0 marker-found :: DOC_OK + +## FILE SCOPE +- lib/doctor-vendored.sh (new), lib/tests/doctor-vendored.test.sh (new) +- doctor.sh (source line + one section), README.md (the `make doctor` / `bash doctor.sh` description lines) + +## PLAN +1. Read doctor.sh (helpers lines 12-15, `check_symlink` 38, the gstack section ~90-118, `check_automode` 258 + its call 310, the summary), lib/vendor-skills.sh (lock read pattern, fallback helpers), lib/profile.sh `active_profile()` + `read_profile()`, link.sh lines 90-105, one hermetic suite for the style. +2. lib/doctor-vendored.sh: `_dv_lock_expectations` (python3 argv → lines `\t`), `_dv_link_names` (parse EXTERNAL_SKILLS), `_dv_profile_has `, `_dv_check_files`, `_dv_check_link`, `check_vendored_skills`. +3. doctor.sh: source the lib; section header in the file's style; resolve the profile file; call the check. +4. README lines; suite; run criteria 1-5. diff --git a/CHANGELOG.md b/CHANGELOG.md index bdf2cc3..dd2f670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] ### Added +- **`make doctor` checks the vendored externals** — new + `lib/doctor-vendored.sh` (`check_vendored_skills`), wired into doctor.sh + after the gstack section: every curl-pinned entry of plugins.lock.json + has its files under `skills-external/` (list, dict or single-path lock + shapes), every `EXTERNAL_SKILLS` name of link.sh is symlinked into + `~/.claude/skills/` when the active profile lists it, parked names are + reported not failed, hints `make plugin` / `make link`. Lock entries are + shape-validated (a malformed lock yields one warn, never a traceback) and + profile, skill and file names pass an allowlist before becoming paths. + Until now doctor only checked the gstack submodule. Hermetic suite + `lib/tests/doctor-vendored.test.sh`, 11 cases. - **`skills/site-motion`** — personal skill for site-level motion choreography (scroll engine choice and Lenis/ScrollTrigger sync, Astro ClientRouter lifecycle, pin/scrub numbers, sticky stacks, video and image