mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
feat(skills): act on stale CLI pin during project resume (#2540)
* feat(skills): probe and bump stale CLI pins during project resume
The entry skill now keeps a resumed project's pinned CLI current instead of
leaving that to a notice nobody acts on. On resuming a project with pinned
scripts, run the read-only probe 'npx hyperframes@latest upgrade --project
. --check'; when it (or the stale-pin stderr notice, or _meta.updateAvailable
from a pinned run) reports the project behind, apply the bump and verify
with 'hyperframes check'. A failed check reverts the bump and keeps the
project on its pinned version, preserving the reproducibility contract the
pin exists for.
The probe matters because the stale-pin notice only exists in >= 0.7.59:
a pinned run of an older CLI prints no warning at all, so a notice-only
trigger never fires for exactly the projects most behind. The probe runs
unpinned, so its behavior never depends on the project's CLI age.
Telemetry: the fleet converges to new releases within about a week via the
background auto-updater and ephemeral npx, but pinned projects form a
persistent stale tail (~10% of weekly actives, e.g. 6.3k users still on
0.6.x three weeks after 0.7.0).
Both skill surfaces now pass an explicit dir ('--project .') because a bare
'--project' followed by another flag consumes that flag as its directory
value and no-ops; the parsing fix is a separate CLI change.
* fix(cli): stop bare --project from eating the next flag as its directory
citty parses --project as a string option, so 'upgrade --project --check'
arrived with project="--check": the dir resolved to a nonexistent path and
the command no-opd with 'No package.json found' while --check was lost.
The documented default-cwd behavior only worked when --project was the
final token — and the trap-prone form is exactly what the scaffolded
template CLAUDE.md instructs.
A leading dash can never be a real directory argument, so resolveProjectArgs
now reclaims the eaten token as the flag the user wrote (--check / --json),
falls back to the current directory, and drops unrelated eaten flags rather
than treating them as paths. Templates and skill references switch to the
explicit-dir form ('--project .'), which behaves correctly on every release
including ones that predate this fix.
* feat(skills): report a successful pin bump in the run summary
Review follow-up on the stale-pin rule: 'hyperframes check' validates
composition structure, not render-output equivalence, so a check-passing
bump can still shift a project's rendered output. The bump stays the right
default for stale projects, but it must not be silent — the summary now
names the old and new version so the user knows the reproducibility
trade was made.
This commit is contained in:
@@ -12,7 +12,51 @@ vi.mock("../utils/updateCheck.js", async (orig) => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
import { upgradeProjectPins } from "./upgrade.js";
|
||||
import { resolveProjectArgs, upgradeProjectPins } from "./upgrade.js";
|
||||
|
||||
describe("resolveProjectArgs", () => {
|
||||
it("reclaims a flag eaten as the --project value", () => {
|
||||
expect(resolveProjectArgs("--check", { check: false, json: false })).toEqual({
|
||||
dir: ".",
|
||||
check: true,
|
||||
json: false,
|
||||
});
|
||||
expect(resolveProjectArgs("--json", { check: false, json: false })).toEqual({
|
||||
dir: ".",
|
||||
check: false,
|
||||
json: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults to the current directory for a bare or boolean --project", () => {
|
||||
expect(resolveProjectArgs(true, { check: true, json: false })).toEqual({
|
||||
dir: ".",
|
||||
check: true,
|
||||
json: false,
|
||||
});
|
||||
expect(resolveProjectArgs("", { check: false, json: false })).toEqual({
|
||||
dir: ".",
|
||||
check: false,
|
||||
json: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("passes a real directory through untouched", () => {
|
||||
expect(resolveProjectArgs("apps/site", { check: false, json: true })).toEqual({
|
||||
dir: "apps/site",
|
||||
check: false,
|
||||
json: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("drops an unrelated eaten flag instead of treating it as a directory", () => {
|
||||
expect(resolveProjectArgs("--yes", { check: false, json: false })).toEqual({
|
||||
dir: ".",
|
||||
check: false,
|
||||
json: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("upgradeProjectPins", () => {
|
||||
const dirs: string[] = [];
|
||||
|
||||
@@ -40,13 +40,13 @@ export default defineCommand({
|
||||
const checkOnly = args.check === true;
|
||||
|
||||
if (args.project !== undefined) {
|
||||
const dir = typeof args.project === "string" && args.project.length ? args.project : ".";
|
||||
const res = await upgradeProjectPins(resolve(dir), { json: useJson, check: checkOnly });
|
||||
if (useJson) {
|
||||
const p = resolveProjectArgs(args.project, { check: checkOnly, json: useJson });
|
||||
const res = await upgradeProjectPins(resolve(p.dir), { json: p.json, check: p.check });
|
||||
if (p.json) {
|
||||
console.log(JSON.stringify(withMeta(res), null, 2));
|
||||
return;
|
||||
}
|
||||
printProjectPinResult(res, checkOnly);
|
||||
printProjectPinResult(res, p.check);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,6 +157,30 @@ function printManualCommands(displayCmd: string, npxFallback: string): void {
|
||||
clack.outro(c.success("Run one of the commands above to upgrade."));
|
||||
}
|
||||
|
||||
/**
|
||||
* citty parses `--project` as a string option, so a bare `--project` followed
|
||||
* by another flag consumes that flag as its value (`upgrade --project --check`
|
||||
* arrives here as project="--check"). A leading dash can never be a real
|
||||
* directory argument, so reclaim the eaten token as the flag the user wrote
|
||||
* and fall back to the current directory.
|
||||
*/
|
||||
export function resolveProjectArgs(
|
||||
project: string | boolean,
|
||||
opts: { check: boolean; json: boolean },
|
||||
): { dir: string; check: boolean; json: boolean } {
|
||||
if (typeof project !== "string" || project.length === 0) {
|
||||
return { dir: ".", check: opts.check, json: opts.json };
|
||||
}
|
||||
if (project.startsWith("-")) {
|
||||
return {
|
||||
dir: ".",
|
||||
check: opts.check || project === "--check",
|
||||
json: opts.json || project === "--json",
|
||||
};
|
||||
}
|
||||
return { dir: project, check: opts.check, json: opts.json };
|
||||
}
|
||||
|
||||
export async function upgradeProjectPins(
|
||||
dir: string,
|
||||
opts: { json: boolean; check: boolean },
|
||||
|
||||
@@ -44,7 +44,7 @@ npx hyperframes docs <topic> # reference docs in terminal
|
||||
> In Claude Code, always run it with `run_in_background: true`. Never run it as a foreground
|
||||
> command — it will time out and the server will die, breaking the browser preview.
|
||||
|
||||
> **Pinned CLI version.** These scripts pin an exact `hyperframes@X.Y.Z` so this project re-renders identically over time. Weeks later that pin lags fixes shipped since. To move up: `npx hyperframes@latest upgrade --project --check` (shows the delta), then `npx hyperframes@latest upgrade --project` to rewrite the pins. Always unpinned — the pinned script re-runs the old version against itself.
|
||||
> **Pinned CLI version.** These scripts pin an exact `hyperframes@X.Y.Z` so this project re-renders identically over time. Weeks later that pin lags fixes shipped since. To move up: `npx hyperframes@latest upgrade --project . --check` (shows the delta), then `npx hyperframes@latest upgrade --project .` to rewrite the pins. Always unpinned — the pinned script re-runs the old version against itself.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ npx hyperframes docs <topic> # reference docs in terminal
|
||||
> In Claude Code, always run it with `run_in_background: true`. Never run it as a foreground
|
||||
> command — it will time out and the server will die, breaking the browser preview.
|
||||
|
||||
> **Pinned CLI version.** These scripts pin an exact `hyperframes@X.Y.Z` so this project re-renders identically over time. Weeks later that pin lags fixes shipped since. To move up: `npx hyperframes@latest upgrade --project --check` (shows the delta), then `npx hyperframes@latest upgrade --project` to rewrite the pins. Always unpinned — the pinned script re-runs the old version against itself.
|
||||
> **Pinned CLI version.** These scripts pin an exact `hyperframes@X.Y.Z` so this project re-renders identically over time. Weeks later that pin lags fixes shipped since. To move up: `npx hyperframes@latest upgrade --project . --check` (shows the delta), then `npx hyperframes@latest upgrade --project .` to rewrite the pins. Always unpinned — the pinned script re-runs the old version against itself.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
Reference in New Issue
Block a user