From 1f4bd4a75af099d6190724d35c35e4837f96bded Mon Sep 17 00:00:00 2001 From: bastien Date: Wed, 30 Sep 2026 15:06:43 +0200 Subject: [PATCH] docs(plan): revise the higgsfield plan and spec after the three-lens challenge --- .../plans/2026-09-30-higgsfield-pack.md | 577 +++++++++++------- .../01-gitignore.patch | 9 +- .../01-lock.patch | 2 +- .../02-higgsfield-skills.sh | 73 ++- .../02-higgsfield.test.sh | 96 ++- .../03-suite.patch | 77 +-- .../03-toggle-external.patch | 88 ++- .../04-install-plugins.patch | 36 +- .../04-suite.patch | 10 +- .../05-suite.patch | 10 +- .../05-update-all.patch | 16 +- .../06-doctor.patch | 10 +- .../07-changelog.patch | 2 +- .../07-readme.patch | 25 +- .../08-claude-global.patch | 12 +- .../2026-09-30-higgsfield-pack-design.md | 132 ++-- 16 files changed, 738 insertions(+), 437 deletions(-) diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.md b/docs/superpowers/plans/2026-09-30-higgsfield-pack.md index a046d7a..f01e3f2 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.md +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.md @@ -2,9 +2,9 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** `make plugin` installs the Higgsfield CLI and its eight skills, off by default, with two toggles to turn them on. +**Goal:** `make plugin` installs the Higgsfield CLI and its skills, off by default, with two toggles to turn them on. -**Architecture:** A sourced helper (`lib/higgsfield-skills.sh`) clones the upstream skills into gitignored `skills-external/higgsfield-*`; both installers call it. `lib/toggle-external.sh` links the skills on demand through two tools, `higgsfield` (media pack) and `higgsfield-websites` (single skill). Nothing is listed in `link.sh` or in a profile, which is what keeps the pack off across re-runs. +**Architecture:** A sourced helper (`lib/higgsfield-skills.sh`) clones the upstream skills into gitignored `skills-external/higgsfield-*` and probes the CLI; both installers and the doctor call it. `lib/toggle-external.sh` links the skills on demand through two tools: `higgsfield`, a fixed allowlist of seven media skills, and `higgsfield-websites`, a single skill. Nothing is listed in `link.sh` or in a profile, which is what keeps the pack off across re-runs. **Tech Stack:** bash, git, npm (run by the user only), shellcheck, hermetic suites under `lib/tests/` run through `make test`. @@ -13,10 +13,12 @@ ## How this plan is packaged -Every edit below was dry-run in a scratch copy: the final suite prints 14 -PASS, shellcheck is clean, and each patch applies to the branch. The exact -bytes live in `docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/`. Each task shows its code inline for reading and names -the file to apply. Apply the file, never a retyped copy. +Every edit below was dry-run in a scratch copy, task by task, in order. The +suite went 0/5 → 5/0 (Task 2), 6/8 → 14/0 (Task 3), 14/1 → 15/0 (Task 4), +15/1 → 16/0 (Task 5); shellcheck is clean; each patch applies to the branch; +three deliberate bugs injected in the scratch code turned the suite red. The +exact bytes live in `docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/`. Each task shows its code inline for reading and +names the file to apply. Apply the file, never a retyped copy. Apply a patch with `git apply `. If `git apply` refuses (the target moved), stop and report `BLOCKED` with the error. Do not hand-merge. @@ -28,30 +30,42 @@ moved), stop and report `BLOCKED` with the error. Do not hand-merge. - Nothing about Higgsfield goes in `link.sh`, `lib/profile.sh`, `lib/profiles/*.profile` or `lib/effort-pins.txt` (BDR-093, BDR-079, BDR-105, BDR-107). The suite locks this. - The skill sync sits before the last `apply_effort_pins "$REPO"` in `install-plugins.sh` and `update-all.sh` (BDR-108, BLK-024). - `lib/toggle-external.sh` takes no new top-level `source` (LRN-178). -- The CLI token never reaches a terminal or a log: every `higgsfield auth token` call redirects to `/dev/null`. -- House limits for new code: functions of 25 logic lines at most, 80 columns, 5 parameters, 5 locals; shellcheck clean; comments state intent. +- The CLI token never reaches a terminal or a log: `higgsfield auth token` is only ever called through `_higgsfield_probe` (helper) or `bounded` (toggle), both of which redirect to `/dev/null`. +- CLI presence is proven by `higgsfield_cli_ok` (the binary answers), never by `command -v higgsfield` alone: the npm shim can sit on PATH with no binary behind it. +- Media pack membership is the allowlist `HIGGSFIELD_MEDIA_SKILLS`, never a glob: upstream is unpinned, and an unlisted skill must stay unlinked (default deny). +- House limits for new code: functions of 25 logic lines at most, 5 parameters, 5 locals; logic lines within 80 columns (message strings on `ok | info | warn | err | echo | printf` lines and the existing long `case` patterns follow the surrounding installer style and may run longer); shellcheck clean; comments state intent. - Commits: explicit paths only (`git add `), never `git add -A`, never `--no-verify`, no attribution trailer. The hooks push each commit. - `CLAUDE.global.md` and `settings.json` are hand-curated (BDR-028): only the orchestrator touches them. ## Review Focus 1. A refresh while the pack is enabled: the live `skills/` link must keep resolving. Pinned by `live-reads` in `SYNC_KEEPS_PARKED` (Task 2). -2. Upstream changes its layout (no `higgsfield-*/SKILL.md`): the sync must return non-zero and keep the existing copies. Pinned by `no-skills` in `SYNC_FAIL_KEEPS_COPY` (Task 2). -3. `enable higgsfield` on a machine with no CLI on PATH: links are created, one warning, exit 0. Pinned by `absent-*` in `SIGNED_OUT_WARNS` (Task 3). -4. npm holds back the package's postinstall script, so the shim exists but the binary does not: the installer must print the `--allow-scripts=` remedy. Pinned by `remedy` in `INSTALL_WIRING` (Task 4). +2. Upstream changes its layout (no `higgsfield-*/SKILL.md`, a pack-named dir without SKILL.md, a pack-named symlink): the sync must skip what is not a real skill directory, and return non-zero with the copies kept when nothing qualifies. Pinned by `noskill`, `symlink` and `no-skills` (Task 2). +3. Upstream adds or renames a skill: it is synced, reported, and never linked by `enable higgsfield`. Pinned by `UNLISTED_NOT_LINKED` (Task 3). +4. npm holds back the package's postinstall script, on a first install or on a later update, so the shim exists and the binary does not. `higgsfield_cli_ok` must say no (`shim-only` in `PROBES_SILENT`, Task 2), the toggle must name that cause and not "sign in" (`shim-*` in `SIGNED_OUT_WARNS`, Task 3), and both installers must gate on the probe (`probe-gates`, `probe-after-npm`, Tasks 4 and 5). The installer branches themselves cannot run in a suite: checked by reading. 5. The generalised pack arms must not change what `21st` prints or does. Pinned by `PACK_21ST_UNCHANGED` (Task 3). +## Rollback + +Before reverting these commits on a machine where the pack was enabled, run `bash lib/toggle-external.sh disable higgsfield` and `disable higgsfield-websites`. Otherwise the live `skills/higgsfield-*` links outlive the toggle that knows them and the gitignore rule that hides them. + +## Known limits (accepted) + +- A skill that upstream removes or renames keeps its last local copy; nothing prunes it. +- Upstream prompts change with no diff to review (tracks `main`). +- Whether `higgsfield auth token` stays local is unverified (closed-source binary), hence the 15 s bound. + --- ### Task 1: Lock entry and gitignore **Files:** - Modify: `plugins.lock.json` (new `higgsfield` entry before `graphifyy`) -- Modify: `.gitignore` (link side after `skills/21st-*`, source side after `skills-external/21st-*/`) +- Modify: `.gitignore` (link side after `skills/21st-*`, source side and sync stage after `skills-external/21st-*/`) **Interfaces:** - Consumes: nothing. -- Produces: lock key `higgsfield` with `version` (read by Tasks 4 and 5); ignore rules `skills/higgsfield-*` and `skills-external/higgsfield-*/` (LRN-025: both states of a toggleable artifact). +- Produces: lock key `higgsfield` with `version` (read by Tasks 4 and 5); ignore rules `skills/higgsfield-*`, `skills-external/higgsfield-*/` and `skills-external/.higgsfield-stage.*/` (LRN-025: both states of a toggleable artifact). - [ ] **Step 1: Apply the lock patch** @@ -67,7 +81,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/01-loc + "higgsfield": { + "source": "npm:@higgsfield/cli", + "version": "latest", -+ "note": "Higgsfield CLI (bins `higgsfield`, `higgs`) — image, video, audio and brand media generation, metered credits; auth is `higgsfield auth login` (browser). Install: npm install -g @higgsfield/cli. The package vendors its binary in a postinstall script; if npm holds it back, add --allow-scripts=@higgsfield/cli. The 8 skills are git-cloned from https://github.com/higgsfield-ai/skills (tracks main, no pin) into skills-external/higgsfield-* by lib/higgsfield-skills.sh (install-plugins.sh Step 8.6, refreshed by update-all.sh). OFF by default and in no profile: `lib/toggle-external.sh enable higgsfield` links the 7 media skills, `enable higgsfield-websites` the landing-page aid." ++ "note": "Higgsfield CLI (bins `higgsfield`, `higgs`) — image, video, audio and brand media generation, metered credits; auth is `higgsfield auth login` (browser). Install: npm install -g @higgsfield/cli. The package vendors its binary in a postinstall script; if npm holds it back, add --allow-scripts=@higgsfield/cli. The upstream skills are git-cloned from https://github.com/higgsfield-ai/skills (tracks main, no pin) into skills-external/higgsfield-* by lib/higgsfield-skills.sh (install-plugins.sh Step 8.6, refreshed by update-all.sh); a skill upstream removes keeps its last local copy. OFF by default and in no profile: `lib/toggle-external.sh enable higgsfield` links the 7 allowlisted media skills (HIGGSFIELD_MEDIA_SKILLS), `enable higgsfield-websites` the landing-page aid." + }, "graphifyy": { "source": "pypi:graphifyy", @@ -81,27 +95,28 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/01-git ````diff --- a/.gitignore +++ b/.gitignore -@@ -101,6 +101,12 @@ +@@ -101,6 +101,11 @@ # membership, so the pack can gain a skill with no edit here. skills/21st-* +# Higgsfield skill pack symlinks — created on demand by toggle-external.sh +# (`enable higgsfield` / `enable higgsfield-websites`). The pack is OFF by -+# default and in no profile, so these usually don't exist. A glob: the -+# upstream repo owns the membership. ++# default and in no profile, so these usually don't exist. +skills/higgsfield-* + # Context7 docs-lookup skill — installed by `ctx7 setup --claude --cli` # (install-plugins.sh Step 6, when absent) into ~/.claude/skills (a symlink to # this repo's skills/). ctx7-managed and re-created on demand — not vendored here. -@@ -236,6 +242,11 @@ +@@ -236,6 +241,13 @@ # layout and the content is sha256-verified against 21st.dev's manifest. skills-external/21st-*/ +# Higgsfield skill pack — machine-owned: a git clone of higgsfield-ai/skills, +# staged by lib/higgsfield-skills.sh (install-plugins.sh Step 8.6) and moved +# here, refreshed by update-all.sh. Not vendored: it tracks upstream main. ++# The second line is the helper's stage, left behind only by a killed run. +skills-external/higgsfield-*/ ++skills-external/.higgsfield-stage.*/ + # npx `skills add` project-scope artifacts — darwin-skill copies itself into # the repo's .agents/ and writes skills-lock.json at root. Our own agents live @@ -113,10 +128,10 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/01-git Run: ```bash python3 -c "import json;d=json.load(open('plugins.lock.json'))['higgsfield'];assert d['version']=='latest' and 'managed_by' not in d;print('LOCK_OK')" -git check-ignore -q skills/higgsfield-generate && git check-ignore -q skills-external/higgsfield-generate/SKILL.md && echo IGNORED_BOTH +git check-ignore -q skills/higgsfield-generate && git check-ignore -q skills-external/higgsfield-generate/SKILL.md && git check-ignore -q skills-external/.higgsfield-stage.abc123/src/x && echo IGNORED_ALL git check-ignore -q skills/feat/SKILL.md || echo CONTROL_OK ``` -Expected: `LOCK_OK`, `IGNORED_BOTH`, `CONTROL_OK` (a tracked skill is not ignored). +Expected: `LOCK_OK`, `IGNORED_ALL`, `CONTROL_OK` (a tracked skill is not ignored). - [ ] **Step 4: Commit** @@ -127,7 +142,7 @@ git commit -m "chore(higgsfield): lock entry and gitignore for the skill pack" --- -### Task 2: Sync helper and its suite +### Task 2: Sync helper, CLI probes and their suite **Files:** - Create: `lib/tests/higgsfield.test.sh` (from `docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/02-higgsfield.test.sh`) @@ -137,9 +152,11 @@ git commit -m "chore(higgsfield): lock entry and gitignore for the skill pack" - Consumes: nothing. - Produces: - `HIGGSFIELD_SKILLS_URL` (env value wins over the upstream default). - - `higgsfield_sync_skills `: prints the number of skills synced on stdout; returns 0 when at least one skill was synced, 1 otherwise (existing copies untouched). + - `higgsfield_sync_skills `: prints the number of skills synced on stdout; returns 0 when at least one skill was synced, 1 otherwise (existing copies untouched). Stages inside `/skills-external/.higgsfield-stage.*` so each replacement is a rename on one filesystem. + - `higgsfield_cli_ok`: returns 0 when `higgsfield version` answers; prints nothing. - `higgsfield_signed_in`: returns 0 when `higgsfield auth token` succeeds; prints nothing. - - Suite helpers later tasks reuse: `expect`, `expect_has`, `expect_not`, `verdict`, `yn`, `entries`, `git_q`, `$WORK`, `$ROOT`; the file ends with a `# ── tally ──` block that must stay last. + - Internal: `_higgsfield_adopt `, `_higgsfield_probe `. + - Suite helpers later tasks reuse: `expect`, `expect_has`, `expect_not`, `verdict`, `yn`, `entries`, `git_q`, `$WORK`, `$ROOT`, `$BIN` (fake `higgsfield` and `21st`), `$CLEAN` (a PATH with the core tools and no CLI); the file ends with a `# ── tally ──` block that must stay last. - [ ] **Step 1: Write the failing suite** @@ -149,7 +166,7 @@ Run: `cp docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/02-higgsfield #!/usr/bin/env bash # lib/tests/higgsfield.test.sh — hermetic suite for the Higgsfield pack. # sync lib/higgsfield-skills.sh against a local git repo shaped like -# upstream (no network) +# upstream (no network), and its CLI probes against a fake CLI # toggle lib/toggle-external.sh `higgsfield` / `higgsfield-websites` # against a fixture tree, fake CLIs first on PATH # wiring static locks on the installers (order, off by default) @@ -171,11 +188,42 @@ verdict() { } # yn — "yes" when the command succeeds, else "no". yn() { if "$@" 2>/dev/null; then echo yes; else echo no; fi; } +# entries — how many entries the directory holds, hidden ones included. entries() { find "$1" -mindepth 1 -maxdepth 1 | wc -l | tr -d ' '; } WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT +# Fake CLIs, first on PATH in every case that needs one. `higgsfield` +# answers per $FAKE_HF_BINARY (ok | missing: the npm shim without its +# binary) and $FAKE_HF_SESSION (in | out). +BIN="$WORK/bin"; mkdir -p "$BIN" +cat > "$BIN/higgsfield" <<'EOF' +#!/usr/bin/env bash +if [ "${FAKE_HF_BINARY:-ok}" = missing ]; then + echo "@higgsfield/cli: binary not found" >&2; exit 1 +fi +case "${1:-} ${2:-}" in + "version ") echo "higgsfield 0.0.0 (fixture) built never"; exit 0 ;; + "auth token") + if [ "${FAKE_HF_SESSION:-in}" = in ]; then echo "fixture-token"; exit 0; fi + echo "Error: Not authenticated." >&2; exit 2 ;; +esac +exit 64 +EOF +cat > "$BIN/21st" <<'EOF' +#!/usr/bin/env bash +[ "${1:-}" = whoami ] && echo "Logged in as fixture (saved in fixture)." +EOF +chmod +x "$BIN/higgsfield" "$BIN/21st" + +# A PATH that holds the tools the scripts under test need and nothing else: +# no `higgsfield`, no `timeout`, whatever this machine has installed. +CLEAN="$WORK/cleanbin"; mkdir -p "$CLEAN" +for t in bash dirname basename mkdir mv rm ln sed; do + ln -s "$(command -v "$t")" "$CLEAN/$t" +done + # git_q — quiet git in a fixture repo: own identity, no # hooks, so the machine's global git config never leaks in. git_q() { @@ -186,17 +234,19 @@ git_q() { } # mk_upstream — a git repo shaped like the upstream skills repo: three -# pack skills, one pack-named dir with no SKILL.md, one foreign skill, and -# root machinery that must never be synced. +# pack skills, a pack-named dir with no SKILL.md, a pack-named symlink to +# a foreign skill, and root machinery that must never be synced. mk_upstream() { local up="$1" s - mkdir -p "$up/scripts" "$up/higgsfield-empty" "$up/other-skill" - for s in higgsfield-alpha higgsfield-beta higgsfield-websites; do + mkdir -p "$up/scripts" "$up/higgsfield-noskill" "$up/other-skill" + for s in higgsfield-generate higgsfield-soul-id higgsfield-websites; do mkdir -p "$up/$s/references" printf -- '---\nname: %s\n---\n' "$s" > "$up/$s/SKILL.md" echo "ref" > "$up/$s/references/notes.md" done - echo "old" > "$up/higgsfield-alpha/old.md" + echo "old" > "$up/higgsfield-generate/old.md" + echo "no skill here" > "$up/higgsfield-noskill/README.md" + ln -s other-skill "$up/higgsfield-linked" echo "---" > "$up/other-skill/SKILL.md" echo "#!/bin/sh" > "$up/setup" echo "#!/bin/sh" > "$up/scripts/update-check.sh" @@ -216,48 +266,69 @@ sync_into() { ) } +# probe — run one CLI probe of the helper on the given +# PATH; prints everything it wrote, then "rc=". +probe() { + PATH="$1" bash -c 'source "$1/lib/higgsfield-skills.sh"; "$2"; echo "rc=$?"' \ + _ "$ROOT" "$2" 2>&1 +} + # ── sync ──────────────────────────────────────────────────── UP="$WORK/upstream"; mk_upstream "$UP" R1="$WORK/r1"; mkdir -p "$R1/skills" "$R1/skills-disabled" EXT="$R1/skills-external" +expect fixture "$(yn test -f "$UP/.git/HEAD")" yes expect rc-count "$(sync_into "$R1")" "0:3" -expect alpha "$(yn test -f "$EXT/higgsfield-alpha/SKILL.md")" yes -expect refs "$(yn test -f "$EXT/higgsfield-beta/references/notes.md")" yes +expect generate "$(yn test -f "$EXT/higgsfield-generate/SKILL.md")" yes +expect refs \ + "$(yn test -f "$EXT/higgsfield-soul-id/references/notes.md")" yes expect websites "$(yn test -f "$EXT/higgsfield-websites/SKILL.md")" yes -expect no-empty "$(yn test -e "$EXT/higgsfield-empty")" no +expect noskill "$(yn test -e "$EXT/higgsfield-noskill")" no +expect symlink "$(yn test -L "$EXT/higgsfield-linked")" no expect no-other "$(yn test -e "$EXT/other-skill")" no expect no-setup "$(yn test -e "$EXT/setup")" no expect no-git "$(find "$EXT" -name .git | wc -l | tr -d ' ')" 0 expect entries "$(entries "$EXT")" 3 verdict SYNC_MOVES_PACK_ONLY -rm "$UP/higgsfield-alpha/old.md"; echo "new" > "$UP/higgsfield-alpha/new.md" +rm "$UP/higgsfield-generate/old.md" +echo "new" > "$UP/higgsfield-generate/new.md" git_q "$UP" add -A; git_q "$UP" commit -m refresh -expect before "$(yn test -f "$EXT/higgsfield-alpha/old.md")" yes +expect before "$(yn test -f "$EXT/higgsfield-generate/old.md")" yes expect rc-count "$(sync_into "$R1")" "0:3" -expect stale-out "$(yn test -e "$EXT/higgsfield-alpha/old.md")" no -expect new-in "$(yn test -f "$EXT/higgsfield-alpha/new.md")" yes +expect stale-out "$(yn test -e "$EXT/higgsfield-generate/old.md")" no +expect new-in "$(yn test -f "$EXT/higgsfield-generate/new.md")" yes verdict SYNC_REFRESH_DROPS_STALE -ln -s "$EXT/higgsfield-beta" "$R1/skills-disabled/higgsfield-beta" -ln -s "$EXT/higgsfield-alpha" "$R1/skills/higgsfield-alpha" -expect rc-count "$(sync_into "$R1")" "0:3" -expect parked-link "$(yn test -L "$R1/skills-disabled/higgsfield-beta")" yes +ln -s "$EXT/higgsfield-soul-id" "$R1/skills-disabled/higgsfield-soul-id" +ln -s "$EXT/higgsfield-generate" "$R1/skills/higgsfield-generate" +expect rc-count "$(sync_into "$R1")" "0:3" +expect parked-link "$(yn test -L "$R1/skills-disabled/higgsfield-soul-id")" yes expect parked-reads \ - "$(yn test -f "$R1/skills-disabled/higgsfield-beta/SKILL.md")" yes -expect not-enabled "$(yn test -e "$R1/skills/higgsfield-beta")" no -expect live-reads "$(yn test -f "$R1/skills/higgsfield-alpha/SKILL.md")" yes + "$(yn test -f "$R1/skills-disabled/higgsfield-soul-id/SKILL.md")" yes +expect not-enabled "$(yn test -e "$R1/skills/higgsfield-soul-id")" no +expect live-reads "$(yn test -f "$R1/skills/higgsfield-generate/SKILL.md")" yes verdict SYNC_KEEPS_PARKED BARE="$WORK/bare-upstream"; mkdir -p "$BARE"; echo "x" > "$BARE/README.md" git_q "$BARE" init; git_q "$BARE" add -A; git_q "$BARE" commit -m fixture expect no-repo "$(sync_into "$R1" "$WORK/no-such-repo")" "1:0" expect no-skills "$(sync_into "$R1" "$BARE")" "1:0" -expect copy-kept "$(yn test -f "$EXT/higgsfield-alpha/new.md")" yes +expect copy-kept "$(yn test -f "$EXT/higgsfield-generate/new.md")" yes expect entries "$(entries "$EXT")" 3 verdict SYNC_FAIL_KEEPS_COPY +expect cli-ok "$(probe "$BIN:$PATH" higgsfield_cli_ok)" "rc=0" +expect signed-in "$(probe "$BIN:$PATH" higgsfield_signed_in)" "rc=0" +expect signed-out \ + "$(FAKE_HF_SESSION=out probe "$BIN:$PATH" higgsfield_signed_in)" "rc=2" +expect shim-only \ + "$(FAKE_HF_BINARY=missing probe "$BIN:$PATH" higgsfield_cli_ok)" "rc=1" +expect no-cli "$(probe "$CLEAN" higgsfield_cli_ok)" "rc=127" +expect no-timeout "$(probe "$BIN:$CLEAN" higgsfield_cli_ok)" "rc=0" +verdict PROBES_SILENT + # ── tally ─────────────────────────────────────────────────── printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ] ```` @@ -265,7 +336,7 @@ printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ] - [ ] **Step 2: Run it, confirm it fails** Run: `make test suite=lib/tests/higgsfield.test.sh` -Expected: four `FAIL SYNC_…` lines (the helper does not exist yet, so every sync answers `127:`), then `PASS=0 FAIL=4` and a non-zero make status. +Expected: five `FAIL` lines (`SYNC_MOVES_PACK_ONLY`, `SYNC_REFRESH_DROPS_STALE`, `SYNC_KEEPS_PARKED`, `SYNC_FAIL_KEEPS_COPY`, `PROBES_SILENT`: the helper does not exist yet, so every sync answers `127:`), then `PASS=0 FAIL=5` and a non-zero make status. - [ ] **Step 3: Write the helper** @@ -274,7 +345,7 @@ Run: `cp docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/02-higgsfield ````bash #!/usr/bin/env bash # ============================================================ -# lib/higgsfield-skills.sh — Higgsfield skill pack sync + session probe +# lib/higgsfield-skills.sh — Higgsfield skill pack sync + CLI probes # # Sourced by install-plugins.sh (Step 8.6), update-all.sh (7.3b) and # doctor.sh. The pack is machine-owned: cloned from upstream and moved @@ -288,49 +359,72 @@ Run: `cp docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/02-higgsfield HIGGSFIELD_SKILLS_URL="${HIGGSFIELD_SKILLS_URL:-\ https://github.com/higgsfield-ai/skills.git}" +# _higgsfield_adopt +# Move every real higgsfield-*/ directory of the clone that holds a SKILL.md +# over its copy in ; prints how many landed. A symlinked entry is +# skipped: only upstream's own directories are adopted. A skill counts only +# once its move succeeded. +_higgsfield_adopt() { + local clone="$1" dest="$2" dir name count=0 + for dir in "$clone"/higgsfield-*/; do + dir="${dir%/}" + { [ -f "$dir/SKILL.md" ] && [ ! -L "$dir" ]; } || continue + name="$(basename "$dir")" + rm -rf "${dest:?}/${name:?}" && mv "$dir" "$dest/$name" \ + && count=$((count + 1)) + done + echo "$count" +} + # higgsfield_sync_skills -# Clone upstream into a throwaway stage and replace each +# Clone upstream into a stage and replace each # /skills-external/higgsfield-* with the fresh copy; upstream's own -# machinery (setup, scripts/, plugin manifests, .git) is left in the stage. -# Prints the number of skills synced. Returns 1, existing copies untouched, -# when the clone fails or upstream holds no higgsfield-*/SKILL.md. A parked -# skill (skills-disabled/, a symlink to the source path) stays parked. +# machinery (setup, scripts/, plugin manifests, .git) stays in the stage. +# The stage sits next to the destination, on the same filesystem, so each +# replacement is a rename. Prints the number of skills synced. Returns 1, +# existing copies untouched, when the clone fails or upstream holds no +# higgsfield-*/SKILL.md. A parked skill (skills-disabled/, a symlink +# to the source path) stays parked. Known limit: a skill that upstream +# removes or renames keeps its last local copy. higgsfield_sync_skills() { - local repo="$1" stage dir name count=0 - stage="$(mktemp -d)" || return 1 - if git clone --quiet --depth 1 "$HIGGSFIELD_SKILLS_URL" "$stage/src" \ - >/dev/null 2>&1; then - mkdir -p "$repo/skills-external" - for dir in "$stage"/src/higgsfield-*/; do - [ -f "${dir}SKILL.md" ] || continue - name="$(basename "$dir")" - rm -rf "${repo:?}/skills-external/${name:?}" - mv "$dir" "$repo/skills-external/$name" - count=$((count + 1)) - done + local dest="$1/skills-external" stage count=0 + mkdir -p "$dest" || return 1 + stage="$(mktemp -d "$dest/.higgsfield-stage.XXXXXX")" || return 1 + # No credential prompt: a private or deleted upstream must fail, not hang. + if GIT_TERMINAL_PROMPT=0 git clone --quiet --depth 1 \ + "$HIGGSFIELD_SKILLS_URL" "$stage/src" >/dev/null 2>&1; then + count="$(_higgsfield_adopt "$stage/src" "$dest")" fi rm -rf "${stage:?}" echo "$count" [ "$count" -gt 0 ] } -# higgsfield_signed_in — 0 when the CLI holds a session. The CLI is closed -# source, so whether `auth token` stays local is unverified: bound it when -# `timeout` exists, feed it no stdin, and never let the token reach a -# terminal or a log. -higgsfield_signed_in() { +# _higgsfield_probe +# Run `higgsfield ` silently, 15 s at most when `timeout` exists. The +# CLI is closed source: a probe must never hang an installer, and what it +# prints (a token, for `auth token`) must never reach a terminal or a log. +_higgsfield_probe() { if command -v timeout >/dev/null 2>&1; then - timeout 15 higgsfield auth token /dev/null 2>&1 + timeout 15 higgsfield "$@" /dev/null 2>&1 else - higgsfield auth token /dev/null 2>&1 + higgsfield "$@" /dev/null 2>&1 fi } + +# higgsfield_cli_ok — 0 when the binary answers. `command -v` alone only +# proves the npm shim: the binary is vendored by a postinstall script that +# npm may hold back, on a first install or on any later update. +higgsfield_cli_ok() { _higgsfield_probe version; } + +# higgsfield_signed_in — 0 when the CLI holds a session. +higgsfield_signed_in() { _higgsfield_probe auth token; } ```` - [ ] **Step 4: Run the suite, confirm it passes** Run: `make test suite=lib/tests/higgsfield.test.sh` -Expected: `PASS SYNC_MOVES_PACK_ONLY`, `PASS SYNC_REFRESH_DROPS_STALE`, `PASS SYNC_KEEPS_PARKED`, `PASS SYNC_FAIL_KEEPS_COPY`, `PASS=4 FAIL=0`. +Expected: five `PASS` lines, `PASS=5 FAIL=0`. - [ ] **Step 5: Shellcheck** @@ -341,7 +435,7 @@ Expected: no output. ```bash git add lib/higgsfield-skills.sh lib/tests/higgsfield.test.sh -git commit -m "feat(higgsfield): skill pack sync helper with hermetic suite" +git commit -m "feat(higgsfield): skill pack sync helper and CLI probes, with suite" ``` --- @@ -350,15 +444,18 @@ git commit -m "feat(higgsfield): skill pack sync helper with hermetic suite" **Files:** - Modify: `lib/tests/higgsfield.test.sh` (toggle cases + `OFF_BY_DEFAULT_WIRING`, inserted above the tally) -- Modify: `lib/toggle-external.sh` (header, `MANAGED_TOOLS`, enumerator, dispatcher, pack arms, single-symlink arms, `usage` range) +- Modify: `lib/toggle-external.sh` (header, `MANAGED_TOOLS`, allowlist, enumerators, dispatcher, probe, hints, pack arms, single-symlink arms, `usage` range) **Interfaces:** - Consumes: the suite helpers of Task 2. - Produces, in `lib/toggle-external.sh`: - tools `higgsfield` and `higgsfield-websites` for `status | enable | disable | list`. - - `higgsfield_skills`: prints every `skills-external/higgsfield-*` holding a `SKILL.md`, minus `higgsfield-websites`. + - `HIGGSFIELD_MEDIA_SKILLS`: the seven allowlisted media skill names. + - `higgsfield_skills`: prints the allowlisted names synced under `skills-external/` (a `SKILL.md` present). + - `higgsfield_unlisted`: prints synced `higgsfield-*` skills that no tool owns. - `pack_skills `: prints the members of `21st` or `higgsfield`. - - `pack_cli_hint `: warns (never fails) when the pack's CLI is missing or signed out. + - `bounded `: silent CLI probe, 15 s at most when `timeout` exists. + - `pack_hints `: warns (never fails) when the pack's CLI is missing, does not answer, or is signed out; for `higgsfield`, also names each unlisted skill. Called after a pack enable and after `enable higgsfield-websites`. - Messages: ` enabled ( skills: restored, linked)`, ` disabled ( skills parked)`, ` already enabled`, ` already disabled`, ` pack not installed in /skills-external — run: make plugin` (LRN-007: the error names the path checked). - [ ] **Step 1: Add the failing cases** @@ -368,32 +465,11 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-sui ````diff --- a/lib/tests/higgsfield.test.sh +++ b/lib/tests/higgsfield.test.sh -@@ -110,5 +110,150 @@ - expect entries "$(entries "$EXT")" 3 - verdict SYNC_FAIL_KEEPS_COPY +@@ -164,5 +164,153 @@ + expect no-timeout "$(probe "$BIN:$CLEAN" higgsfield_cli_ok)" "rc=0" + verdict PROBES_SILENT +# ── toggle ────────────────────────────────────────────────── -+BIN="$WORK/bin"; mkdir -p "$BIN" -+cat > "$BIN/higgsfield" <<'EOF' -+#!/usr/bin/env bash -+# Fake CLI: `auth token` answers per $FAKE_HF_SESSION (in | out). -+[ "${1:-} ${2:-}" = "auth token" ] || exit 64 -+if [ "${FAKE_HF_SESSION:-in}" = in ]; then echo "fixture-token"; exit 0; fi -+echo "Error: Not authenticated." >&2; exit 2 -+EOF -+cat > "$BIN/21st" <<'EOF' -+#!/usr/bin/env bash -+[ "${1:-}" = whoami ] && echo "Logged in as fixture (saved in fixture)." -+EOF -+chmod +x "$BIN/higgsfield" "$BIN/21st" -+ -+# Precondition, loud: the CLI-absent case runs on a sanitized PATH that must -+# not resolve a real `higgsfield`. -+if PATH=/usr/bin:/bin command -v higgsfield >/dev/null 2>&1; then -+ echo "FAIL precondition: a system-wide higgsfield resolves on /usr/bin:/bin" -+ exit 1 -+fi -+ +# mk_toggle_fx [skill...] — fixture repo: the toggle script plus one +# skills-external source per named skill (none → installed-nothing tree). +mk_toggle_fx() { @@ -405,7 +481,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-sui + echo "---" > "$fx/skills-external/$s/SKILL.md" + done +} -+PACK=(higgsfield-alpha higgsfield-beta higgsfield-websites) ++PACK=(higgsfield-generate higgsfield-soul-id higgsfield-websites) + +# tog — run the fixture's toggle script, fake CLIs first. +tog() { @@ -422,7 +498,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-sui +expect web-missing "$(tog "$F0" status higgsfield-websites)" missing +expect pack-disabled "$(tog "$F1" status higgsfield)" disabled +expect web-disabled "$(tog "$F1" status higgsfield-websites)" disabled -+ln -s "$F1/skills-external/higgsfield-alpha" "$F1/skills/higgsfield-alpha" ++ln -s "$F1/skills-external/higgsfield-generate" "$F1/skills/higgsfield-generate" +expect pack-partial "$(tog "$F1" status higgsfield)" enabled +expect web-apart "$(tog "$F1" status higgsfield-websites)" disabled +expect list-pack "$(list_row "$F1" higgsfield)" enabled @@ -432,10 +508,10 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-sui +F2="$WORK/f2"; mk_toggle_fx "$F2" "${PACK[@]}" +out="$(tog "$F2" enable higgsfield)"; rc=$? +expect rc "$rc" 0 -+expect alpha "$(readlink "$F2/skills/higgsfield-alpha")" \ -+ "$F2/skills-external/higgsfield-alpha" -+expect beta "$(readlink "$F2/skills/higgsfield-beta")" \ -+ "$F2/skills-external/higgsfield-beta" ++expect generate "$(readlink "$F2/skills/higgsfield-generate")" \ ++ "$F2/skills-external/higgsfield-generate" ++expect soul-id "$(readlink "$F2/skills/higgsfield-soul-id")" \ ++ "$F2/skills-external/higgsfield-soul-id" +expect no-websites "$(yn test -e "$F2/skills/higgsfield-websites")" no +expect_has count "$out" "higgsfield enabled (2 skills: 0 restored, 2 linked)" +out="$(tog "$F2" enable higgsfield)"; rc=$? @@ -443,14 +519,33 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-sui +expect_has again "$out" "higgsfield already enabled" +verdict ENABLE_PACK_EXCLUDES_WEBSITES + ++# The media pack is an allowlist: a synced skill nobody listed is reported, ++# never linked; neither is a listed name whose directory holds no SKILL.md. ++F8="$WORK/f8"; mk_toggle_fx "$F8" "${PACK[@]}" higgsfield-newcomer ++mkdir -p "$F8/skills-external/higgsfield-brandkit" \ ++ "$F8/skills-external/higgsfield-noskill" ++out="$(tog "$F8" enable higgsfield)"; rc=$? ++expect rc "$rc" 0 ++expect_has count "$out" "higgsfield enabled (2 skills: 0 restored, 2 linked)" ++expect newcomer-off "$(yn test -e "$F8/skills/higgsfield-newcomer")" no ++expect brandkit-off "$(yn test -e "$F8/skills/higgsfield-brandkit")" no ++expect_has reported "$out" "higgsfield-newcomer" ++expect_not noskill-quiet "$out" "higgsfield-noskill" ++expect links "$(entries "$F8/skills")" 2 ++verdict UNLISTED_NOT_LINKED ++ +F3="$WORK/f3"; mk_toggle_fx "$F3" "${PACK[@]}" +out="$(tog "$F3" enable higgsfield-websites)"; rc=$? +expect rc "$rc" 0 +expect link "$(readlink "$F3/skills/higgsfield-websites")" \ + "$F3/skills-external/higgsfield-websites" -+expect no-alpha "$(yn test -e "$F3/skills/higgsfield-alpha")" no ++expect no-generate "$(yn test -e "$F3/skills/higgsfield-generate")" no +expect pack-status "$(tog "$F3" status higgsfield)" disabled +expect web-status "$(tog "$F3" status higgsfield-websites)" enabled ++tog "$F3" disable higgsfield-websites >/dev/null ++out="$(FAKE_HF_SESSION=out tog "$F3" enable higgsfield-websites)"; rc=$? ++expect hint-rc "$rc" 0 ++expect_has web-hint "$out" "higgsfield auth login" +verdict ENABLE_WEBSITES_ALONE + +# Continues on F2: the pack is enabled, websites is not. @@ -458,8 +553,8 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-sui +out="$(tog "$F2" disable higgsfield)"; rc=$? +expect rc "$rc" 0 +expect_has msg "$out" "higgsfield disabled (2 skills parked)" -+expect parked "$(yn test -L "$F2/skills-disabled/higgsfield-alpha")" yes -+expect unlinked "$(yn test -e "$F2/skills/higgsfield-alpha")" no ++expect parked "$(yn test -L "$F2/skills-disabled/higgsfield-generate")" yes ++expect unlinked "$(yn test -e "$F2/skills/higgsfield-generate")" no +expect web-untouched "$(yn test -e "$F2/skills/higgsfield-websites")" yes +out="$(tog "$F2" enable higgsfield)" +expect_has restored "$out" "2 restored, 0 linked" @@ -471,18 +566,23 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-sui +F4="$WORK/f4"; mk_toggle_fx "$F4" "${PACK[@]}" +out="$(FAKE_HF_SESSION=out tog "$F4" enable higgsfield)"; rc=$? +expect out-rc "$rc" 0 -+expect out-linked "$(yn test -e "$F4/skills/higgsfield-alpha")" yes ++expect out-linked "$(yn test -e "$F4/skills/higgsfield-generate")" yes +expect_has out-hint "$out" "higgsfield auth login" +F5="$WORK/f5"; mk_toggle_fx "$F5" "${PACK[@]}" +out="$(FAKE_HF_SESSION=in tog "$F5" enable higgsfield)" +expect_not in-quiet "$out" "auth login" +expect_not in-no-token "$out" "fixture-token" +F6="$WORK/f6"; mk_toggle_fx "$F6" "${PACK[@]}" -+out="$(TOGGLE_EXTERNAL_REPO_OVERRIDE="$F6" PATH=/usr/bin:/bin \ ++out="$(TOGGLE_EXTERNAL_REPO_OVERRIDE="$F6" PATH="$CLEAN" \ + bash "$F6/lib/toggle-external.sh" enable higgsfield 2>&1)"; rc=$? +expect absent-rc "$rc" 0 -+expect absent-linked "$(yn test -e "$F6/skills/higgsfield-alpha")" yes ++expect absent-linked "$(yn test -e "$F6/skills/higgsfield-generate")" yes +expect_has absent-hint "$out" "not on PATH" ++F9="$WORK/f9"; mk_toggle_fx "$F9" "${PACK[@]}" ++out="$(FAKE_HF_BINARY=missing tog "$F9" enable higgsfield)"; rc=$? ++expect shim-rc "$rc" 0 ++expect_has shim-hint "$out" "does not answer" ++expect_not shim-not-login "$out" "auth login" +verdict SIGNED_OUT_WARNS + +out="$(tog "$F0" enable higgsfield)"; rc=$? @@ -509,7 +609,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-sui +count() { grep -cF -- "$2" "$ROOT/$1"; } + +# Positive control first: the pattern does bite on a line that carries it. -+expect control "$(echo 'higgsfield-alpha external' | grep -cF higgsfield)" 1 ++expect control "$(echo 'higgsfield-x external' | grep -cF higgsfield)" 1 +expect link-sh "$(count link.sh higgsfield)" 0 +expect profile-sh "$(count lib/profile.sh higgsfield)" 0 +expect profiles \ @@ -524,7 +624,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-sui - [ ] **Step 2: Run, confirm the new cases fail** Run: `make test suite=lib/tests/higgsfield.test.sh` -Expected: `PASS=5 FAIL=7`. The four `SYNC_…` cases and `OFF_BY_DEFAULT_WIRING` PASS. `STATUS_STATES`, `ENABLE_PACK_EXCLUDES_WEBSITES`, `ENABLE_WEBSITES_ALONE`, `DISABLE_PARKS`, `SIGNED_OUT_WARNS`, `ENABLE_MISSING_ERRS` FAIL (the script answers `unknown` / `Unknown tool`), and `PACK_21ST_UNCHANGED` fails on `hf-apart` alone (`unknown` instead of `missing`). +Expected: `PASS=6 FAIL=8`. The five Task 2 cases and `OFF_BY_DEFAULT_WIRING` PASS. `STATUS_STATES`, `ENABLE_PACK_EXCLUDES_WEBSITES`, `UNLISTED_NOT_LINKED`, `ENABLE_WEBSITES_ALONE`, `DISABLE_PARKS`, `SIGNED_OUT_WARNS`, `ENABLE_MISSING_ERRS` FAIL (the script answers `unknown` / `Unknown tool`), and `PACK_21ST_UNCHANGED` fails on `hf-apart` alone (`unknown` instead of `missing`). - [ ] **Step 3: Apply the toggle patch** @@ -562,19 +662,39 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-tog 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 -@@ -69,6 +73,51 @@ +@@ -69,6 +73,87 @@ done } -+# Prints the skill names of the "higgsfield" media pack: every -+# skills-external/higgsfield-* synced by lib/higgsfield-skills.sh, minus -+# higgsfield-websites, which is its own tool (landing-page aid, named ask). ++# Media skills of the "higgsfield" pack: an explicit allowlist. Upstream is ++# unpinned, so a skill it adds or renames must never be linked by ++# `enable higgsfield` without an edit here (default deny). ++# higgsfield-websites is its own tool: landing-page aid, named ask only. ++HIGGSFIELD_MEDIA_SKILLS=(higgsfield-generate higgsfield-soul-id ++ higgsfield-product-photoshoot higgsfield-brandkit ++ higgsfield-marketplace-cards higgsfield-video-explainer ++ higgsfield-youtube-thumbnail) ++ ++# Prints the allowlisted media skills synced under skills-external/. +higgsfield_skills() { -+ local d ++ local name ++ for name in "${HIGGSFIELD_MEDIA_SKILLS[@]}"; do ++ [ -f "$REPO/skills-external/$name/SKILL.md" ] && echo "$name" ++ done ++ return 0 ++} ++ ++# Prints the synced higgsfield-* skills no tool owns: neither on the media ++# allowlist nor higgsfield-websites. Upstream added or renamed something. ++higgsfield_unlisted() { ++ local d name + for d in "$REPO"/skills-external/higgsfield-*/; do + [ -f "${d}SKILL.md" ] || continue -+ [ "$(basename "$d")" = "higgsfield-websites" ] && continue -+ basename "$d" ++ name="$(basename "$d")" ++ case " ${HIGGSFIELD_MEDIA_SKILLS[*]} higgsfield-websites " in ++ *" $name "*) ;; ++ *) echo "$name" ;; ++ esac + done +} + @@ -586,10 +706,24 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-tog + esac +} + -+# The pack skills shell out to their CLI; without it (or without a session) -+# they can only report failure. Warn, never block: the pack is still -+# correctly wired and `make plugin` installs the CLI. -+pack_cli_hint() { ++# bounded — run a CLI probe silently, 15 s at most when `timeout` ++# exists: a closed-source binary must never hang a toggle, and what it ++# prints (a token) must never reach the terminal. Twin of ++# _higgsfield_probe in lib/higgsfield-skills.sh, kept here because this ++# script takes no extra `source` (the fixture suites copy it alone). ++bounded() { ++ if command -v timeout >/dev/null 2>&1; then ++ timeout 15 "$@" /dev/null 2>&1 ++ else ++ "$@" /dev/null 2>&1 ++ fi ++} ++ ++# Post-enable notes for a pack. Its skills shell out to a CLI: without it ++# (or without a session) they can only report failure. Warn, never block: ++# the pack is still correctly wired and `make plugin` installs the CLI. ++pack_hints() { ++ local name + case "$1" in + 21st) + if ! command -v 21st >/dev/null 2>&1; then @@ -599,14 +733,16 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-tog + fi + ;; + higgsfield) -+ # Same probe as higgsfield_signed_in (lib/higgsfield-skills.sh), -+ # inlined: this script takes no extra `source`, the fixture suites -+ # copy it alone. The token goes to /dev/null, never to the terminal. + if ! command -v higgsfield >/dev/null 2>&1; then + warn "the \`higgsfield\` CLI is not on PATH — run: make plugin" -+ elif ! higgsfield auth token /dev/null 2>&1; then ++ elif ! bounded higgsfield version; then ++ warn "the \`higgsfield\` CLI does not answer (npm shim without its binary) — run: make plugin" ++ elif ! bounded higgsfield auth token; then + warn "not signed in to Higgsfield — generation needs: higgsfield auth login" + fi ++ while read -r name; do ++ warn "$name is synced but on no allowlist, not linked — see HIGGSFIELD_MEDIA_SKILLS in lib/toggle-external.sh" ++ done < <(higgsfield_unlisted) + ;; + esac +} @@ -614,7 +750,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-tog # Prints the names (directory basenames) that belong to "gstack". # Source of truth: skills-external/gstack/*/SKILL.md. The repo's # skills/ symlinks are generated from these by gstack ./setup. -@@ -94,7 +143,7 @@ +@@ -94,7 +179,7 @@ ;; emil-design-eng|observability-and-instrumentation|deprecation-and-migration|ci-cd-and-automation| \ scroll-world-storytelling|build-threejs-scroll-worlds|scroll-scrubbed-visual-sequence| \ @@ -623,7 +759,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-tog [ -d "$REPO/skills-external/$tool" ] || { echo "missing"; return; } [ -e "$SKILLS_DIR/$tool" ] && echo "enabled" || echo "disabled" ;; -@@ -102,12 +151,12 @@ +@@ -102,12 +187,12 @@ [ -d "$HOME/.agents/skills/$tool" ] || { echo "missing"; return; } [ -e "$SKILLS_DIR/$tool" ] && echo "enabled" || echo "disabled" ;; @@ -638,7 +774,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-tog [ "$installed" -eq 1 ] && echo "disabled" || echo "missing" ;; *) -@@ -135,7 +184,8 @@ +@@ -135,7 +220,8 @@ ;; emil-design-eng|darwin-skill|observability-and-instrumentation|deprecation-and-migration| \ ci-cd-and-automation|scroll-world-storytelling|build-threejs-scroll-worlds| \ @@ -648,7 +784,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-tog if [ -e "$SKILLS_DIR/$tool" ]; then rm -rf "${DISABLED_DIR:?}/${tool:?}" mv "$SKILLS_DIR/$tool" "$DISABLED_DIR/$tool" -@@ -144,7 +194,7 @@ +@@ -144,7 +230,7 @@ warn "$tool already disabled" fi ;; @@ -657,7 +793,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-tog # Parked under the plain skill name — same convention as the other # externals, so profile.sh's park/restore path stays interoperable. local parked=0 -@@ -153,11 +203,11 @@ +@@ -153,11 +239,11 @@ rm -rf "${DISABLED_DIR:?}/${name:?}" mv "$SKILLS_DIR/$name" "$DISABLED_DIR/$name" parked=$((parked + 1)) @@ -672,7 +808,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-tog fi ;; *) err "Unknown tool: $tool"; return 1 ;; -@@ -194,7 +244,8 @@ +@@ -194,7 +280,8 @@ ;; emil-design-eng|darwin-skill|observability-and-instrumentation|deprecation-and-migration| \ ci-cd-and-automation|scroll-world-storytelling|build-threejs-scroll-worlds| \ @@ -682,16 +818,18 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-tog local src case "$tool" in darwin-skill) src="$HOME/.agents/skills/$tool" ;; -@@ -214,7 +265,7 @@ +@@ -213,8 +300,9 @@ + err "$tool not installed at $src — run: make plugin" return 1 fi ++ if [ "$tool" = "higgsfield-websites" ]; then pack_hints higgsfield; fi ;; - 21st) + 21st|higgsfield) local restored=0 linked=0 while read -r name; do if [ -e "$DISABLED_DIR/$name" ]; then -@@ -227,24 +278,17 @@ +@@ -227,24 +315,17 @@ ln -sf "$REPO/skills-external/$name" "$SKILLS_DIR/$name" linked=$((linked + 1)) fi @@ -718,11 +856,11 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-tog - warn "not signed in to 21st — component retrieval and 21st AI need: 21st login" - fi + ok "$tool enabled ($((restored + linked)) skills: $restored restored, $linked linked)" -+ pack_cli_hint "$tool" ++ pack_hints "$tool" ;; *) err "Unknown tool: $tool"; return 1 ;; esac -@@ -259,7 +303,7 @@ +@@ -259,7 +340,7 @@ } usage() { @@ -743,7 +881,7 @@ make test suite=lib/tests/gstack-removed.test.sh make test suite=lib/tests/profile-set-managed.test.sh make test suite=lib/tests/profile-default.test.sh ``` -Expected: `PASS=12 FAIL=0` for the Higgsfield suite, and each of the four others ends green (make status 0). +Expected: `PASS=14 FAIL=0` for the Higgsfield suite, and each of the four others ends green (make status 0). - [ ] **Step 5: Shellcheck and the help text** @@ -766,8 +904,8 @@ git commit -m "feat(toggle): higgsfield and higgsfield-websites toggles" - Modify: `install-plugins.sh` (Step 6 login test, new Step 8.6 before Step 8.7, Step 8.7 login test, one summary line) **Interfaces:** -- Consumes: `higgsfield_sync_skills`, `higgsfield_signed_in`, `HIGGSFIELD_SKILLS_URL` (Task 2); lock key `higgsfield` through the existing `pinned_version` (Task 1); the installer's `ok | info | warn | err` helpers and `$REPO`. -- Produces: Step 8.6 (CLI install, skill sync, login offer on a terminal stdin); login offers of Steps 6 and 8.7 reachable under the `tee` redirect (`[ -t 0 ]` alone, BDR-093 TTY-only login kept). +- Consumes: `higgsfield_sync_skills`, `higgsfield_cli_ok`, `higgsfield_signed_in`, `HIGGSFIELD_SKILLS_URL` (Task 2); lock key `higgsfield` through the existing `pinned_version` (Task 1); the installer's `ok | info | warn | err` helpers and `$REPO`. +- Produces: Step 8.6 (CLI install proven by the probe, skill sync, login offer on a terminal stdin); login offers of Steps 6 and 8.7 reachable under the `tee` redirect (`[ -t 0 ]` alone, BDR-093 TTY-only login kept). - [ ] **Step 1: Add the failing case** @@ -776,7 +914,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-sui ````diff --- a/lib/tests/higgsfield.test.sh +++ b/lib/tests/higgsfield.test.sh -@@ -255,5 +255,29 @@ +@@ -312,5 +312,29 @@ expect pins-map "$(count lib/effort-pins.txt higgsfield)" 0 verdict OFF_BY_DEFAULT_WIRING @@ -787,7 +925,8 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-sui +PINS='apply_effort_pins "$REPO"' + +# install-plugins.sh: the sync sits in Step 8.6, before the effort pins -+# (BDR-108), and every login offer tests stdin alone (stdout is the tee pipe). ++# (BDR-108); the CLI is proven by a probe, not by its shim; every login ++# offer tests stdin alone (stdout is the tee pipe). +sync_ln="$(ln_last install-plugins.sh 'higgsfield_sync_skills')" +expect after-8.5 "$(yn test "$sync_ln" -gt \ + "$(ln_first install-plugins.sh 'Step 8.5: External skills')")" yes @@ -795,13 +934,12 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-sui + "$(ln_first install-plugins.sh 'Step 8.7: 21st.dev')")" yes +expect before-pins "$(yn test "$sync_ln" -lt \ + "$(ln_last install-plugins.sh "$PINS")")" yes ++expect probe-gates \ ++ "$(yn test "$(count install-plugins.sh 'if higgsfield_cli_ok')" -ge 3)" yes +expect control "$(echo 'if [ -t 0 ] && [ -t 1 ]; then' | grep -cF -- '-t 1')" 1 +expect no-stdout-test "$(count install-plugins.sh '-t 1')" 0 +expect stdin-tests \ + "$(yn test "$(count install-plugins.sh '[ -t 0 ]')" -ge 3)" yes -+expect summary "$(sed -n '/Install Summary/,$p' "$ROOT/install-plugins.sh" \ -+ | grep -cF 'enable higgsfield')" 1 -+expect remedy "$(count install-plugins.sh '--allow-scripts=')" 1 +verdict INSTALL_WIRING + # ── tally ─────────────────────────────────────────────────── @@ -811,7 +949,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-sui - [ ] **Step 2: Run, confirm it fails** Run: `make test suite=lib/tests/higgsfield.test.sh` -Expected: `FAIL INSTALL_WIRING:` listing `after-8.5`, `before-8.7`, `before-pins`, `no-stdout-test`, `stdin-tests`, `summary`, `remedy`; the 12 earlier cases PASS. +Expected: `PASS=14 FAIL=1`, the failure being `FAIL INSTALL_WIRING:` listing `after-8.5`, `before-8.7`, `before-pins`, `probe-gates`, `no-stdout-test`, `stdin-tests`. - [ ] **Step 3: Apply the installer patch** @@ -838,7 +976,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-ins # Interactive terminal: offer to log in now (opens a browser). printf '%b' "${BLUE}→${NC} Authenticate ctx7 now for higher rate limits? [y/N] " read -r ctx7_ans || ctx7_ans="" -@@ -991,6 +993,78 @@ +@@ -991,6 +993,76 @@ echo "" # ============================================================ @@ -860,34 +998,32 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-ins +# shellcheck source=lib/higgsfield-skills.sh disable=SC1091 +source "$REPO/lib/higgsfield-skills.sh" +HF_PKG="@higgsfield/cli" -+if command -v higgsfield &>/dev/null; then ++# The package vendors its binary in a postinstall script that npm may hold ++# back; this form lets that one script run. ++HF_REMEDY="npm install -g --allow-scripts=${HF_PKG} ${HF_PKG}" ++ ++# higgsfield_cli_ok, not `command -v`: the npm shim can sit on PATH with no ++# binary behind it, and only a probe tells the two apart. ++if higgsfield_cli_ok; then + ok "Higgsfield CLI already installed" +else + HF_VER=$(pinned_version "higgsfield") -+ if [ "$HF_VER" != "latest" ]; then -+ info "Installing ${HF_PKG}@${HF_VER} (pinned in plugins.lock.json)..." -+ npm install -g "${HF_PKG}@${HF_VER}" || true -+ else -+ info "Installing ${HF_PKG}@latest (consider pinning in plugins.lock.json)..." -+ npm install -g "$HF_PKG" || true -+ fi -+ # The package vendors its binary in a postinstall script: `version` proves -+ # the binary landed, `command -v` alone only proves the JS shim. -+ if higgsfield version &>/dev/null; then ++ [ "$HF_VER" = "latest" ] || HF_PKG="${HF_PKG}@${HF_VER}" ++ info "Installing ${HF_PKG} (version from plugins.lock.json: ${HF_VER})..." ++ npm install -g "$HF_PKG" || true ++ if higgsfield_cli_ok; then + ok "Higgsfield CLI installed" + else -+ err "Higgsfield CLI install failed — run manually: npm install -g --allow-scripts=${HF_PKG} ${HF_PKG}" ++ err "Higgsfield CLI install failed — run manually: $HF_REMEDY" + fi +fi + -+if command -v higgsfield &>/dev/null; then ++if higgsfield_cli_ok; then + # Skill pack — cloned to a stage, then moved under skills-external/. + if HF_N=$(higgsfield_sync_skills "$REPO"); then + ok "Higgsfield skill pack synced to skills-external/ ($HF_N skills)" -+ elif [ -f "$REPO/skills-external/higgsfield-generate/SKILL.md" ]; then -+ ok "Higgsfield skill pack already present (refresh failed — existing copy kept)" + else -+ warn "Higgsfield skill pack sync failed — check: git clone $HIGGSFIELD_SKILLS_URL" ++ warn "Higgsfield skill pack sync failed — existing copies kept (check: git clone $HIGGSFIELD_SKILLS_URL)" + fi + + # Auth — offer the login only when stdin is a terminal: a non-interactive @@ -917,7 +1053,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-ins # STEP 8.7 — 21ST.DEV CLI + SKILL PACK # ============================================================ # `@21st-dev/cli` (bin `21st`): one browser login (`21st login`, token in -@@ -1065,13 +1139,13 @@ +@@ -1065,13 +1137,13 @@ # 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 @@ -933,7 +1069,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-ins printf '%b' "${BLUE}→${NC} Sign in to 21st now? (opens a browser) [y/N] " read -r tfd_ans || tfd_ans="" if [[ "$tfd_ans" =~ ^[Yy]([Ee][Ss])?$ ]]; then -@@ -1236,6 +1310,7 @@ +@@ -1236,6 +1308,7 @@ echo " 🔄 mengto scroll skills — scroll-world-storytelling, build-threejs-scroll-worlds, scroll-scrubbed-visual-sequence, scroll-scrubbed-word-reveal, scroll-progress-timeline (curl → symlink, pinned commit)" echo " 🔄 darwin-skill — autonomous skill optimizer (npx skills, ~/.agents/skills/)" echo " 🔄 21st skill pack — 21st.dev CLI skills; design ones follow the profile (full by default), publishing ones on demand (toggle: lib/toggle-external.sh enable 21st)" @@ -950,7 +1086,7 @@ make test suite=lib/tests/higgsfield.test.sh make test suite=lib/tests/effort-routing.test.sh make test suite=lib/tests/curated-config-guard.test.sh ``` -Expected: `PASS=13 FAIL=0`; the two others green. +Expected: `PASS=15 FAIL=0`; the two others green. - [ ] **Step 5: Shellcheck and syntax** @@ -973,8 +1109,8 @@ git commit -m "feat(install): Higgsfield step 8.6; login offers test stdin alone - Modify: `update-all.sh` (new block 7.3b before 7.4) **Interfaces:** -- Consumes: `higgsfield_sync_skills` (Task 2); lock key `higgsfield` (Task 1); the script's `ok | warn | info` helpers and `$REPO`. -- Produces: block 7.3b. It skips when the CLI is absent, replaces only `skills-external/` sources (a parked pack stays parked), and runs before the effort-pins re-apply (BDR-108). +- Consumes: `higgsfield_sync_skills`, `higgsfield_cli_ok` (Task 2); lock key `higgsfield` (Task 1); the script's `ok | warn | info` helpers and `$REPO`. +- Produces: block 7.3b. It skips when the CLI is absent, proves the updated CLI with the probe (a shim left without its binary gets a warning and the remedy), replaces only `skills-external/` sources (a parked pack stays parked), and runs before the effort-pins re-apply (BDR-108). - [ ] **Step 1: Add the failing case** @@ -983,19 +1119,19 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-sui ````diff --- a/lib/tests/higgsfield.test.sh +++ b/lib/tests/higgsfield.test.sh -@@ -279,5 +279,16 @@ - expect remedy "$(count install-plugins.sh '--allow-scripts=')" 1 +@@ -336,5 +336,16 @@ + "$(yn test "$(count install-plugins.sh '[ -t 0 ]')" -ge 3)" yes verdict INSTALL_WIRING +# update-all.sh: refresh before the 21st block and before the pins re-apply, -+# skipped when the CLI is absent. ++# and the updated CLI is proven by the probe. +sync_ln="$(ln_last update-all.sh 'higgsfield_sync_skills')" +expect before-21st "$(yn test "$sync_ln" -lt \ + "$(ln_first update-all.sh '7.4. Update the 21st.dev')")" yes +expect before-pins "$(yn test "$sync_ln" -lt \ + "$(ln_last update-all.sh "$PINS")")" yes -+expect skip-no-cli \ -+ "$(count update-all.sh 'Higgsfield CLI not installed — skipping')" 1 ++expect probe-after-npm \ ++ "$(yn test "$(count update-all.sh 'if higgsfield_cli_ok')" -ge 1)" yes +verdict UPDATE_WIRING + # ── tally ─────────────────────────────────────────────────── @@ -1005,7 +1141,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-sui - [ ] **Step 2: Run, confirm it fails** Run: `make test suite=lib/tests/higgsfield.test.sh` -Expected: `FAIL UPDATE_WIRING:` listing `before-21st`, `before-pins`, `skip-no-cli`. +Expected: `PASS=15 FAIL=1`, the failure being `FAIL UPDATE_WIRING:` listing `before-21st`, `before-pins`, `probe-after-npm`. - [ ] **Step 3: Apply the updater patch** @@ -1014,7 +1150,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-upd ````diff --- a/update-all.sh +++ b/update-all.sh -@@ -465,6 +465,41 @@ +@@ -465,6 +465,45 @@ fi fi @@ -1028,6 +1164,8 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-upd +if ! command -v higgsfield &>/dev/null; then + info "Higgsfield CLI not installed — skipping (run: make plugin)" +else ++ # shellcheck source=lib/higgsfield-skills.sh disable=SC1091 ++ source "$REPO/lib/higgsfield-skills.sh" + HF_VER="" + if [ -f "$REPO/plugins.lock.json" ] && command -v python3 &>/dev/null; then + HF_VER=$(python3 -c " @@ -1038,14 +1176,16 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-upd +" 2>/dev/null || true) + fi + HF_PKG="@higgsfield/cli@latest" -+ [ -n "$HF_VER" ] && [ "$HF_VER" != "latest" ] && HF_PKG="@higgsfield/cli@${HF_VER}" -+ if npm install -g "$HF_PKG" 2>/dev/null; then ++ [ -n "$HF_VER" ] && [ "$HF_VER" != "latest" ] \ ++ && HF_PKG="@higgsfield/cli@${HF_VER}" ++ npm install -g "$HF_PKG" 2>/dev/null || true ++ # The probe, not npm's exit status: an update that skips the package's ++ # postinstall script leaves the shim on PATH with no binary behind it. ++ if higgsfield_cli_ok; then + ok "Higgsfield CLI updated (${HF_VER:-latest})" + else -+ warn "Higgsfield CLI update failed — existing binary kept" ++ warn "Higgsfield CLI does not answer after the update — run: npm install -g --allow-scripts=@higgsfield/cli @higgsfield/cli" + fi -+ # shellcheck source=lib/higgsfield-skills.sh disable=SC1091 -+ source "$REPO/lib/higgsfield-skills.sh" + if HF_N=$(higgsfield_sync_skills "$REPO"); then + ok "Higgsfield skill pack refreshed ($HF_N skills)" + else @@ -1064,7 +1204,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-upd make test suite=lib/tests/higgsfield.test.sh make test suite=lib/tests/effort-routing.test.sh ``` -Expected: `PASS=14 FAIL=0`; effort-routing green. +Expected: `PASS=16 FAIL=0`; effort-routing green. - [ ] **Step 5: Shellcheck and syntax** @@ -1086,8 +1226,8 @@ git commit -m "feat(update): refresh the Higgsfield CLI and skill pack" - Modify: `doctor.sh` (one `source` next to the others, one block in section 4 after the Graphifyy check) **Interfaces:** -- Consumes: `higgsfield_signed_in` (Task 2); the script's `pass | info` helpers. -- Produces: `Higgsfield CLI installed ()` + `Higgsfield session active`, or the `info` fallbacks. Never `warn`, never `fail`: the tool is optional and off by default. Every probe sits inside an `if`, so `set -e` cannot trip. +- Consumes: `higgsfield_cli_ok`, `higgsfield_signed_in` (Task 2); the script's `pass | info` helpers. +- Produces: `Higgsfield CLI installed ()` + `Higgsfield session active`, or the `info` fallbacks (shim without binary, not installed, not signed in). Never `warn`, never `fail`: the tool is optional and off by default. Every probe sits inside an `if`, so `set -e` cannot trip. - [ ] **Step 1: Apply the doctor patch** @@ -1105,18 +1245,22 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/06-doc echo "" echo "═══ claude-config doctor (v${VERSION}) ═══" -@@ -246,6 +248,18 @@ +@@ -246,6 +248,22 @@ info "Graphifyy not installed (optional — codebase knowledge graph: pipx install graphifyy)" fi +# Higgsfield is optional and off by default: info level, never a warning. -+if command -v higgsfield >/dev/null 2>&1; then -+ pass "Higgsfield CLI installed ($(higgsfield version 2>/dev/null | awk 'NR==1 {print $2}'))" ++# The probe, not `command -v`: the npm shim can outlive its binary. ++if higgsfield_cli_ok; then ++ HF_VERSION="$(higgsfield version 2>/dev/null | awk 'NR==1 {print $2}')" ++ pass "Higgsfield CLI installed (${HF_VERSION:-version unknown})" + if higgsfield_signed_in; then + pass "Higgsfield session active" + else + info "Higgsfield not signed in (generation needs: higgsfield auth login)" + fi ++elif command -v higgsfield >/dev/null 2>&1; then ++ info "Higgsfield CLI on PATH but its binary does not answer (run: npm install -g --allow-scripts=@higgsfield/cli @higgsfield/cli)" +else + info "Higgsfield CLI not installed (optional — media generation: make plugin)" +fi @@ -1161,7 +1305,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/07-rea ````diff --- a/README.md +++ b/README.md -@@ -348,6 +348,46 @@ +@@ -348,6 +348,55 @@ auto-approving with no prompt raised (LRN-153), so an `ask` entry would have declared an intent without gating anything. @@ -1177,24 +1321,29 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/07-rea +``` + +`make plugin` does both (Step 8.6 installs the CLI, then offers the login in -+an interactive terminal) and clones the eight skills of ++an interactive terminal) and clones the skills of +[higgsfield-ai/skills](https://github.com/higgsfield-ai/skills) into +`skills-external/higgsfield-*`. `make update` refreshes the CLI and the +skills, and `make doctor` reports the CLI and its session. The copies are +machine-owned and gitignored. They follow upstream `main`, so a prompt -+change arrives with no diff to review. ++change arrives with no diff to review, and a skill that upstream removes ++keeps its last local copy. + +The pack is off by default and belongs to no profile. It costs nothing until +you ask for it, and no `profile set` touches it: + +```bash -+bash lib/toggle-external.sh enable higgsfield # 7 media skills ++bash lib/toggle-external.sh enable higgsfield # media skills +bash lib/toggle-external.sh enable higgsfield-websites # landing-page aid +bash lib/toggle-external.sh disable higgsfield +``` + -+`higgsfield` turns on generate, soul-id, product-photoshoot, brandkit, -+marketplace-cards, video-explainer and youtube-thumbnail. ++`higgsfield` links a fixed list of seven media skills: generate, soul-id, ++product-photoshoot, brandkit, marketplace-cards, video-explainer and ++youtube-thumbnail. The list is `HIGGSFIELD_MEDIA_SKILLS` in ++`lib/toggle-external.sh`. A skill that upstream adds later is synced and ++reported, and stays unlinked until it is added there. ++ +`higgsfield-websites` is kept apart. Here it helps with landing pages inside +the design stack (assets, references), and `higgsfield website +create|deploy|publish` stays unused. Claude enables either toggle itself on @@ -1202,8 +1351,12 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/07-rea +with `higgsfield generate cost` before a paid run. + +The skills are cloned, not installed with `npx skills add`: that installer -+links all eight skills into `~/.claude/skills` on every refresh, which would -+undo the off-by-default state. ++links every skill into `~/.claude/skills` on each refresh, which would undo ++the off-by-default state. ++ ++The package ships its binary through a postinstall script. If npm holds that ++script back, `higgsfield` exists on PATH and fails at once; reinstall with ++`npm install -g --allow-scripts=@higgsfield/cli @higgsfield/cli`. + --- @@ -1221,7 +1374,7 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/07-cha ## [Unreleased] ### Added -+- **Higgsfield pack, off by default**: `make plugin` installs the `@higgsfield/cli` CLI (Step 8.6) and clones the eight skills of higgsfield-ai/skills into `skills-external/higgsfield-*` through the new `lib/higgsfield-skills.sh`; `make update` refreshes both; `make doctor` reports the CLI and its session at info level. The pack belongs to no profile: `lib/toggle-external.sh enable higgsfield` links the seven media skills, `enable higgsfield-websites` the landing-page aid, and no `profile set` or `make link` re-enables either. `CLAUDE.global.md` routes explicit media-generation asks to it. Hermetic suite `lib/tests/higgsfield.test.sh`. ++- **Higgsfield pack, off by default**: `make plugin` installs the `@higgsfield/cli` CLI (Step 8.6) and clones the skills of higgsfield-ai/skills into `skills-external/higgsfield-*` through the new `lib/higgsfield-skills.sh`; `make update` refreshes both; `make doctor` reports the CLI and its session at info level. The pack belongs to no profile: `lib/toggle-external.sh enable higgsfield` links the seven allowlisted media skills, `enable higgsfield-websites` the landing-page aid, and no `profile set` or `make link` re-enables either. `CLAUDE.global.md` routes explicit media-generation asks to it. Hermetic suite `lib/tests/higgsfield.test.sh`. - **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**: @@ -1265,7 +1418,7 @@ git commit -m "docs: Higgsfield pack in README and CHANGELOG" ### Task 8 (orchestrator only): Routing lines in CLAUDE.global.md **Files:** -- Modify: `CLAUDE.global.md` (Skill routing, 8 lines after the SEO line; 306 → 314 lines, guard 320: BDR-062, BDR-098) +- Modify: `CLAUDE.global.md` (Skill routing, 6 lines after the SEO line; 306 → 312 lines, guard 320: BDR-062, BDR-098) Hand edit of a guarded config (BDR-028). Not dispatched. @@ -1276,18 +1429,16 @@ Run: `git apply docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/08-cla ````diff --- a/CLAUDE.global.md +++ b/CLAUDE.global.md -@@ -266,6 +266,14 @@ +@@ -266,6 +266,12 @@ verification-before-completion → the verifier gates - SEO+GEO → seo (GEO only → geo); W3C + WCAG a11y → web-validate; security audit (secrets, CVE, OWASP) → cso +- Media generation (image, video, audio, brand kit), explicit ask → + Higgsfield pack, off by default: `bash ~/.claude/lib/toggle-external.sh -+ enable higgsfield`, then its skill (not listed yet → Read its SKILL.md -+ under `~/.claude/skills/`). Metered: `higgsfield generate cost` before -+ a paid run. Landing page "with Higgsfield", named ask only → `enable -+ higgsfield-websites` as an aid (assets, references) inside the Design -+ work stack and the site rules above; never `higgsfield website -+ create|deploy|publish`. ++ enable higgsfield`, then Read the skill under `~/.claude/skills/`; ++ `higgsfield generate cost` before a paid run. Landing page "with ++ Higgsfield", named ask → `enable higgsfield-websites`: an aid inside ++ Design work and the site rules, never `website create|deploy|publish`. gstack OFF → its skills (investigate, qa, review, health, retro, office-hours…) are gone: use the fallback above, else say so. @@ -1315,10 +1466,11 @@ git commit -m "docs(global): route media generation to the Higgsfield pack" - [ ] **Step 1: First sync (network)** +Run in the repo root, in the Bash tool's own shell (no `bash -c`, which the settings ask about): ```bash -bash -c 'source lib/higgsfield-skills.sh; higgsfield_sync_skills "$PWD"' +source lib/higgsfield-skills.sh && higgsfield_sync_skills "$PWD" ``` -Expected: `8`. +Expected: `8`. Upstream was read at f83af0b on 2026-09-30: no SKILL.md references a file outside its own folder, so dropping upstream's root `setup` and `scripts/` loses nothing. - [ ] **Step 2: Enable the media pack** @@ -1326,14 +1478,17 @@ Expected: `8`. bash lib/toggle-external.sh enable higgsfield bash lib/toggle-external.sh status higgsfield-websites ``` -Expected: `higgsfield enabled (7 skills: 0 restored, 7 linked)`, no sign-in warning; then `disabled`. +Expected: `higgsfield enabled (7 skills: 0 restored, 7 linked)`, no warning (CLI answers, session active, no unlisted skill); then `disabled`. -- [ ] **Step 3: Tree stays clean** +- [ ] **Step 3: Tree stays clean, routing census stays green** -Run: `git status --short` -Expected: only `.claude/` paths (the pack is gitignored on both sides). +```bash +git status --short +make test suite=lib/tests/skill-routing-census.test.sh +``` +Expected: only `.claude/` paths in the status (the pack is gitignored on both sides); the census green with the seven descriptions now in the live catalog (simulated at 0.50 max before the change). -- [ ] **Step 4: Full suite and gates** +- [ ] **Step 4: Full suite, doctor, gates** ```bash make test diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/01-gitignore.patch b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/01-gitignore.patch index 512a79d..36e9522 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/01-gitignore.patch +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/01-gitignore.patch @@ -1,26 +1,27 @@ --- a/.gitignore +++ b/.gitignore -@@ -101,6 +101,12 @@ +@@ -101,6 +101,11 @@ # membership, so the pack can gain a skill with no edit here. skills/21st-* +# Higgsfield skill pack symlinks — created on demand by toggle-external.sh +# (`enable higgsfield` / `enable higgsfield-websites`). The pack is OFF by -+# default and in no profile, so these usually don't exist. A glob: the -+# upstream repo owns the membership. ++# default and in no profile, so these usually don't exist. +skills/higgsfield-* + # Context7 docs-lookup skill — installed by `ctx7 setup --claude --cli` # (install-plugins.sh Step 6, when absent) into ~/.claude/skills (a symlink to # this repo's skills/). ctx7-managed and re-created on demand — not vendored here. -@@ -236,6 +242,11 @@ +@@ -236,6 +241,13 @@ # layout and the content is sha256-verified against 21st.dev's manifest. skills-external/21st-*/ +# Higgsfield skill pack — machine-owned: a git clone of higgsfield-ai/skills, +# staged by lib/higgsfield-skills.sh (install-plugins.sh Step 8.6) and moved +# here, refreshed by update-all.sh. Not vendored: it tracks upstream main. ++# The second line is the helper's stage, left behind only by a killed run. +skills-external/higgsfield-*/ ++skills-external/.higgsfield-stage.*/ + # npx `skills add` project-scope artifacts — darwin-skill copies itself into # the repo's .agents/ and writes skills-lock.json at root. Our own agents live diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/01-lock.patch b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/01-lock.patch index 6807a9b..ccd2287 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/01-lock.patch +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/01-lock.patch @@ -7,7 +7,7 @@ + "higgsfield": { + "source": "npm:@higgsfield/cli", + "version": "latest", -+ "note": "Higgsfield CLI (bins `higgsfield`, `higgs`) — image, video, audio and brand media generation, metered credits; auth is `higgsfield auth login` (browser). Install: npm install -g @higgsfield/cli. The package vendors its binary in a postinstall script; if npm holds it back, add --allow-scripts=@higgsfield/cli. The 8 skills are git-cloned from https://github.com/higgsfield-ai/skills (tracks main, no pin) into skills-external/higgsfield-* by lib/higgsfield-skills.sh (install-plugins.sh Step 8.6, refreshed by update-all.sh). OFF by default and in no profile: `lib/toggle-external.sh enable higgsfield` links the 7 media skills, `enable higgsfield-websites` the landing-page aid." ++ "note": "Higgsfield CLI (bins `higgsfield`, `higgs`) — image, video, audio and brand media generation, metered credits; auth is `higgsfield auth login` (browser). Install: npm install -g @higgsfield/cli. The package vendors its binary in a postinstall script; if npm holds it back, add --allow-scripts=@higgsfield/cli. The upstream skills are git-cloned from https://github.com/higgsfield-ai/skills (tracks main, no pin) into skills-external/higgsfield-* by lib/higgsfield-skills.sh (install-plugins.sh Step 8.6, refreshed by update-all.sh); a skill upstream removes keeps its last local copy. OFF by default and in no profile: `lib/toggle-external.sh enable higgsfield` links the 7 allowlisted media skills (HIGGSFIELD_MEDIA_SKILLS), `enable higgsfield-websites` the landing-page aid." + }, "graphifyy": { "source": "pypi:graphifyy", diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/02-higgsfield-skills.sh b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/02-higgsfield-skills.sh index 114c997..95f03dc 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/02-higgsfield-skills.sh +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/02-higgsfield-skills.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # ============================================================ -# lib/higgsfield-skills.sh — Higgsfield skill pack sync + session probe +# lib/higgsfield-skills.sh — Higgsfield skill pack sync + CLI probes # # Sourced by install-plugins.sh (Step 8.6), update-all.sh (7.3b) and # doctor.sh. The pack is machine-owned: cloned from upstream and moved @@ -14,40 +14,63 @@ HIGGSFIELD_SKILLS_URL="${HIGGSFIELD_SKILLS_URL:-\ https://github.com/higgsfield-ai/skills.git}" +# _higgsfield_adopt +# Move every real higgsfield-*/ directory of the clone that holds a SKILL.md +# over its copy in ; prints how many landed. A symlinked entry is +# skipped: only upstream's own directories are adopted. A skill counts only +# once its move succeeded. +_higgsfield_adopt() { + local clone="$1" dest="$2" dir name count=0 + for dir in "$clone"/higgsfield-*/; do + dir="${dir%/}" + { [ -f "$dir/SKILL.md" ] && [ ! -L "$dir" ]; } || continue + name="$(basename "$dir")" + rm -rf "${dest:?}/${name:?}" && mv "$dir" "$dest/$name" \ + && count=$((count + 1)) + done + echo "$count" +} + # higgsfield_sync_skills -# Clone upstream into a throwaway stage and replace each +# Clone upstream into a stage and replace each # /skills-external/higgsfield-* with the fresh copy; upstream's own -# machinery (setup, scripts/, plugin manifests, .git) is left in the stage. -# Prints the number of skills synced. Returns 1, existing copies untouched, -# when the clone fails or upstream holds no higgsfield-*/SKILL.md. A parked -# skill (skills-disabled/, a symlink to the source path) stays parked. +# machinery (setup, scripts/, plugin manifests, .git) stays in the stage. +# The stage sits next to the destination, on the same filesystem, so each +# replacement is a rename. Prints the number of skills synced. Returns 1, +# existing copies untouched, when the clone fails or upstream holds no +# higgsfield-*/SKILL.md. A parked skill (skills-disabled/, a symlink +# to the source path) stays parked. Known limit: a skill that upstream +# removes or renames keeps its last local copy. higgsfield_sync_skills() { - local repo="$1" stage dir name count=0 - stage="$(mktemp -d)" || return 1 - if git clone --quiet --depth 1 "$HIGGSFIELD_SKILLS_URL" "$stage/src" \ - >/dev/null 2>&1; then - mkdir -p "$repo/skills-external" - for dir in "$stage"/src/higgsfield-*/; do - [ -f "${dir}SKILL.md" ] || continue - name="$(basename "$dir")" - rm -rf "${repo:?}/skills-external/${name:?}" - mv "$dir" "$repo/skills-external/$name" - count=$((count + 1)) - done + local dest="$1/skills-external" stage count=0 + mkdir -p "$dest" || return 1 + stage="$(mktemp -d "$dest/.higgsfield-stage.XXXXXX")" || return 1 + # No credential prompt: a private or deleted upstream must fail, not hang. + if GIT_TERMINAL_PROMPT=0 git clone --quiet --depth 1 \ + "$HIGGSFIELD_SKILLS_URL" "$stage/src" >/dev/null 2>&1; then + count="$(_higgsfield_adopt "$stage/src" "$dest")" fi rm -rf "${stage:?}" echo "$count" [ "$count" -gt 0 ] } -# higgsfield_signed_in — 0 when the CLI holds a session. The CLI is closed -# source, so whether `auth token` stays local is unverified: bound it when -# `timeout` exists, feed it no stdin, and never let the token reach a -# terminal or a log. -higgsfield_signed_in() { +# _higgsfield_probe +# Run `higgsfield ` silently, 15 s at most when `timeout` exists. The +# CLI is closed source: a probe must never hang an installer, and what it +# prints (a token, for `auth token`) must never reach a terminal or a log. +_higgsfield_probe() { if command -v timeout >/dev/null 2>&1; then - timeout 15 higgsfield auth token /dev/null 2>&1 + timeout 15 higgsfield "$@" /dev/null 2>&1 else - higgsfield auth token /dev/null 2>&1 + higgsfield "$@" /dev/null 2>&1 fi } + +# higgsfield_cli_ok — 0 when the binary answers. `command -v` alone only +# proves the npm shim: the binary is vendored by a postinstall script that +# npm may hold back, on a first install or on any later update. +higgsfield_cli_ok() { _higgsfield_probe version; } + +# higgsfield_signed_in — 0 when the CLI holds a session. +higgsfield_signed_in() { _higgsfield_probe auth token; } diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/02-higgsfield.test.sh b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/02-higgsfield.test.sh index 720b883..813e660 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/02-higgsfield.test.sh +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/02-higgsfield.test.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # lib/tests/higgsfield.test.sh — hermetic suite for the Higgsfield pack. # sync lib/higgsfield-skills.sh against a local git repo shaped like -# upstream (no network) +# upstream (no network), and its CLI probes against a fake CLI # toggle lib/toggle-external.sh `higgsfield` / `higgsfield-websites` # against a fixture tree, fake CLIs first on PATH # wiring static locks on the installers (order, off by default) @@ -23,11 +23,42 @@ verdict() { } # yn — "yes" when the command succeeds, else "no". yn() { if "$@" 2>/dev/null; then echo yes; else echo no; fi; } +# entries — how many entries the directory holds, hidden ones included. entries() { find "$1" -mindepth 1 -maxdepth 1 | wc -l | tr -d ' '; } WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT +# Fake CLIs, first on PATH in every case that needs one. `higgsfield` +# answers per $FAKE_HF_BINARY (ok | missing: the npm shim without its +# binary) and $FAKE_HF_SESSION (in | out). +BIN="$WORK/bin"; mkdir -p "$BIN" +cat > "$BIN/higgsfield" <<'EOF' +#!/usr/bin/env bash +if [ "${FAKE_HF_BINARY:-ok}" = missing ]; then + echo "@higgsfield/cli: binary not found" >&2; exit 1 +fi +case "${1:-} ${2:-}" in + "version ") echo "higgsfield 0.0.0 (fixture) built never"; exit 0 ;; + "auth token") + if [ "${FAKE_HF_SESSION:-in}" = in ]; then echo "fixture-token"; exit 0; fi + echo "Error: Not authenticated." >&2; exit 2 ;; +esac +exit 64 +EOF +cat > "$BIN/21st" <<'EOF' +#!/usr/bin/env bash +[ "${1:-}" = whoami ] && echo "Logged in as fixture (saved in fixture)." +EOF +chmod +x "$BIN/higgsfield" "$BIN/21st" + +# A PATH that holds the tools the scripts under test need and nothing else: +# no `higgsfield`, no `timeout`, whatever this machine has installed. +CLEAN="$WORK/cleanbin"; mkdir -p "$CLEAN" +for t in bash dirname basename mkdir mv rm ln sed; do + ln -s "$(command -v "$t")" "$CLEAN/$t" +done + # git_q — quiet git in a fixture repo: own identity, no # hooks, so the machine's global git config never leaks in. git_q() { @@ -38,17 +69,19 @@ git_q() { } # mk_upstream — a git repo shaped like the upstream skills repo: three -# pack skills, one pack-named dir with no SKILL.md, one foreign skill, and -# root machinery that must never be synced. +# pack skills, a pack-named dir with no SKILL.md, a pack-named symlink to +# a foreign skill, and root machinery that must never be synced. mk_upstream() { local up="$1" s - mkdir -p "$up/scripts" "$up/higgsfield-empty" "$up/other-skill" - for s in higgsfield-alpha higgsfield-beta higgsfield-websites; do + mkdir -p "$up/scripts" "$up/higgsfield-noskill" "$up/other-skill" + for s in higgsfield-generate higgsfield-soul-id higgsfield-websites; do mkdir -p "$up/$s/references" printf -- '---\nname: %s\n---\n' "$s" > "$up/$s/SKILL.md" echo "ref" > "$up/$s/references/notes.md" done - echo "old" > "$up/higgsfield-alpha/old.md" + echo "old" > "$up/higgsfield-generate/old.md" + echo "no skill here" > "$up/higgsfield-noskill/README.md" + ln -s other-skill "$up/higgsfield-linked" echo "---" > "$up/other-skill/SKILL.md" echo "#!/bin/sh" > "$up/setup" echo "#!/bin/sh" > "$up/scripts/update-check.sh" @@ -68,47 +101,68 @@ sync_into() { ) } +# probe — run one CLI probe of the helper on the given +# PATH; prints everything it wrote, then "rc=". +probe() { + PATH="$1" bash -c 'source "$1/lib/higgsfield-skills.sh"; "$2"; echo "rc=$?"' \ + _ "$ROOT" "$2" 2>&1 +} + # ── sync ──────────────────────────────────────────────────── UP="$WORK/upstream"; mk_upstream "$UP" R1="$WORK/r1"; mkdir -p "$R1/skills" "$R1/skills-disabled" EXT="$R1/skills-external" +expect fixture "$(yn test -f "$UP/.git/HEAD")" yes expect rc-count "$(sync_into "$R1")" "0:3" -expect alpha "$(yn test -f "$EXT/higgsfield-alpha/SKILL.md")" yes -expect refs "$(yn test -f "$EXT/higgsfield-beta/references/notes.md")" yes +expect generate "$(yn test -f "$EXT/higgsfield-generate/SKILL.md")" yes +expect refs \ + "$(yn test -f "$EXT/higgsfield-soul-id/references/notes.md")" yes expect websites "$(yn test -f "$EXT/higgsfield-websites/SKILL.md")" yes -expect no-empty "$(yn test -e "$EXT/higgsfield-empty")" no +expect noskill "$(yn test -e "$EXT/higgsfield-noskill")" no +expect symlink "$(yn test -L "$EXT/higgsfield-linked")" no expect no-other "$(yn test -e "$EXT/other-skill")" no expect no-setup "$(yn test -e "$EXT/setup")" no expect no-git "$(find "$EXT" -name .git | wc -l | tr -d ' ')" 0 expect entries "$(entries "$EXT")" 3 verdict SYNC_MOVES_PACK_ONLY -rm "$UP/higgsfield-alpha/old.md"; echo "new" > "$UP/higgsfield-alpha/new.md" +rm "$UP/higgsfield-generate/old.md" +echo "new" > "$UP/higgsfield-generate/new.md" git_q "$UP" add -A; git_q "$UP" commit -m refresh -expect before "$(yn test -f "$EXT/higgsfield-alpha/old.md")" yes +expect before "$(yn test -f "$EXT/higgsfield-generate/old.md")" yes expect rc-count "$(sync_into "$R1")" "0:3" -expect stale-out "$(yn test -e "$EXT/higgsfield-alpha/old.md")" no -expect new-in "$(yn test -f "$EXT/higgsfield-alpha/new.md")" yes +expect stale-out "$(yn test -e "$EXT/higgsfield-generate/old.md")" no +expect new-in "$(yn test -f "$EXT/higgsfield-generate/new.md")" yes verdict SYNC_REFRESH_DROPS_STALE -ln -s "$EXT/higgsfield-beta" "$R1/skills-disabled/higgsfield-beta" -ln -s "$EXT/higgsfield-alpha" "$R1/skills/higgsfield-alpha" -expect rc-count "$(sync_into "$R1")" "0:3" -expect parked-link "$(yn test -L "$R1/skills-disabled/higgsfield-beta")" yes +ln -s "$EXT/higgsfield-soul-id" "$R1/skills-disabled/higgsfield-soul-id" +ln -s "$EXT/higgsfield-generate" "$R1/skills/higgsfield-generate" +expect rc-count "$(sync_into "$R1")" "0:3" +expect parked-link "$(yn test -L "$R1/skills-disabled/higgsfield-soul-id")" yes expect parked-reads \ - "$(yn test -f "$R1/skills-disabled/higgsfield-beta/SKILL.md")" yes -expect not-enabled "$(yn test -e "$R1/skills/higgsfield-beta")" no -expect live-reads "$(yn test -f "$R1/skills/higgsfield-alpha/SKILL.md")" yes + "$(yn test -f "$R1/skills-disabled/higgsfield-soul-id/SKILL.md")" yes +expect not-enabled "$(yn test -e "$R1/skills/higgsfield-soul-id")" no +expect live-reads "$(yn test -f "$R1/skills/higgsfield-generate/SKILL.md")" yes verdict SYNC_KEEPS_PARKED BARE="$WORK/bare-upstream"; mkdir -p "$BARE"; echo "x" > "$BARE/README.md" git_q "$BARE" init; git_q "$BARE" add -A; git_q "$BARE" commit -m fixture expect no-repo "$(sync_into "$R1" "$WORK/no-such-repo")" "1:0" expect no-skills "$(sync_into "$R1" "$BARE")" "1:0" -expect copy-kept "$(yn test -f "$EXT/higgsfield-alpha/new.md")" yes +expect copy-kept "$(yn test -f "$EXT/higgsfield-generate/new.md")" yes expect entries "$(entries "$EXT")" 3 verdict SYNC_FAIL_KEEPS_COPY +expect cli-ok "$(probe "$BIN:$PATH" higgsfield_cli_ok)" "rc=0" +expect signed-in "$(probe "$BIN:$PATH" higgsfield_signed_in)" "rc=0" +expect signed-out \ + "$(FAKE_HF_SESSION=out probe "$BIN:$PATH" higgsfield_signed_in)" "rc=2" +expect shim-only \ + "$(FAKE_HF_BINARY=missing probe "$BIN:$PATH" higgsfield_cli_ok)" "rc=1" +expect no-cli "$(probe "$CLEAN" higgsfield_cli_ok)" "rc=127" +expect no-timeout "$(probe "$BIN:$CLEAN" higgsfield_cli_ok)" "rc=0" +verdict PROBES_SILENT + # ── tally ─────────────────────────────────────────────────── printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ] diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-suite.patch b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-suite.patch index db56379..52b9316 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-suite.patch +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-suite.patch @@ -1,31 +1,10 @@ --- a/lib/tests/higgsfield.test.sh +++ b/lib/tests/higgsfield.test.sh -@@ -110,5 +110,150 @@ - expect entries "$(entries "$EXT")" 3 - verdict SYNC_FAIL_KEEPS_COPY +@@ -164,5 +164,153 @@ + expect no-timeout "$(probe "$BIN:$CLEAN" higgsfield_cli_ok)" "rc=0" + verdict PROBES_SILENT +# ── toggle ────────────────────────────────────────────────── -+BIN="$WORK/bin"; mkdir -p "$BIN" -+cat > "$BIN/higgsfield" <<'EOF' -+#!/usr/bin/env bash -+# Fake CLI: `auth token` answers per $FAKE_HF_SESSION (in | out). -+[ "${1:-} ${2:-}" = "auth token" ] || exit 64 -+if [ "${FAKE_HF_SESSION:-in}" = in ]; then echo "fixture-token"; exit 0; fi -+echo "Error: Not authenticated." >&2; exit 2 -+EOF -+cat > "$BIN/21st" <<'EOF' -+#!/usr/bin/env bash -+[ "${1:-}" = whoami ] && echo "Logged in as fixture (saved in fixture)." -+EOF -+chmod +x "$BIN/higgsfield" "$BIN/21st" -+ -+# Precondition, loud: the CLI-absent case runs on a sanitized PATH that must -+# not resolve a real `higgsfield`. -+if PATH=/usr/bin:/bin command -v higgsfield >/dev/null 2>&1; then -+ echo "FAIL precondition: a system-wide higgsfield resolves on /usr/bin:/bin" -+ exit 1 -+fi -+ +# mk_toggle_fx [skill...] — fixture repo: the toggle script plus one +# skills-external source per named skill (none → installed-nothing tree). +mk_toggle_fx() { @@ -37,7 +16,7 @@ + echo "---" > "$fx/skills-external/$s/SKILL.md" + done +} -+PACK=(higgsfield-alpha higgsfield-beta higgsfield-websites) ++PACK=(higgsfield-generate higgsfield-soul-id higgsfield-websites) + +# tog — run the fixture's toggle script, fake CLIs first. +tog() { @@ -54,7 +33,7 @@ +expect web-missing "$(tog "$F0" status higgsfield-websites)" missing +expect pack-disabled "$(tog "$F1" status higgsfield)" disabled +expect web-disabled "$(tog "$F1" status higgsfield-websites)" disabled -+ln -s "$F1/skills-external/higgsfield-alpha" "$F1/skills/higgsfield-alpha" ++ln -s "$F1/skills-external/higgsfield-generate" "$F1/skills/higgsfield-generate" +expect pack-partial "$(tog "$F1" status higgsfield)" enabled +expect web-apart "$(tog "$F1" status higgsfield-websites)" disabled +expect list-pack "$(list_row "$F1" higgsfield)" enabled @@ -64,10 +43,10 @@ +F2="$WORK/f2"; mk_toggle_fx "$F2" "${PACK[@]}" +out="$(tog "$F2" enable higgsfield)"; rc=$? +expect rc "$rc" 0 -+expect alpha "$(readlink "$F2/skills/higgsfield-alpha")" \ -+ "$F2/skills-external/higgsfield-alpha" -+expect beta "$(readlink "$F2/skills/higgsfield-beta")" \ -+ "$F2/skills-external/higgsfield-beta" ++expect generate "$(readlink "$F2/skills/higgsfield-generate")" \ ++ "$F2/skills-external/higgsfield-generate" ++expect soul-id "$(readlink "$F2/skills/higgsfield-soul-id")" \ ++ "$F2/skills-external/higgsfield-soul-id" +expect no-websites "$(yn test -e "$F2/skills/higgsfield-websites")" no +expect_has count "$out" "higgsfield enabled (2 skills: 0 restored, 2 linked)" +out="$(tog "$F2" enable higgsfield)"; rc=$? @@ -75,14 +54,33 @@ +expect_has again "$out" "higgsfield already enabled" +verdict ENABLE_PACK_EXCLUDES_WEBSITES + ++# The media pack is an allowlist: a synced skill nobody listed is reported, ++# never linked; neither is a listed name whose directory holds no SKILL.md. ++F8="$WORK/f8"; mk_toggle_fx "$F8" "${PACK[@]}" higgsfield-newcomer ++mkdir -p "$F8/skills-external/higgsfield-brandkit" \ ++ "$F8/skills-external/higgsfield-noskill" ++out="$(tog "$F8" enable higgsfield)"; rc=$? ++expect rc "$rc" 0 ++expect_has count "$out" "higgsfield enabled (2 skills: 0 restored, 2 linked)" ++expect newcomer-off "$(yn test -e "$F8/skills/higgsfield-newcomer")" no ++expect brandkit-off "$(yn test -e "$F8/skills/higgsfield-brandkit")" no ++expect_has reported "$out" "higgsfield-newcomer" ++expect_not noskill-quiet "$out" "higgsfield-noskill" ++expect links "$(entries "$F8/skills")" 2 ++verdict UNLISTED_NOT_LINKED ++ +F3="$WORK/f3"; mk_toggle_fx "$F3" "${PACK[@]}" +out="$(tog "$F3" enable higgsfield-websites)"; rc=$? +expect rc "$rc" 0 +expect link "$(readlink "$F3/skills/higgsfield-websites")" \ + "$F3/skills-external/higgsfield-websites" -+expect no-alpha "$(yn test -e "$F3/skills/higgsfield-alpha")" no ++expect no-generate "$(yn test -e "$F3/skills/higgsfield-generate")" no +expect pack-status "$(tog "$F3" status higgsfield)" disabled +expect web-status "$(tog "$F3" status higgsfield-websites)" enabled ++tog "$F3" disable higgsfield-websites >/dev/null ++out="$(FAKE_HF_SESSION=out tog "$F3" enable higgsfield-websites)"; rc=$? ++expect hint-rc "$rc" 0 ++expect_has web-hint "$out" "higgsfield auth login" +verdict ENABLE_WEBSITES_ALONE + +# Continues on F2: the pack is enabled, websites is not. @@ -90,8 +88,8 @@ +out="$(tog "$F2" disable higgsfield)"; rc=$? +expect rc "$rc" 0 +expect_has msg "$out" "higgsfield disabled (2 skills parked)" -+expect parked "$(yn test -L "$F2/skills-disabled/higgsfield-alpha")" yes -+expect unlinked "$(yn test -e "$F2/skills/higgsfield-alpha")" no ++expect parked "$(yn test -L "$F2/skills-disabled/higgsfield-generate")" yes ++expect unlinked "$(yn test -e "$F2/skills/higgsfield-generate")" no +expect web-untouched "$(yn test -e "$F2/skills/higgsfield-websites")" yes +out="$(tog "$F2" enable higgsfield)" +expect_has restored "$out" "2 restored, 0 linked" @@ -103,18 +101,23 @@ +F4="$WORK/f4"; mk_toggle_fx "$F4" "${PACK[@]}" +out="$(FAKE_HF_SESSION=out tog "$F4" enable higgsfield)"; rc=$? +expect out-rc "$rc" 0 -+expect out-linked "$(yn test -e "$F4/skills/higgsfield-alpha")" yes ++expect out-linked "$(yn test -e "$F4/skills/higgsfield-generate")" yes +expect_has out-hint "$out" "higgsfield auth login" +F5="$WORK/f5"; mk_toggle_fx "$F5" "${PACK[@]}" +out="$(FAKE_HF_SESSION=in tog "$F5" enable higgsfield)" +expect_not in-quiet "$out" "auth login" +expect_not in-no-token "$out" "fixture-token" +F6="$WORK/f6"; mk_toggle_fx "$F6" "${PACK[@]}" -+out="$(TOGGLE_EXTERNAL_REPO_OVERRIDE="$F6" PATH=/usr/bin:/bin \ ++out="$(TOGGLE_EXTERNAL_REPO_OVERRIDE="$F6" PATH="$CLEAN" \ + bash "$F6/lib/toggle-external.sh" enable higgsfield 2>&1)"; rc=$? +expect absent-rc "$rc" 0 -+expect absent-linked "$(yn test -e "$F6/skills/higgsfield-alpha")" yes ++expect absent-linked "$(yn test -e "$F6/skills/higgsfield-generate")" yes +expect_has absent-hint "$out" "not on PATH" ++F9="$WORK/f9"; mk_toggle_fx "$F9" "${PACK[@]}" ++out="$(FAKE_HF_BINARY=missing tog "$F9" enable higgsfield)"; rc=$? ++expect shim-rc "$rc" 0 ++expect_has shim-hint "$out" "does not answer" ++expect_not shim-not-login "$out" "auth login" +verdict SIGNED_OUT_WARNS + +out="$(tog "$F0" enable higgsfield)"; rc=$? @@ -141,7 +144,7 @@ +count() { grep -cF -- "$2" "$ROOT/$1"; } + +# Positive control first: the pattern does bite on a line that carries it. -+expect control "$(echo 'higgsfield-alpha external' | grep -cF higgsfield)" 1 ++expect control "$(echo 'higgsfield-x external' | grep -cF higgsfield)" 1 +expect link-sh "$(count link.sh higgsfield)" 0 +expect profile-sh "$(count lib/profile.sh higgsfield)" 0 +expect profiles \ diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-toggle-external.patch b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-toggle-external.patch index 9e92f80..5fc7462 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-toggle-external.patch +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/03-toggle-external.patch @@ -29,19 +29,39 @@ 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 -@@ -69,6 +73,51 @@ +@@ -69,6 +73,87 @@ done } -+# Prints the skill names of the "higgsfield" media pack: every -+# skills-external/higgsfield-* synced by lib/higgsfield-skills.sh, minus -+# higgsfield-websites, which is its own tool (landing-page aid, named ask). ++# Media skills of the "higgsfield" pack: an explicit allowlist. Upstream is ++# unpinned, so a skill it adds or renames must never be linked by ++# `enable higgsfield` without an edit here (default deny). ++# higgsfield-websites is its own tool: landing-page aid, named ask only. ++HIGGSFIELD_MEDIA_SKILLS=(higgsfield-generate higgsfield-soul-id ++ higgsfield-product-photoshoot higgsfield-brandkit ++ higgsfield-marketplace-cards higgsfield-video-explainer ++ higgsfield-youtube-thumbnail) ++ ++# Prints the allowlisted media skills synced under skills-external/. +higgsfield_skills() { -+ local d ++ local name ++ for name in "${HIGGSFIELD_MEDIA_SKILLS[@]}"; do ++ [ -f "$REPO/skills-external/$name/SKILL.md" ] && echo "$name" ++ done ++ return 0 ++} ++ ++# Prints the synced higgsfield-* skills no tool owns: neither on the media ++# allowlist nor higgsfield-websites. Upstream added or renamed something. ++higgsfield_unlisted() { ++ local d name + for d in "$REPO"/skills-external/higgsfield-*/; do + [ -f "${d}SKILL.md" ] || continue -+ [ "$(basename "$d")" = "higgsfield-websites" ] && continue -+ basename "$d" ++ name="$(basename "$d")" ++ case " ${HIGGSFIELD_MEDIA_SKILLS[*]} higgsfield-websites " in ++ *" $name "*) ;; ++ *) echo "$name" ;; ++ esac + done +} + @@ -53,10 +73,24 @@ + esac +} + -+# The pack skills shell out to their CLI; without it (or without a session) -+# they can only report failure. Warn, never block: the pack is still -+# correctly wired and `make plugin` installs the CLI. -+pack_cli_hint() { ++# bounded — run a CLI probe silently, 15 s at most when `timeout` ++# exists: a closed-source binary must never hang a toggle, and what it ++# prints (a token) must never reach the terminal. Twin of ++# _higgsfield_probe in lib/higgsfield-skills.sh, kept here because this ++# script takes no extra `source` (the fixture suites copy it alone). ++bounded() { ++ if command -v timeout >/dev/null 2>&1; then ++ timeout 15 "$@" /dev/null 2>&1 ++ else ++ "$@" /dev/null 2>&1 ++ fi ++} ++ ++# Post-enable notes for a pack. Its skills shell out to a CLI: without it ++# (or without a session) they can only report failure. Warn, never block: ++# the pack is still correctly wired and `make plugin` installs the CLI. ++pack_hints() { ++ local name + case "$1" in + 21st) + if ! command -v 21st >/dev/null 2>&1; then @@ -66,14 +100,16 @@ + fi + ;; + higgsfield) -+ # Same probe as higgsfield_signed_in (lib/higgsfield-skills.sh), -+ # inlined: this script takes no extra `source`, the fixture suites -+ # copy it alone. The token goes to /dev/null, never to the terminal. + if ! command -v higgsfield >/dev/null 2>&1; then + warn "the \`higgsfield\` CLI is not on PATH — run: make plugin" -+ elif ! higgsfield auth token /dev/null 2>&1; then ++ elif ! bounded higgsfield version; then ++ warn "the \`higgsfield\` CLI does not answer (npm shim without its binary) — run: make plugin" ++ elif ! bounded higgsfield auth token; then + warn "not signed in to Higgsfield — generation needs: higgsfield auth login" + fi ++ while read -r name; do ++ warn "$name is synced but on no allowlist, not linked — see HIGGSFIELD_MEDIA_SKILLS in lib/toggle-external.sh" ++ done < <(higgsfield_unlisted) + ;; + esac +} @@ -81,7 +117,7 @@ # Prints the names (directory basenames) that belong to "gstack". # Source of truth: skills-external/gstack/*/SKILL.md. The repo's # skills/ symlinks are generated from these by gstack ./setup. -@@ -94,7 +143,7 @@ +@@ -94,7 +179,7 @@ ;; emil-design-eng|observability-and-instrumentation|deprecation-and-migration|ci-cd-and-automation| \ scroll-world-storytelling|build-threejs-scroll-worlds|scroll-scrubbed-visual-sequence| \ @@ -90,7 +126,7 @@ [ -d "$REPO/skills-external/$tool" ] || { echo "missing"; return; } [ -e "$SKILLS_DIR/$tool" ] && echo "enabled" || echo "disabled" ;; -@@ -102,12 +151,12 @@ +@@ -102,12 +187,12 @@ [ -d "$HOME/.agents/skills/$tool" ] || { echo "missing"; return; } [ -e "$SKILLS_DIR/$tool" ] && echo "enabled" || echo "disabled" ;; @@ -105,7 +141,7 @@ [ "$installed" -eq 1 ] && echo "disabled" || echo "missing" ;; *) -@@ -135,7 +184,8 @@ +@@ -135,7 +220,8 @@ ;; emil-design-eng|darwin-skill|observability-and-instrumentation|deprecation-and-migration| \ ci-cd-and-automation|scroll-world-storytelling|build-threejs-scroll-worlds| \ @@ -115,7 +151,7 @@ if [ -e "$SKILLS_DIR/$tool" ]; then rm -rf "${DISABLED_DIR:?}/${tool:?}" mv "$SKILLS_DIR/$tool" "$DISABLED_DIR/$tool" -@@ -144,7 +194,7 @@ +@@ -144,7 +230,7 @@ warn "$tool already disabled" fi ;; @@ -124,7 +160,7 @@ # Parked under the plain skill name — same convention as the other # externals, so profile.sh's park/restore path stays interoperable. local parked=0 -@@ -153,11 +203,11 @@ +@@ -153,11 +239,11 @@ rm -rf "${DISABLED_DIR:?}/${name:?}" mv "$SKILLS_DIR/$name" "$DISABLED_DIR/$name" parked=$((parked + 1)) @@ -139,7 +175,7 @@ fi ;; *) err "Unknown tool: $tool"; return 1 ;; -@@ -194,7 +244,8 @@ +@@ -194,7 +280,8 @@ ;; emil-design-eng|darwin-skill|observability-and-instrumentation|deprecation-and-migration| \ ci-cd-and-automation|scroll-world-storytelling|build-threejs-scroll-worlds| \ @@ -149,16 +185,18 @@ local src case "$tool" in darwin-skill) src="$HOME/.agents/skills/$tool" ;; -@@ -214,7 +265,7 @@ +@@ -213,8 +300,9 @@ + err "$tool not installed at $src — run: make plugin" return 1 fi ++ if [ "$tool" = "higgsfield-websites" ]; then pack_hints higgsfield; fi ;; - 21st) + 21st|higgsfield) local restored=0 linked=0 while read -r name; do if [ -e "$DISABLED_DIR/$name" ]; then -@@ -227,24 +278,17 @@ +@@ -227,24 +315,17 @@ ln -sf "$REPO/skills-external/$name" "$SKILLS_DIR/$name" linked=$((linked + 1)) fi @@ -185,11 +223,11 @@ - warn "not signed in to 21st — component retrieval and 21st AI need: 21st login" - fi + ok "$tool enabled ($((restored + linked)) skills: $restored restored, $linked linked)" -+ pack_cli_hint "$tool" ++ pack_hints "$tool" ;; *) err "Unknown tool: $tool"; return 1 ;; esac -@@ -259,7 +303,7 @@ +@@ -259,7 +340,7 @@ } usage() { diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-install-plugins.patch b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-install-plugins.patch index 7e0a61a..24df0aa 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-install-plugins.patch +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-install-plugins.patch @@ -18,7 +18,7 @@ # Interactive terminal: offer to log in now (opens a browser). printf '%b' "${BLUE}→${NC} Authenticate ctx7 now for higher rate limits? [y/N] " read -r ctx7_ans || ctx7_ans="" -@@ -991,6 +993,78 @@ +@@ -991,6 +993,76 @@ echo "" # ============================================================ @@ -40,34 +40,32 @@ +# shellcheck source=lib/higgsfield-skills.sh disable=SC1091 +source "$REPO/lib/higgsfield-skills.sh" +HF_PKG="@higgsfield/cli" -+if command -v higgsfield &>/dev/null; then ++# The package vendors its binary in a postinstall script that npm may hold ++# back; this form lets that one script run. ++HF_REMEDY="npm install -g --allow-scripts=${HF_PKG} ${HF_PKG}" ++ ++# higgsfield_cli_ok, not `command -v`: the npm shim can sit on PATH with no ++# binary behind it, and only a probe tells the two apart. ++if higgsfield_cli_ok; then + ok "Higgsfield CLI already installed" +else + HF_VER=$(pinned_version "higgsfield") -+ if [ "$HF_VER" != "latest" ]; then -+ info "Installing ${HF_PKG}@${HF_VER} (pinned in plugins.lock.json)..." -+ npm install -g "${HF_PKG}@${HF_VER}" || true -+ else -+ info "Installing ${HF_PKG}@latest (consider pinning in plugins.lock.json)..." -+ npm install -g "$HF_PKG" || true -+ fi -+ # The package vendors its binary in a postinstall script: `version` proves -+ # the binary landed, `command -v` alone only proves the JS shim. -+ if higgsfield version &>/dev/null; then ++ [ "$HF_VER" = "latest" ] || HF_PKG="${HF_PKG}@${HF_VER}" ++ info "Installing ${HF_PKG} (version from plugins.lock.json: ${HF_VER})..." ++ npm install -g "$HF_PKG" || true ++ if higgsfield_cli_ok; then + ok "Higgsfield CLI installed" + else -+ err "Higgsfield CLI install failed — run manually: npm install -g --allow-scripts=${HF_PKG} ${HF_PKG}" ++ err "Higgsfield CLI install failed — run manually: $HF_REMEDY" + fi +fi + -+if command -v higgsfield &>/dev/null; then ++if higgsfield_cli_ok; then + # Skill pack — cloned to a stage, then moved under skills-external/. + if HF_N=$(higgsfield_sync_skills "$REPO"); then + ok "Higgsfield skill pack synced to skills-external/ ($HF_N skills)" -+ elif [ -f "$REPO/skills-external/higgsfield-generate/SKILL.md" ]; then -+ ok "Higgsfield skill pack already present (refresh failed — existing copy kept)" + else -+ warn "Higgsfield skill pack sync failed — check: git clone $HIGGSFIELD_SKILLS_URL" ++ warn "Higgsfield skill pack sync failed — existing copies kept (check: git clone $HIGGSFIELD_SKILLS_URL)" + fi + + # Auth — offer the login only when stdin is a terminal: a non-interactive @@ -97,7 +95,7 @@ # STEP 8.7 — 21ST.DEV CLI + SKILL PACK # ============================================================ # `@21st-dev/cli` (bin `21st`): one browser login (`21st login`, token in -@@ -1065,13 +1139,13 @@ +@@ -1065,13 +1137,13 @@ # 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 @@ -113,7 +111,7 @@ printf '%b' "${BLUE}→${NC} Sign in to 21st now? (opens a browser) [y/N] " read -r tfd_ans || tfd_ans="" if [[ "$tfd_ans" =~ ^[Yy]([Ee][Ss])?$ ]]; then -@@ -1236,6 +1310,7 @@ +@@ -1236,6 +1308,7 @@ echo " 🔄 mengto scroll skills — scroll-world-storytelling, build-threejs-scroll-worlds, scroll-scrubbed-visual-sequence, scroll-scrubbed-word-reveal, scroll-progress-timeline (curl → symlink, pinned commit)" echo " 🔄 darwin-skill — autonomous skill optimizer (npx skills, ~/.agents/skills/)" echo " 🔄 21st skill pack — 21st.dev CLI skills; design ones follow the profile (full by default), publishing ones on demand (toggle: lib/toggle-external.sh enable 21st)" diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-suite.patch b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-suite.patch index 799424e..1b76ab2 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-suite.patch +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/04-suite.patch @@ -1,6 +1,6 @@ --- a/lib/tests/higgsfield.test.sh +++ b/lib/tests/higgsfield.test.sh -@@ -255,5 +255,29 @@ +@@ -312,5 +312,29 @@ expect pins-map "$(count lib/effort-pins.txt higgsfield)" 0 verdict OFF_BY_DEFAULT_WIRING @@ -11,7 +11,8 @@ +PINS='apply_effort_pins "$REPO"' + +# install-plugins.sh: the sync sits in Step 8.6, before the effort pins -+# (BDR-108), and every login offer tests stdin alone (stdout is the tee pipe). ++# (BDR-108); the CLI is proven by a probe, not by its shim; every login ++# offer tests stdin alone (stdout is the tee pipe). +sync_ln="$(ln_last install-plugins.sh 'higgsfield_sync_skills')" +expect after-8.5 "$(yn test "$sync_ln" -gt \ + "$(ln_first install-plugins.sh 'Step 8.5: External skills')")" yes @@ -19,13 +20,12 @@ + "$(ln_first install-plugins.sh 'Step 8.7: 21st.dev')")" yes +expect before-pins "$(yn test "$sync_ln" -lt \ + "$(ln_last install-plugins.sh "$PINS")")" yes ++expect probe-gates \ ++ "$(yn test "$(count install-plugins.sh 'if higgsfield_cli_ok')" -ge 3)" yes +expect control "$(echo 'if [ -t 0 ] && [ -t 1 ]; then' | grep -cF -- '-t 1')" 1 +expect no-stdout-test "$(count install-plugins.sh '-t 1')" 0 +expect stdin-tests \ + "$(yn test "$(count install-plugins.sh '[ -t 0 ]')" -ge 3)" yes -+expect summary "$(sed -n '/Install Summary/,$p' "$ROOT/install-plugins.sh" \ -+ | grep -cF 'enable higgsfield')" 1 -+expect remedy "$(count install-plugins.sh '--allow-scripts=')" 1 +verdict INSTALL_WIRING + # ── tally ─────────────────────────────────────────────────── diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-suite.patch b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-suite.patch index a287aab..fadb429 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-suite.patch +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-suite.patch @@ -1,18 +1,18 @@ --- a/lib/tests/higgsfield.test.sh +++ b/lib/tests/higgsfield.test.sh -@@ -279,5 +279,16 @@ - expect remedy "$(count install-plugins.sh '--allow-scripts=')" 1 +@@ -336,5 +336,16 @@ + "$(yn test "$(count install-plugins.sh '[ -t 0 ]')" -ge 3)" yes verdict INSTALL_WIRING +# update-all.sh: refresh before the 21st block and before the pins re-apply, -+# skipped when the CLI is absent. ++# and the updated CLI is proven by the probe. +sync_ln="$(ln_last update-all.sh 'higgsfield_sync_skills')" +expect before-21st "$(yn test "$sync_ln" -lt \ + "$(ln_first update-all.sh '7.4. Update the 21st.dev')")" yes +expect before-pins "$(yn test "$sync_ln" -lt \ + "$(ln_last update-all.sh "$PINS")")" yes -+expect skip-no-cli \ -+ "$(count update-all.sh 'Higgsfield CLI not installed — skipping')" 1 ++expect probe-after-npm \ ++ "$(yn test "$(count update-all.sh 'if higgsfield_cli_ok')" -ge 1)" yes +verdict UPDATE_WIRING + # ── tally ─────────────────────────────────────────────────── diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-update-all.patch b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-update-all.patch index f0a9711..3a5161c 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-update-all.patch +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/05-update-all.patch @@ -1,6 +1,6 @@ --- a/update-all.sh +++ b/update-all.sh -@@ -465,6 +465,41 @@ +@@ -465,6 +465,45 @@ fi fi @@ -14,6 +14,8 @@ +if ! command -v higgsfield &>/dev/null; then + info "Higgsfield CLI not installed — skipping (run: make plugin)" +else ++ # shellcheck source=lib/higgsfield-skills.sh disable=SC1091 ++ source "$REPO/lib/higgsfield-skills.sh" + HF_VER="" + if [ -f "$REPO/plugins.lock.json" ] && command -v python3 &>/dev/null; then + HF_VER=$(python3 -c " @@ -24,14 +26,16 @@ +" 2>/dev/null || true) + fi + HF_PKG="@higgsfield/cli@latest" -+ [ -n "$HF_VER" ] && [ "$HF_VER" != "latest" ] && HF_PKG="@higgsfield/cli@${HF_VER}" -+ if npm install -g "$HF_PKG" 2>/dev/null; then ++ [ -n "$HF_VER" ] && [ "$HF_VER" != "latest" ] \ ++ && HF_PKG="@higgsfield/cli@${HF_VER}" ++ npm install -g "$HF_PKG" 2>/dev/null || true ++ # The probe, not npm's exit status: an update that skips the package's ++ # postinstall script leaves the shim on PATH with no binary behind it. ++ if higgsfield_cli_ok; then + ok "Higgsfield CLI updated (${HF_VER:-latest})" + else -+ warn "Higgsfield CLI update failed — existing binary kept" ++ warn "Higgsfield CLI does not answer after the update — run: npm install -g --allow-scripts=@higgsfield/cli @higgsfield/cli" + fi -+ # shellcheck source=lib/higgsfield-skills.sh disable=SC1091 -+ source "$REPO/lib/higgsfield-skills.sh" + if HF_N=$(higgsfield_sync_skills "$REPO"); then + ok "Higgsfield skill pack refreshed ($HF_N skills)" + else diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/06-doctor.patch b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/06-doctor.patch index 29f0c89..8050139 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/06-doctor.patch +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/06-doctor.patch @@ -9,18 +9,22 @@ echo "" echo "═══ claude-config doctor (v${VERSION}) ═══" -@@ -246,6 +248,18 @@ +@@ -246,6 +248,22 @@ info "Graphifyy not installed (optional — codebase knowledge graph: pipx install graphifyy)" fi +# Higgsfield is optional and off by default: info level, never a warning. -+if command -v higgsfield >/dev/null 2>&1; then -+ pass "Higgsfield CLI installed ($(higgsfield version 2>/dev/null | awk 'NR==1 {print $2}'))" ++# The probe, not `command -v`: the npm shim can outlive its binary. ++if higgsfield_cli_ok; then ++ HF_VERSION="$(higgsfield version 2>/dev/null | awk 'NR==1 {print $2}')" ++ pass "Higgsfield CLI installed (${HF_VERSION:-version unknown})" + if higgsfield_signed_in; then + pass "Higgsfield session active" + else + info "Higgsfield not signed in (generation needs: higgsfield auth login)" + fi ++elif command -v higgsfield >/dev/null 2>&1; then ++ info "Higgsfield CLI on PATH but its binary does not answer (run: npm install -g --allow-scripts=@higgsfield/cli @higgsfield/cli)" +else + info "Higgsfield CLI not installed (optional — media generation: make plugin)" +fi diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/07-changelog.patch b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/07-changelog.patch index 43571bf..c3a3eba 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/07-changelog.patch +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/07-changelog.patch @@ -4,7 +4,7 @@ ## [Unreleased] ### Added -+- **Higgsfield pack, off by default**: `make plugin` installs the `@higgsfield/cli` CLI (Step 8.6) and clones the eight skills of higgsfield-ai/skills into `skills-external/higgsfield-*` through the new `lib/higgsfield-skills.sh`; `make update` refreshes both; `make doctor` reports the CLI and its session at info level. The pack belongs to no profile: `lib/toggle-external.sh enable higgsfield` links the seven media skills, `enable higgsfield-websites` the landing-page aid, and no `profile set` or `make link` re-enables either. `CLAUDE.global.md` routes explicit media-generation asks to it. Hermetic suite `lib/tests/higgsfield.test.sh`. ++- **Higgsfield pack, off by default**: `make plugin` installs the `@higgsfield/cli` CLI (Step 8.6) and clones the skills of higgsfield-ai/skills into `skills-external/higgsfield-*` through the new `lib/higgsfield-skills.sh`; `make update` refreshes both; `make doctor` reports the CLI and its session at info level. The pack belongs to no profile: `lib/toggle-external.sh enable higgsfield` links the seven allowlisted media skills, `enable higgsfield-websites` the landing-page aid, and no `profile set` or `make link` re-enables either. `CLAUDE.global.md` routes explicit media-generation asks to it. Hermetic suite `lib/tests/higgsfield.test.sh`. - **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**: diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/07-readme.patch b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/07-readme.patch index a7dc1d7..61f9a28 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/07-readme.patch +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/07-readme.patch @@ -1,6 +1,6 @@ --- a/README.md +++ b/README.md -@@ -348,6 +348,46 @@ +@@ -348,6 +348,55 @@ auto-approving with no prompt raised (LRN-153), so an `ask` entry would have declared an intent without gating anything. @@ -16,24 +16,29 @@ +``` + +`make plugin` does both (Step 8.6 installs the CLI, then offers the login in -+an interactive terminal) and clones the eight skills of ++an interactive terminal) and clones the skills of +[higgsfield-ai/skills](https://github.com/higgsfield-ai/skills) into +`skills-external/higgsfield-*`. `make update` refreshes the CLI and the +skills, and `make doctor` reports the CLI and its session. The copies are +machine-owned and gitignored. They follow upstream `main`, so a prompt -+change arrives with no diff to review. ++change arrives with no diff to review, and a skill that upstream removes ++keeps its last local copy. + +The pack is off by default and belongs to no profile. It costs nothing until +you ask for it, and no `profile set` touches it: + +```bash -+bash lib/toggle-external.sh enable higgsfield # 7 media skills ++bash lib/toggle-external.sh enable higgsfield # media skills +bash lib/toggle-external.sh enable higgsfield-websites # landing-page aid +bash lib/toggle-external.sh disable higgsfield +``` + -+`higgsfield` turns on generate, soul-id, product-photoshoot, brandkit, -+marketplace-cards, video-explainer and youtube-thumbnail. ++`higgsfield` links a fixed list of seven media skills: generate, soul-id, ++product-photoshoot, brandkit, marketplace-cards, video-explainer and ++youtube-thumbnail. The list is `HIGGSFIELD_MEDIA_SKILLS` in ++`lib/toggle-external.sh`. A skill that upstream adds later is synced and ++reported, and stays unlinked until it is added there. ++ +`higgsfield-websites` is kept apart. Here it helps with landing pages inside +the design stack (assets, references), and `higgsfield website +create|deploy|publish` stays unused. Claude enables either toggle itself on @@ -41,8 +46,12 @@ +with `higgsfield generate cost` before a paid run. + +The skills are cloned, not installed with `npx skills add`: that installer -+links all eight skills into `~/.claude/skills` on every refresh, which would -+undo the off-by-default state. ++links every skill into `~/.claude/skills` on each refresh, which would undo ++the off-by-default state. ++ ++The package ships its binary through a postinstall script. If npm holds that ++script back, `higgsfield` exists on PATH and fails at once; reinstall with ++`npm install -g --allow-scripts=@higgsfield/cli @higgsfield/cli`. + --- diff --git a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/08-claude-global.patch b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/08-claude-global.patch index 4c108a2..f9ae518 100644 --- a/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/08-claude-global.patch +++ b/docs/superpowers/plans/2026-09-30-higgsfield-pack.patches/08-claude-global.patch @@ -1,17 +1,15 @@ --- a/CLAUDE.global.md +++ b/CLAUDE.global.md -@@ -266,6 +266,14 @@ +@@ -266,6 +266,12 @@ verification-before-completion → the verifier gates - SEO+GEO → seo (GEO only → geo); W3C + WCAG a11y → web-validate; security audit (secrets, CVE, OWASP) → cso +- Media generation (image, video, audio, brand kit), explicit ask → + Higgsfield pack, off by default: `bash ~/.claude/lib/toggle-external.sh -+ enable higgsfield`, then its skill (not listed yet → Read its SKILL.md -+ under `~/.claude/skills/`). Metered: `higgsfield generate cost` before -+ a paid run. Landing page "with Higgsfield", named ask only → `enable -+ higgsfield-websites` as an aid (assets, references) inside the Design -+ work stack and the site rules above; never `higgsfield website -+ create|deploy|publish`. ++ enable higgsfield`, then Read the skill under `~/.claude/skills/`; ++ `higgsfield generate cost` before a paid run. Landing page "with ++ Higgsfield", named ask → `enable higgsfield-websites`: an aid inside ++ Design work and the site rules, never `website create|deploy|publish`. gstack OFF → its skills (investigate, qa, review, health, retro, office-hours…) are gone: use the fallback above, else say so. diff --git a/docs/superpowers/specs/2026-09-30-higgsfield-pack-design.md b/docs/superpowers/specs/2026-09-30-higgsfield-pack-design.md index 66a425f..3e9f875 100644 --- a/docs/superpowers/specs/2026-09-30-higgsfield-pack-design.md +++ b/docs/superpowers/specs/2026-09-30-higgsfield-pack-design.md @@ -2,6 +2,7 @@ Date: 2026-09-30. Contract: `.claude/tasks/contracts/2026-09-30-higgsfield-pack-1412.md` (binding: request, clarifications, 14 acceptance criteria). +Revision 2, after the three-lens plan challenge: changes marked `[r2]`. ## Goal @@ -18,7 +19,7 @@ The pack costs nothing when unused: it is installed on disk, linked on demand. Design work stack and site rules: assets and references. Never `higgsfield website create|deploy|publish`. - Skills come from a git clone of `higgsfield-ai/skills` (tracks `main`), - not from `npx skills add`, which re-links all 8 skills on every refresh. + not from `npx skills add`, which re-links every skill on each refresh. - CLI `@higgsfield/cli` at `latest` (21st precedent). - Routing lines in `CLAUDE.global.md`: explicit ask → enable the toggle → follow the skill. @@ -31,35 +32,39 @@ The pack costs nothing when unused: it is installed on disk, linked on demand. ### `lib/higgsfield-skills.sh` (new, sourced) -Owns everything both installers share. +Owns everything the installers and the doctor share. - `HIGGSFIELD_SKILLS_URL`: defaults to `https://github.com/higgsfield-ai/skills.git`; an env value wins (tests point it at a local fixture repo). -- `higgsfield_sync_skills `: `git clone --depth 1` into a `mktemp -d` - stage. Every `higgsfield-*/` directory of the stage that holds a - `SKILL.md` replaces `/skills-external/` (rm, then mv). Prints - the number of skills synced. Returns non-zero, leaving the existing copies - untouched, when the clone fails or yields no skill. Always removes the +- `higgsfield_sync_skills `: `git clone --depth 1` into a stage + created inside `/skills-external/` `[r2]`, so each replacement is a + rename on one filesystem. Every real `higgsfield-*/` directory of the + clone that holds a `SKILL.md` replaces `/skills-external/`; + symlinked entries are skipped `[r2]`; a skill counts only once its move + succeeded `[r2]`. Prints the number of skills synced. Returns non-zero, + leaving the existing copies untouched, when the clone fails or yields no + skill. The clone never prompts for credentials `[r2]`. Always removes the stage. Nothing else from upstream is kept (`setup`, `scripts/`, plugin manifests, `.git`). -- `higgsfield_signed_in`: `timeout 15 higgsfield auth token /dev/null 2>&1`. The CLI is closed source, so the call is bounded and its - output never reaches a terminal or a log. +- `higgsfield_cli_ok` `[r2]`: the binary answers (`higgsfield version`). + `command -v` alone only proves the npm shim: the binary is vendored by a + postinstall script that npm may hold back, on a first install or on any + later update. +- `higgsfield_signed_in`: `higgsfield auth token` succeeds. +- Both probes run silently and for 15 s at most. The CLI is closed source, + so its output never reaches a terminal or a log. -Depends on: `git`, `mktemp`, `timeout`. No dependency on the repo's other -libs. +Depends on: `git`, `mktemp`, optionally `timeout`. No dependency on the +repo's other libs. ### `install-plugins.sh`: Step 8.6, between 8.5 and 8.7 -1. CLI: present → `ok`. Absent → `npm install -g @higgsfield/cli` (or the - pinned version from `plugins.lock.json`), then `higgsfield version` as - the proof; failure prints the manual command, including the - `--allow-scripts=@higgsfield/cli` form, since the package vendors its - binary in a postinstall script that newer npm versions may hold back. -2. Skills: only when the CLI is present, `higgsfield_sync_skills "$REPO"`. - A failed refresh with an existing copy is an `ok` (copy kept); with no - copy, a `warn` naming the manual command. +1. CLI: `higgsfield_cli_ok` → `ok`. Otherwise `npm install -g` the lock + version, then the probe again as the proof; failure prints the manual + command with `--allow-scripts=@higgsfield/cli`. +2. Skills: only when the CLI answers, `higgsfield_sync_skills "$REPO"`. A + failed sync is one `warn` (existing copies kept) `[r2]`. 3. Sign-in: signed in → `ok`. Otherwise, stdin is a terminal → offer `higgsfield auth login` (default no). Else an `info` line with the command. @@ -73,51 +78,58 @@ The ctx7 (Step 6) and 21st (Step 8.7) login offers change from ### `update-all.sh`: block before 7.4 -CLI absent → `info`, skip. Else `npm install -g` the lock version, then -`higgsfield_sync_skills "$REPO"`. A parked skill is a symlink in -`skills-disabled/` pointing at `skills-external/`: replacing the -source keeps the parked state. +CLI absent → `info`, skip. Else `npm install -g` the lock version, then the +probe: a shim left without its binary gets a `warn` with the remedy, never a +success line `[r2]`. Then `higgsfield_sync_skills "$REPO"`. A parked skill is +a symlink in `skills-disabled/` pointing at `skills-external/`: +replacing the source keeps the parked state. ### `lib/toggle-external.sh` - `MANAGED_TOOLS` gains `higgsfield` and `higgsfield-websites`. -- `higgsfield_skills()`: the `skills-external/higgsfield-*/` directories - holding a `SKILL.md`, minus `higgsfield-websites`. +- `HIGGSFIELD_MEDIA_SKILLS` `[r2]`: the seven media skill names, an explicit + allowlist. Upstream is unpinned, so a skill it adds or renames is never + linked without an edit here (default deny). +- `higgsfield_skills()`: the allowlisted names synced under + `skills-external/`. `higgsfield_unlisted()` `[r2]`: synced `higgsfield-*` + skills that no tool owns. - The three pack arms (status, disable, enable) serve `21st|higgsfield` - through `pack_skills `, which dispatches to the right enumerator. - Messages use the tool name. Status is `enabled` when any member is linked. -- After enabling a pack, a per-tool hint warns (never blocks) when the CLI - is missing or signed out. For Higgsfield the probe is inlined: this script - takes no new `source` (four fixture suites copy it). + through `pack_skills `. Messages use the tool name. Status is + `enabled` when any member is linked. +- `pack_hints `, after a pack enable and after + `enable higgsfield-websites` `[r2]`: warns (never blocks) when the CLI is + missing, does not answer, or is signed out, and names each unlisted + skill. Its probes go through `bounded`, a local twin of the helper's + probe `[r2]`: this script takes no new `source` (four fixture suites copy + it alone). - `higgsfield-websites` joins the single-symlink arm, source `skills-external/higgsfield-websites`. -- Header: two tool lines after `21st`; `usage()` prints two more lines. +- Header: two tool lines after `21st`; `usage()` prints them. ### `doctor.sh`: section 4 -CLI present → `pass` with its version; absent → `info` with the install -command. When present: signed in → `pass`, else `info` naming -`higgsfield auth login`. Never `warn`, never `fail`. +CLI answers → `pass` with its version, then signed in → `pass`, else `info` +naming `higgsfield auth login`. Shim on PATH without its binary → `info` +with the remedy `[r2]`. Absent → `info`. Never `warn`, never `fail`. ### Config and docs - `plugins.lock.json`: `higgsfield` entry (source, version, note naming the skills repo and the toggles; no `managed_by`). -- `.gitignore`: `skills/higgsfield-*` and `skills-external/higgsfield-*/`. -- `CLAUDE.global.md`, Skill routing, 8 lines (306 → 314, guard 320): +- `.gitignore`: `skills/higgsfield-*`, `skills-external/higgsfield-*/` and + the sync stage `skills-external/.higgsfield-stage.*/` `[r2]`. +- `CLAUDE.global.md`, Skill routing, 6 lines `[r2]` (306 → 312, guard 320): ``` - Media generation (image, video, audio, brand kit), explicit ask → Higgsfield pack, off by default: `bash ~/.claude/lib/toggle-external.sh - enable higgsfield`, then its skill (not listed yet → Read its SKILL.md - under `~/.claude/skills/`). Metered: `higgsfield generate cost` before - a paid run. Landing page "with Higgsfield", named ask only → `enable - higgsfield-websites` as an aid (assets, references) inside the Design - work stack and the site rules above; never `higgsfield website - create|deploy|publish`. + enable higgsfield`, then Read the skill under `~/.claude/skills/`; + `higgsfield generate cost` before a paid run. Landing page "with + Higgsfield", named ask → `enable higgsfield-websites`: an aid inside + Design work and the site rules, never `website create|deploy|publish`. ``` - `README.md`: `### Higgsfield CLI` after the 21st section. -- `CHANGELOG.md`: one `### Added` bullet under `[Unreleased]`. +- `CHANGELOG.md`: `[Unreleased]` bullets under Added, Security, Fixed. ## Not changed, on purpose @@ -129,30 +141,32 @@ re-enable it on every `make link` or pull it into the profile census. | Case | Behaviour | |---|---| -| npm install fails or the binary is not vendored | `err` with the manual command; the step goes on, skills skipped | -| clone fails, copy exists | copy kept, `ok` | -| clone fails, no copy | `warn` with the manual command | -| upstream layout changes (no `higgsfield-*/SKILL.md`) | sync returns non-zero, copies kept, `warn` | -| toggle enabled, CLI missing or signed out | links created, `warn` | +| npm install fails, or the binary is not vendored | `err` with the `--allow-scripts` command; the step goes on, skills skipped | +| an update leaves the shim without its binary | `warn` with the remedy; doctor says so at info level; the toggle names that cause | +| clone fails, or upstream holds no skill | copies kept, one `warn` | +| upstream adds or renames a skill | synced, reported by the toggle, not linked | +| toggle enabled, CLI missing, mute or signed out | links created, `warn` | | `enable` with no source | `err` naming the path checked, rc 1 | ## Tests: `lib/tests/higgsfield.test.sh` -Hermetic: a mktemp repo holding copies of `toggle-external.sh`, -`gstack-removed.sh` and `higgsfield-skills.sh`; a local git repo as the -skills source; a fake `higgsfield` first on PATH, switchable between signed -in and signed out. +Hermetic: a mktemp repo holding copies of `toggle-external.sh` and +`gstack-removed.sh`; a local git repo as the skills source; fake +`higgsfield` and `21st` first on PATH; a clean PATH built from symlinks for +the no-CLI cases, so the suite behaves the same whatever is installed. Named cases (the contract's oracle greps them): `SYNC_MOVES_PACK_ONLY`, `SYNC_REFRESH_DROPS_STALE`, `SYNC_KEEPS_PARKED`, `SYNC_FAIL_KEEPS_COPY`, -`ENABLE_PACK_EXCLUDES_WEBSITES`, `ENABLE_WEBSITES_ALONE`, `DISABLE_PARKS`, -`STATUS_STATES`, `SIGNED_OUT_WARNS`. Plus static locks on the two installers: -the sync call sits before the last `apply_effort_pins`, and no `-t 1` test -remains in `install-plugins.sh`. +`PROBES_SILENT`, `STATUS_STATES`, `ENABLE_PACK_EXCLUDES_WEBSITES`, +`UNLISTED_NOT_LINKED`, `ENABLE_WEBSITES_ALONE`, `DISABLE_PARKS`, +`SIGNED_OUT_WARNS`, `ENABLE_MISSING_ERRS`, `PACK_21ST_UNCHANGED`, +`OFF_BY_DEFAULT_WIRING`, `INSTALL_WIRING`, `UPDATE_WIRING`. ## Known limits - Upstream prompts change with no diff to review (same trade-off as 21st). +- A skill that upstream removes or renames keeps its last local copy. - Each upstream skill reinstalls the CLI through `curl | sh` when - `higgsfield` is off PATH. With the CLI installed by npm this never fires. -- `higgsfield auth token` locality is unverified. + `higgsfield` is off PATH. With the CLI installed by npm this never fires, + and the deny rule on `* | sh` blocks it anyway. +- `higgsfield auth token` locality is unverified, hence the 15 s bound.