mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-15 22:08:03 +00:00
`hyperframes skills update` failed hard or looped forever once a skill was
retired/renamed upstream while still installed locally (hyperframes-media folded
into media-use; hyperframes-captions/compose/tts consolidated earlier). Two
paths dead-ended:
- Install: target selection could trust a stale local skills-manifest.json
(findRepoManifest) while `skills add` always installs from the canonical repo.
isCoreSkill matches the `hyperframes-` prefix, so a retired skill was forced
into the target set, `skills add` silently declined it (exit 0), and strict
verifyInstalled threw "Skill(s) still missing after install".
- Prune: upstream `skills remove` scans on-disk directories, so a lock entry
retired before it ever shipped a bundle has nothing to match — a silent
exit-0 no-op that never clears the lock, so detectRemoved re-flags it on
every run.
The stale-skills nudge compounded it: it fired even from `skills update` itself
(pointing users back at the failing command) and its count ignored the removed
bucket.
Resolve update targets against the canonical manifest (checkSkills({ canonical:
true })) so a retired skill is never targeted. Add pruneOrphanedLockEntries to
clear the orphaned lock entries the upstream remover can't (idempotent, so a
second run is a clean no-op). Exclude `skills` from the update-nudge gate and
thread the removed count through the nudge total.
43 lines
1.9 KiB
TypeScript
43 lines
1.9 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const cliSource = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "cli.ts"), "utf8");
|
|
const helpSource = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "help.ts"), "utf8");
|
|
|
|
function commandLoaderBlock(): string {
|
|
const match = cliSource.match(/const commandLoaders = \{([\s\S]*?)\n\};/);
|
|
expect(match).toBeTruthy();
|
|
return match![1]!;
|
|
}
|
|
|
|
describe("CLI command registration", () => {
|
|
it("registers keyframes as the only keyframe inspection command", () => {
|
|
const loaders = commandLoaderBlock();
|
|
|
|
expect(loaders).toMatch(/\bkeyframes:\s*\(\)\s*=>\s*import\("\.\/commands\/keyframes\.js"\)/);
|
|
expect(loaders).not.toMatch(/\bmotion:\s*\(\)\s*=>/);
|
|
expect(loaders).not.toContain("./commands/motion.js");
|
|
});
|
|
|
|
it("shows keyframes in root help", () => {
|
|
expect(helpSource).toContain(
|
|
'["keyframes", "Inspect keyframes and render onion-shot diagnostics"]',
|
|
);
|
|
});
|
|
|
|
// A command actively reconciling skills (`skills check`/`skills update`)
|
|
// must not also nudge the user to go reconcile skills — that nudge is
|
|
// either redundant (it just ran) or misleading (a stale cached count from
|
|
// the 24h background check, contradicting whatever it just reported).
|
|
it("excludes 'skills' from the background skills-nudge gate, alongside 'upgrade' and 'events'", () => {
|
|
const match = cliSource.match(/if \(([\s\S]*?)\) \{\s*\/\/ Report any completed auto-install/);
|
|
expect(match, "expected to find the background nudge gate's if-condition").toBeTruthy();
|
|
const condition = match![1]!;
|
|
expect(condition).toContain('command !== "upgrade"');
|
|
expect(condition).toContain('command !== "events"');
|
|
expect(condition).toContain('command !== "skills"');
|
|
});
|
|
});
|