Skip to content

feat(skills): act on stale CLI pin during project resume#2540

Merged
WaterrrForever merged 3 commits into
mainfrom
feat/skill-stale-pin-bump
Jul 16, 2026
Merged

feat(skills): act on stale CLI pin during project resume#2540
WaterrrForever merged 3 commits into
mainfrom
feat/skill-stale-pin-bump

Conversation

@WaterrrForever

@WaterrrForever WaterrrForever commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Why

Telemetry shows the CLI fleet converges to new releases within about a week — the background auto-updater covers global installs and ephemeral npx @latest runs are always fresh. The persistent stale tail (~10% of weekly actives; 6.3k users were still on 0.6.x three weeks after 0.7.0 shipped) is almost entirely projects with pinned scripts: init writes hyperframes@<version> into package.json scripts for render reproducibility, and nothing advances that pin.

A notice-only trigger can't reach that tail: the stale-pin stderr notice shipped in 0.7.59, and a pinned run of an older CLI prints no warning at all — so the projects most behind are exactly the ones that emit no signal.

What

1. Entry-skill rule (skills/hyperframes/SKILL.md § 1). When resuming a project whose scripts carry a pin, probe once (read-only, runs the latest CLI regardless of the project's pin):

npx hyperframes@latest upgrade --project . --check

When the probe — or the stale-pin notice, or _meta.updateAvailable: true from a pinned-script run — reports the project behind, apply with upgrade --project . and verify with npx 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 signal is acted on, not relayed.

2. --project arg-parsing fix (found while testing). citty parses --project as a string option, so upgrade --project --check consumed --check as the directory value and no-opd with No package.json found — the documented default-cwd behavior only worked with --project as the final token, and the trap-prone form is exactly what the scaffolded template CLAUDE.md instructed. resolveProjectArgs now reclaims an eaten --check/--json, defaults to the current directory, and drops unrelated eaten flags rather than treating them as paths. Unit-tested.

3. Doc surfaces aligned. Scaffolded templates (_shared/CLAUDE.md + AGENTS.md, kept byte-identical) and hyperframes-cli/references/upgrade-info-misc.md switch to the explicit-dir form --project ., which behaves correctly on every release including ones that predate the parsing fix.

Tested

Against published hyperframes@0.7.60 on a scratch project pinning 0.6.121 in three scripts:

  • upgrade --project . --checkwould bump project scripts 0.6.121 → 0.7.60, package.json untouched
  • ... --check --json{ changed: true, from: ["0.6.121"], to: "0.7.60", path: ... }
  • upgrade --project . → all three pins rewritten to 0.7.60
  • no-pin project → no-op (already on hyperframes@0.7.60)
  • upgrade --project --check (bare) → reproduces the trap on 0.7.60; with this branch's source the same invocation reports the delta correctly and leaves package.json untouched

upgrade.project.test.ts covers the reclaim (--check/--json), bare/boolean --project, real-directory pass-through, and unrelated eaten flags. 8/8 pass; oxlint and oxfmt clean.

Why the skill channel

Skills propagate from main directly (skills add --full-depth / skills update), so the rule reaches users without waiting for a CLI release — and because probe and bump are invoked unpinned, they work even when the project's installed CLI predates this change.

User shape Covered by
Global installs (TTY or not) existing background auto-updater
npx @latest runs always fresh
Agent sessions in pinned projects (the stale tail) this PR

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.
@WaterrrForever WaterrrForever force-pushed the feat/skill-stale-pin-bump branch from 74ff09f to 7eff755 Compare July 16, 2026 10:32
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.

@miga-heygen miga-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — feat(skills): act on stale CLI pin during project resume

Scope: 7 files, +88/−10 — arg-parsing fix for upgrade --project, new skill rule, doc alignment.

SSOT inventory

Artifact Role Verdict
resolveProjectArgs Canonical resolver for the eaten-flag trap — reclaims --check/--json, drops unknowns, defaults dir to . Clean — pure function, single consumer, no duplicated decision
SKILL.md § "Keep the project's CLI current" Agent-facing instruction: probe → apply → verify → revert-on-fail Clean — actionable, covers failure path, acts-don't-relay
Template docs (CLAUDE.md + AGENTS.md) Both switch --project--project . Clean — byte-identical by design (different AI-tool targets)
upgrade-info-misc.md Adds backward-compat warning for older releases Clean — matches the fix

What I checked

  1. Eaten-flag matrix. --project --check, --project --json, --project --check --json (in both orderings), --project (bare), --project "" — all resolve correctly through the reclaim/boolean/empty branches. The --yes case (unrelated to project mode) correctly drops to dir: "." without reclaim, which is a no-op since --yes has no meaning in the --project path.

  2. Caller integration. The command handler now threads p.check and p.json (from resolveProjectArgs) instead of checkOnly and useJson (from raw args). This is the crux of the fix: if --check was eaten, args.check === false but p.check === true. Single source of truth for all three values in the project branch.

  3. Test coverage. 4 cases covering reclaim, bare/boolean, real directory, unrelated flag. Each asserts the full {dir, check, json} triple.

  4. Skill instruction quality. Read-only probe → apply → hyperframes check → revert-on-fail is the right shape. The "keep the explicit ." rationale (backward compat with older CLI releases) is a good detail. "Act on the signal rather than relaying it to the user" is exactly right for an agent skill.

Edge case noted (not blocking)

A directory name starting with - (e.g., -my-weird-dir) would be treated as an eaten flag and silently default to .. Pathological in practice — Unix directories starting with - are adversarial even in normal shell usage — but worth a comment if anyone ever wonders why resolveProjectArgs("-foo", ...) returns ..

Verdict

Clean PR. Arg-parsing fix is correct, well-tested, and the skill/doc surfaces are all aligned to the explicit-dir form. No SSOT violations.

— Miga

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent review, additive to @miga. Clean, well-scoped PR — the revert-if-broken safety is exactly the right call for the 6k-on-0.6.x cohort.

Mechanism sound + safe. The template / SKILL instructions have the agent probe read-only (upgrade --project . --check), apply only when behind, verify with hyperframes check, and — the key safety — revert the package.json + stay pinned + report if the check fails. "Act on the signal, never leave a bumped pin unverified" is the right posture. resolveProjectArgs correctly reclaims the citty --project --check arg-eating quirk, and the unknown-flag / --yes → cwd edge is covered by a test.

One consideration (non-blocking): hyperframes check validates composition structure/lint, not render-output equivalence — so a bump that passes check can still change a project's rendered output. The pin exists so a project "re-renders identically over time" (your own template copy); auto-advancing it trades that reproducibility for the newer fixes. Defensible as a default (most stale users want the fixes, and the revert covers hard breaks) — but a check-passing bump isn't guaranteed byte-identical output, so for a reproducibility-sensitive project a silent advance could surprise. Worth considering: have the agent note the bump it made to the user, or a --check line that flags "output may shift," rather than a fully silent rewrite.

The un-reachable tail (users who never refresh skills at all) is real but genuinely outside what the CLI side can reach — that's the comms question, not a code one.

— Rames Jusso

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.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at 1e6e4f0 on James's go.

The new commit since my review (1e6e4f0, "report a successful pin bump in the run summary") resolves the one consideration I raised — the SKILL.md now states plainly that a passing check "confirms the project's compositions still validate on the new version — not that rendered output is frame-identical to the old pin — so a successful bump is never silent: name the old and new version in the run's summary." That's exactly the fix: acknowledges check ≠ output-equivalence and requires the agent to surface the bump instead of a silent rewrite.

Re-verified the incremental diff (061bcbf→1e6e4f0): 2-line doc change + expected skills-manifest hash auto-regen — zero render impact. The mechanism (probe → apply-only-if-behind → verify → revert-on-fail) and the resolveProjectArgs citty-quirk fix are unchanged and sound.

CI green except two regression-shards still pending — doc-only diff, so no render exposure, and the merge stays CI-gated by branch protection regardless.

— Rames Jusso

@WaterrrForever WaterrrForever merged commit f8c33ca into main Jul 16, 2026
50 checks passed
@WaterrrForever WaterrrForever deleted the feat/skill-stale-pin-bump branch July 16, 2026 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants