11 KiB
CONTRACT — mengto-vendor
- date: 2026-09-27 | flow: feat (ad-hoc dispatch, /feat gates replayed by the orchestrator) | branch: feature/mengto-site-motion
- status: active
REQUEST (verbatim — IMMUTABLE)
Vendor five scroll-choreography skills from MengTo/Skills (
agent-skills/web-design) as machine-owned copies at pinned commita965851e27dc179e693fde1bee94457a64e1a7a5(main, 2026-09-23), text files only: scroll-world-storytelling (SKILL.md, REFERENCES.md); build-threejs-scroll-worlds (SKILL.md, references/kage-anatomy.md, references/quality-and-qa.md, references/realtime-architecture.md, references/scroll-conductor.js, references/world-bible.md); scroll-scrubbed-visual-sequence (SKILL.md, REFERENCES.md); scroll-scrubbed-word-reveal (SKILL.md, REFERENCES.md); scroll-progress-timeline (SKILL.md, REFERENCES.md). Never demo/, agents/, assets, images, video, fonts or minified js. Lock entrymengto-skillswith per-skill file lists; install and update read the pin from the lock; the vendoring loop becomes ONE shared helper used by both the agent-skills entry and this one (agent-skills behaviour unchanged, re-verified). Registered like emil-design-eng in link.sh, .gitignore, lib/toggle-external.sh, lib/profile.sh MANAGED_EXTERNALS and the design-class profiles (design, web, web-full, full). User go 2026-09-27 ("ok pour 1, l'hybride", case 7 of the repo review).
CLARIFICATIONS
- Lock shape:
"mengto-skills": {"source": "https://github.com/MengTo/Skills", "commit": "<sha>", "path": "agent-skills/web-design", "skills": {"<name>": ["SKILL.md", "..."]}, "managed_by": "curl", "note": "..."}. The helper accepts both shapes:skillsas a list of names (agent-skills: files = ["SKILL.md"], path default "skills") and as a dict name → file list. - Helper:
lib/vendor-skills.sh, functionvendor_pinned_skills <lock-key> [refresh], sourced by install-plugins.sh (Step 8e, replacing its inline loop) and update-all.sh (step 7.3, replacing its inline loop). Reads the lock with python3 via argv (never string-spliced). Raw URL =https://raw.githubusercontent.com/<owner>/<repo>/<sha>/<path>/<name>/<file>; the prefix up to<sha>is overridable throughVENDOR_BASE_URLso a hermetic test can servefile://fixtures. Per file: tmp + mv, tmp removed on failure; a skill counts as installed only when every listed file landed, otherwiseerrwith the manual curl. Skip files already present unlessrefresh. Subdirectories (references/) created as needed. - Byte-for-byte copies; Codex-isms in the text stay.
- Profiles: the five under the design/external section of design, web, web-full, full; ALSO add the line
site-motionwith thepersonallabel to the same four profiles (a sibling contract writes skills/site-motion and must not touch profiles); mirror how personal design skills are listed there. Never backend/dev. - Not mirrored on purpose (design-only or sibling-owned): CLAUDE.global.md, lib/design-gate.md, agents/plugin-advisor.md, agents/plugin-probe.md, lib/tests/fixtures/registry-index-drift.md, lib/profiles/backend.profile, lib/profiles/dev.profile.
- Executor materializes the files with the same curl (network read allowed); never runs link.sh, make link, make plugin, update-all.sh or install-plugins.sh.
- Routing census pre-checked 2026-09-27: no pair ≥ 0.50 among the five descriptions.
ACCEPTANCE CRITERIA
- Every listed file present per skill, frontmatter name = dir name, nothing else vendored. CHECK: ok=1; declare -A F=( [scroll-world-storytelling]="SKILL.md REFERENCES.md" [build-threejs-scroll-worlds]="SKILL.md references/kage-anatomy.md references/quality-and-qa.md references/realtime-architecture.md references/scroll-conductor.js references/world-bible.md" [scroll-scrubbed-visual-sequence]="SKILL.md REFERENCES.md" [scroll-scrubbed-word-reveal]="SKILL.md REFERENCES.md" [scroll-progress-timeline]="SKILL.md REFERENCES.md" ); for s in "${!F[@]}"; do for f in ${F[$s]}; do [ -f "skills-external/$s/$f" ] || { echo "missing $s/$f"; ok=0; }; done; grep -q "^name: $s$" "skills-external/$s/SKILL.md" || { echo "name mismatch $s"; ok=0; }; n=$(find "skills-external/$s" -type f | wc -l); [ "$n" -eq "$(echo ${F[$s]} | wc -w)" ] || { echo "extra files in $s"; ok=0; }; done; [ "$ok" -eq 1 ] && echo VENDORED EXPECT: VENDORED EVIDENCE: MET exit=0 marker-found :: VENDORED
- Pin and file lists recorded in the lock. CHECK: python3 -c 'import json; d=json.load(open("plugins.lock.json"))["mengto-skills"]; assert d["commit"]=="a965851e27dc179e693fde1bee94457a64e1a7a5", d; assert d["path"]=="agent-skills/web-design"; s=d["skills"]; assert set(s)=={"scroll-world-storytelling","build-threejs-scroll-worlds","scroll-scrubbed-visual-sequence","scroll-scrubbed-word-reveal","scroll-progress-timeline"}, s; assert set(s["build-threejs-scroll-worlds"])=={"SKILL.md","references/kage-anatomy.md","references/quality-and-qa.md","references/realtime-architecture.md","references/scroll-conductor.js","references/world-bible.md"}; assert all(set(v)=={"SKILL.md","REFERENCES.md"} for k,v in s.items() if k!="build-threejs-scroll-worlds"); print("PINNED")' EXPECT: PINNED EVIDENCE: MET exit=0 marker-found :: PINNED
- One shared helper, both scripts use it, no sha hardcoded. CHECK: [ -f lib/vendor-skills.sh ] && grep -q '^vendor_pinned_skills()' lib/vendor-skills.sh && grep -q 'vendor-skills.sh' install-plugins.sh && grep -q 'vendor-skills.sh' update-all.sh && [ "$(grep -c 'vendor_pinned_skills' install-plugins.sh)" -ge 2 ] && [ "$(grep -c 'vendor_pinned_skills' update-all.sh)" -ge 2 ] && ! grep -qE 'a965851|2686b620' lib/vendor-skills.sh install-plugins.sh update-all.sh link.sh lib/toggle-external.sh && echo LOCK_DRIVEN EXPECT: LOCK_DRIVEN EVIDENCE: MET exit=0 marker-found :: LOCK_DRIVEN
- Hermetic helper suite: list-shape and dict-shape entries, file:// base, tmp+mv, failure leaves nothing, refresh overwrites. CHECK: out=$(make test suite=lib/tests/vendor-skills.test.sh 2>&1); echo "$out" | grep -qE "FAIL=[1-9]" && { echo "$out" | tail -15; exit 1; }; for k in LIST_SHAPE DICT_SHAPE FAIL_LEAVES_NOTHING REFRESH_OVERWRITES SKIP_PRESENT; do echo "$out" | grep -q "PASS $k" || { echo "missing PASS $k"; exit 1; }; done; echo HELPER_SUITE_GREEN EXPECT: HELPER_SUITE_GREEN EVIDENCE: MET exit=0 marker-found :: HELPER_SUITE_GREEN
- Copies and symlink paths gitignored. CHECK: ok=1; for s in scroll-world-storytelling build-threejs-scroll-worlds scroll-scrubbed-visual-sequence scroll-scrubbed-word-reveal scroll-progress-timeline; do git check-ignore -q "skills-external/$s" && git check-ignore -q "skills/$s" || { echo "not ignored $s"; ok=0; }; done; [ "$ok" -eq 1 ] && echo IGNORED EXPECT: IGNORED EVIDENCE: MET exit=0 marker-found :: IGNORED
- link.sh, toggle-external, profile.sh and the four design profiles list the five; the four profiles also list
site-motionas personal. CHECK: ok=1; for s in scroll-world-storytelling build-threejs-scroll-worlds scroll-scrubbed-visual-sequence scroll-scrubbed-word-reveal scroll-progress-timeline; do grep -q "$s" link.sh && grep -q "$s" lib/toggle-external.sh && grep -q "$s" lib/profile.sh || { echo "not registered $s"; ok=0; }; for p in design web web-full full; do grep -q "^$s" "lib/profiles/$p.profile" || { echo "not in $p: $s"; ok=0; }; done; done; for p in design web web-full full; do grep -qE "^site-motion\s+personal" "lib/profiles/$p.profile" || { echo "site-motion missing in $p"; ok=0; }; done; for p in backend dev; do grep -qE "^(scroll-|site-motion)" "lib/profiles/$p.profile" && { echo "leak into $p"; ok=0; }; done; [ "$ok" -eq 1 ] && echo REGISTERED EXPECT: REGISTERED EVIDENCE: MET exit=0 marker-found :: REGISTERED - Profile, toggle-external, doctrine-citers and routing-census suites green. CHECK: out=$(make test suite="lib/tests/profile-default.test.sh lib/tests/profile-set-managed.test.sh lib/tests/toggle-external-repo-resolution.test.sh lib/tests/doctrine-citers.test.sh lib/tests/skill-routing-census.test.sh" 2>&1); echo "$out" | grep -qE "FAIL=[1-9]" && { echo "$out" | tail -20; exit 1; }; echo SUITES_GREEN EXPECT: SUITES_GREEN EVIDENCE: MET exit=0 marker-found :: SUITES_GREEN
- agent-skills behaviour intact through the shared helper. CHECK: ok=1; for s in observability-and-instrumentation deprecation-and-migration ci-cd-and-automation; do [ -f "skills-external/$s/SKILL.md" ] || ok=0; done; python3 -c 'import json; d=json.load(open("plugins.lock.json"))["agent-skills"]; assert d["commit"]=="2686b620fc1fed2e8f60c704839c766b8594c6b6"; assert isinstance(d["skills"], list) and len(d["skills"])==3' && [ "$ok" -eq 1 ] && echo AGENT_SKILLS_INTACT EXPECT: AGENT_SKILLS_INTACT EVIDENCE: MET exit=0 marker-found :: AGENT_SKILLS_INTACT
- shellcheck clean on every touched script. CHECK: shellcheck install-plugins.sh update-all.sh link.sh lib/toggle-external.sh lib/profile.sh lib/vendor-skills.sh lib/tests/vendor-skills.test.sh && echo SHELLCHECK_OK EXPECT: SHELLCHECK_OK EVIDENCE: MET exit=0 marker-found :: SHELLCHECK_OK
FILE SCOPE
- plugins.lock.json, install-plugins.sh, update-all.sh, link.sh, .gitignore
- lib/vendor-skills.sh (new), lib/tests/vendor-skills.test.sh (new)
- lib/toggle-external.sh, lib/profile.sh, lib/profiles/{design,web,web-full,full}.profile
- lib/tests/profile-default.test.sh, lib/tests/profile-set-managed.test.sh, lib/tests/toggle-external-repo-resolution.test.sh (only if they enumerate externals)
- skills-external// materialized (gitignored)
PLAN
- Read install-plugins.sh Step 8e +
pinned_commit, update-all.sh 7.3, link.sh EXTERNAL_SKILLS, .gitignore blocks, lib/toggle-external.sh, lib/profile.sh, the four design profiles (how emil-design-eng and personal skills are listed). lib/vendor-skills.sh: lock reader (python3 argv → source/commit/path/skills, normalising list → dict), URL builder honoringVENDOR_BASE_URL, per-file tmp+mv loop, skip/refresh, summary lines in the scripts' ok/info/err style (define fallbacks if sourced standalone). Functions ≤ 25 logic lines, 80-char lines.- install-plugins.sh Step 8e → source the lib, call
vendor_pinned_skills agent-skillsthenvendor_pinned_skills mengto-skills; update-all.sh 7.3 → same withrefresh. Remove the now-dead inline loops; keep or foldpinned_commit. - Lock entry; link.sh EXTERNAL_SKILLS += 5; .gitignore both patterns ×5 with a source comment; toggle-external MANAGED_TOOLS += 5; profile.sh MANAGED_EXTERNALS += 5; profiles (five +
site-motion personal). lib/tests/vendor-skills.test.sh: temp dir with a fake lock (one list-shape key, one dict-shape key with a references/ file), fixture tree served viaVENDOR_BASE_URL=file://…, checks LIST_SHAPE, DICT_SHAPE, SKIP_PRESENT, REFRESH_OVERWRITES, FAIL_LEAVES_NOTHING (missing fixture file → no partial file, no tmp).PASS=n FAIL=msummary.- Materialize the five with the helper against the real lock (network); run criteria 1-9.