fix(cli,skills): install workflow skills on demand instead of re-pulling the full set (#2012)

* fix(cli,skills): install workflow skills on demand instead of re-pulling the full set

Users report every init re-pulls all 21 skills into ~/.agents/skills
whenever anything is stale or missing - heavy, noisy, and it re-expands
deliberate partial installs.

Split the set into two tiers:

- core: the /hyperframes router + hyperframes-* domain skills +
  media-use, which every workflow references structurally. init and
  bare 'skills update' keep these (plus anything already installed)
  fresh, and never expand the install.
- on demand: the end-user workflow skills (and figma). They install at
  trigger time via 'skills update <name...>' - positional names are
  the only way update expands an install: one targeted
  'skills add --skill <name>' covering only stale/missing targets, a
  fast no-op when current, presence-verified after install, exit 1 on
  unknown names, and a presence-only degrade when GitHub is
  unreachable.

The /hyperframes router now runs 'skills update <workflow>' after
routing and before reading the workflow skill, so a routed workflow is
guaranteed present even on a machine that only has the core set. Each
on-demand skill also opens with the same self-maintenance step (run
'npx hyperframes skills update <name>' silently), so a workflow
triggered directly - without the router - still refreshes itself and
restores any missing core skill before relying on it.

When the manifest is unreachable (offline / rate-limited) the engine
degrades honestly instead of claiming success: named runs presence-check
the request plus a pinned fallback core list (unit-pinned to skills/)
and blind-install whatever is absent; a bare strict update fails loudly
so the 'check || update' chain can't pass while everything stays stale;
init reports the skipped freshness check. --json emits structured
errors on failure paths.

skills check still lists every skill, but exits non-zero only for
stale installed skills, an incomplete core set, or removed leftovers -
workflow skills not yet installed are reported as available on demand.
Bare 'hyperframes skills' (and 'skills add --all') remain the explicit
full-set installs.

Verified end-to-end with a sandboxed $HOME: fresh init installs the 9
core skills only; 'skills update slideshow' adds exactly that skill
(no-op on re-run, exit 1 on unknown names); bare update refreshes
without expanding; a live Claude Code run routed PR-to-video, executed
the router's update step, and the workflow skill appeared before use;
and a second live run triggered an installed workflow directly, whose
opening maintenance step restored a deliberately removed core skill.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(skills): clarify update-engine contracts + document lazy-install model

- skills.ts: note the UpdateSkillsResult.unknown strict-mode contract,
  verifyInstalled's non-strict (warn-not-throw) intent, and that a
  partial install stays "refreshed but never expanded" (review nits).
- docs/guides/skills.mdx: add a "Keeping skills current" section covering
  the core-eager / workflow-on-demand model and the skills check|update
  commands, per the repo's catalog-maintenance rule.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: kiritowoo <295860553+kiritowoo@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Miao Yang <miao.yang@heygen.com>
This commit is contained in:
kiritowoo
2026-07-09 01:30:51 +08:00
committed by GitHub
co-authored by Claude Opus 4.8 kiritowoo Miao Yang
parent e52bfc246a
commit 81884a7495
25 changed files with 982 additions and 160 deletions
+38 -39
View File
@@ -577,48 +577,46 @@ async function scaffoldProject(
}
/**
* Ensure the AI coding skills are present and current. Checks the installed
* skills against the latest published on GitHub and only (re)installs when
* something is outdated or missing — so re-running `init` on an up-to-date
* machine is a no-op. Best-effort: if the version check can't reach GitHub, it
* installs anyway. The install itself (`installAllSkills`) installs the full set
* once GLOBALLY (~/.claude/skills + ~/.agents/skills) and mirrors it into every
* other installed agent, so it is project-independent — the check is global-first
* to match.
* Keep the AI coding skills present and current — TARGETED, not the full
* set. Guarantees the core set (the `/hyperframes` entry router + shared
* domain skills) and refreshes any skill already installed; the end-user
* workflow skills are NOT pulled here — they install on demand when their
* workflow is triggered (`hyperframes skills update <name>`, which the router
* runs before entering a workflow). Re-running `init` on an up-to-date machine
* is a no-op, and `init` never expands a deliberate partial install.
* Best-effort: offline, it degrades to a presence check and never breaks init.
* The install itself lands once GLOBALLY (~/.claude/skills + ~/.agents/skills)
* and mirrors into every other installed agent, so it is project-independent —
* the check is global-first to match.
*/
async function ensureSkillsCurrent(destDir: string): Promise<void> {
const { installAllSkills } = await import("./skills.js");
const { checkSkills } = await import("../utils/skillsManifest.js");
async function keepSkillsCurrent(destDir: string): Promise<void> {
const { updateSkills } = await import("./skills.js");
console.log();
console.log(c.bold("Checking AI coding skills against GitHub..."));
let needsInstall = true;
// Wrap defensively (non-strict already swallows most failures): a
// skills-install failure can never break `init` itself — it warns and
// proceeds, since --skip-skills no longer escapes this path.
try {
const result = await checkSkills({ cwd: destDir });
needsInstall = result.updateAvailable;
} catch {
// Couldn't reach GitHub (offline, rate-limited) — install anyway.
}
if (needsInstall) {
// installAllSkills installs the full set once globally and mirrors it into
// every installed agent's global dir — project-independent, so a freshly
// scaffolded project doesn't need any agent folders yet.
//
// Best-effort: installAllSkills (non-strict here) already swallows its own
// failures, but now that --skip-skills no longer escapes this path every
// init runs it — including offline ones, where checkSkills throws and we
// fall through to "install anyway". Wrap defensively so a skills-install
// failure can never break `init` itself; it only warns and proceeds.
try {
await installAllSkills({ cwd: destDir });
} catch (err) {
const result = await updateSkills({ refreshInstalled: true, cwd: destDir });
if (result.presenceOnly) {
// Freshness never got checked (GitHub unreachable) — don't claim
// "up to date"; the engine already reported what it could verify or
// blind-install. Point at the recovery command instead.
console.log(
c.dim(`AI coding skills install skipped: ${err instanceof Error ? err.message : err}`),
c.dim("Skills freshness unverified — run `npx hyperframes skills update` when online."),
);
} else if (result.installed.length === 0) {
console.log(c.success("AI coding skills are already up to date."));
} else {
console.log(
c.dim("Workflow skills not installed here are added on demand, when first used."),
);
}
} else {
console.log(c.success("AI coding skills are already up to date."));
} catch (err) {
console.log(
c.dim(`AI coding skills install skipped: ${err instanceof Error ? err.message : err}`),
);
}
}
@@ -871,7 +869,7 @@ export default defineCommand({
}
if (!skipSkills) {
await ensureSkillsCurrent(destDir);
await keepSkillsCurrent(destDir);
}
console.log();
@@ -1087,11 +1085,12 @@ export default defineCommand({
const files = readdirSync(destDir);
clack.note(files.map((f) => c.accent(f)).join("\n"), c.success(`Created ${name}/`));
// Check skills against GitHub and (re)install only if outdated or missing —
// init is the one place the full set is pulled. The --skip-skills flag is
// temporarily neutered (see above); CI/tests opt out via HYPERFRAMES_SKIP_SKILLS=1.
// Check skills against GitHub and refresh only what's stale — the core set
// plus anything already installed; workflow skills install on demand. The
// --skip-skills flag is temporarily neutered (see above); CI/tests opt out
// via HYPERFRAMES_SKIP_SKILLS=1.
if (!skipSkills) {
await ensureSkillsCurrent(destDir);
await keepSkillsCurrent(destDir);
}
// Auto-launch studio preview