refactor: extract @hyperframes/lint from core#1756
Conversation
9b0a843 to
019278f
Compare
25df696 to
6ba8de9
Compare
This stack of pull requests is managed by Graphite. Learn more about stacking. |
f16620a to
89fd76a
Compare
terencecho
left a comment
There was a problem hiding this comment.
Pass on the API change is clean. One substantive finding worth resolving before merge — the PR description doesn't quite match the diff, and the divergence it conceals is a real footgun.
The thing. Description says "HTML-level primitives stay in core (they're shared with the compiler); what moves out is the project orchestration layer." True for lintProject and friends, but the entire rule engine — packages/core/src/lint/hyperframeLinter.ts, rules/{adapters,captions,core,gsap,media,textures,composition,fonts,slideshow}.ts, plus every *.test.ts — is still present in core and now also exists in packages/lint/src/ as a byte-near-identical copy (only diff is import-path rewrites in composition.ts / fonts.ts / slideshow.ts to consume @hyperframes/parsers / @hyperframes/core/fonts/aliases / @hyperframes/core/slideshow).
That gives the runtime two paths into "the linter":
- core's compiler at
packages/core/src/compiler/staticGuard.ts:1doesimport { lintHyperframeHtml } from "../lint/hyperframeLinter"— the local copy. - studio's preview at
packages/studio/vite.adapter.ts:193doesserver.ssrLoadModule("@hyperframes/core/lint")→@hyperframes/core/src/lint/index.tsstub →@hyperframes/lint— the other copy.
ALL_RULES at packages/lint/src/hyperframeLinter.ts:14-24 is the same 9-array spread as core's at packages/core/src/lint/hyperframeLinter.ts:14-24. Byte-identical today, which is exactly when this risk is invisible — the first rule fix that lands in only one diverges the studio preview from the render-time render-gate guard.
Two reasonable resolutions:
- Pick a direction: re-point
staticGuard.tsat@hyperframes/lint, deletepackages/core/src/lint/{rules,hyperframeLinter,context,types,utils}.ts(and their tests, which are also duplicated). The deprecated stub atpackages/core/src/lint/index.tsalready routes everything else through. - Document the dual-copy contract explicitly in both
hyperframeLinter.tsfiles andstaticGuard.tsso a future maintainer is forced to update both copies in lockstep.
(1) is cleaner; (2) is fast.
Everything else verified clean
- Signature break — fully migrated.
lintProject(projectDir: string)atpackages/lint/src/project.ts:168. All 4 in-CLI callers updated:commands/lint.ts:41(project.dir),commands/preview.ts:129(dir),commands/publish.ts:38(dir),commands/render.ts:741(project.dir).studioServer.tsdoesn't calllintProject. Tests inpackages/cli/src/utils/lintProject.test.tsupdated. No stray{ dir, name, indexPath }shape on this branch. - Behavioral parity. Old
lintProjectconsumedproject.indexPath+project.dir;project.namewas always caller-only. Newproject.ts:169derivesindexPathviaresolve(projectDir, "index.html"). Same outcome. - External-consumer surface. Only two
@hyperframes/core/lintsubpath consumers in the repo (cli/src/utils/lintFormat.test.ts:2,studio/vite.adapter.ts:193); both resolve cleanly through the stub. No@hyperframes/core/lint/rules//shouldBlockRendersubpath exports incore/package.json— so no other subpath imports could've existed. - Stack-base:
gh pr diff 1756touches 51 files, zero inpackages/parsers/— no #1755 bleed. - Rule registration:
ALL_RULESspreads all 9 rule arrays identically in both copies — no rule silently dropped during the move.
Not approving yet purely on the dual-engine point — happy to flip to approve once that's either resolved or explicitly documented.
— Review by tai (pr-review)
|
Thanks for the dual-engine catch — that was a real footgun. Went with resolution (1): deleted core's copy of the rule engine ( One follow-on: re-exporting
|
terencecho
left a comment
There was a problem hiding this comment.
Verified the dual-engine point is resolved at f86aa6b:
packages/core/src/compiler/staticGuard.ts:1now importslintHyperframeHtmlfrom@hyperframes/lintdirectly — the local re-import is gone.packages/core/src/lint/is empty except for the 84-byte@deprecatedre-export stub atindex.ts. All the duplicated rule files (hyperframeLinter.ts,rules/*.ts,context.ts,types.ts,utils.ts, plus tests) are gone from core.- Bonus rewire on
packages/producer/src/services/hyperframeLint.ts:3— that consumer also picks up from@hyperframes/lintdirectly rather than going through the stub. Nice.
Single source of truth for the rule engine, with the stub catching any straggler @hyperframes/core/lint imports through the deprecation window. No way for the studio preview and render-gate to diverge.
UNSTABLE merge state is just the regression shards + perf shards + Graphite mergeability check still spinning — none gating on this change shape.
— Review by tai (pr-review)
Moves all lint rules, hyperframeLinter, lintProject, and related types from packages/core/src/lint/ into a new standalone packages/lint package. Core keeps a thin re-export stub at @hyperframes/core/lint for backward compatibility. Consumer imports (cli lint command, producer hyperframeLint) are updated to import from @hyperframes/lint directly. Depends on @hyperframes/parsers (PR #1755).
f86aa6b to
d47539e
Compare
0eb1a42 to
cdf9c81
Compare
The base branch was changed.
Delete core's byte-identical copy of the lint rule engine and re-point staticGuard at @hyperframes/lint, so the render-time render-gate and the studio preview share one rule engine instead of two copies that could silently diverge. Back-compat preserved via the @hyperframes/core/lint stub. Addresses review feedback on the dual-copy footgun.
d47539e to
3c46e38
Compare
The lint extraction (#1756) made @hyperframes/lint a runtime dependency of core — core's compiled compiler/staticGuard.js imports it via the package's "node" export condition (./dist/index.js). But the Test and Studio-load-smoke jobs pre-build only @hyperframes/{parsers,studio-server} before packages/core, so loading core's dist at test / dev-server time fails with: ERR_MODULE_NOT_FOUND: Cannot find module .../@hyperframes/lint/dist/index.js imported from .../packages/core/dist/compiler/staticGuard.js Build the canonical pre-core set @hyperframes/{parsers,lint,studio-server} (the glob the root build script uses) in both jobs so it can't drift again. The SDK job is left as-is — it builds parsers+core only and passes. Reproduced locally: removing packages/lint/dist reproduces the exact ERR_MODULE_NOT_FOUND; building lint resolves it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1768) * fix(cli): always check GitHub skills on init while skills.sh syncs The "don't pass --skip-skills" guidance lives in SKILL.md, which ships through the laggy skills.sh registry and can't be relied on to reach the agent — so an agent that improvises `--skip-skills` silently dodges the GitHub skills freshness pull. Put the guarantee in the CLI instead (the one channel that updates promptly via `npx hyperframes@latest`): - Neuter the `--skip-skills` FLAG so it no longer skips the check; gate skipping on the HYPERFRAMES_SKIP_SKILLS=1 env var instead (the agent/user CLI path never sets it). Print a one-line notice when the ignored flag is passed. - Wire the env escape hatch into the init test helper (one place) and the CI smoke-test / windows-canary steps so they stay offline and fast. - Update the skill docs that previously told agents `--skip-skills` opts out. Temporary measure while skills.sh catches up — revert init.ts's `skipSkills` to `args["skip-skills"] === true` once it does (noted inline). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): build @hyperframes/lint before core in Test and Studio jobs The lint extraction (#1756) made @hyperframes/lint a runtime dependency of core — core's compiled compiler/staticGuard.js imports it via the package's "node" export condition (./dist/index.js). But the Test and Studio-load-smoke jobs pre-build only @hyperframes/{parsers,studio-server} before packages/core, so loading core's dist at test / dev-server time fails with: ERR_MODULE_NOT_FOUND: Cannot find module .../@hyperframes/lint/dist/index.js imported from .../packages/core/dist/compiler/staticGuard.js Build the canonical pre-core set @hyperframes/{parsers,lint,studio-server} (the glob the root build script uses) in both jobs so it can't drift again. The SDK job is left as-is — it builds parsers+core only and passes. Reproduced locally: removing packages/lint/dist reproduces the exact ERR_MODULE_NOT_FOUND; building lint resolves it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): address PR #1768 review — stale comment + harden offline init - Update the stale interactive-path comment that still said "Opt out with --skip-skills"; the flag is neutered, opt-out is HYPERFRAMES_SKIP_SKILLS=1. - Wrap installAllSkills in ensureSkillsCurrent with try/catch. installAllSkills is already non-strict (swallows its own failures), but since --skip-skills no longer escapes this path, every init — including offline ones that fall through to "install anyway" — runs it. The guard guarantees a skills-install failure only warns and proceeds, never breaks init. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Summary
Extracts the project-level linter —
lintProject, the rule engine, andshouldBlockRender— out of@hyperframes/core/src/lint/into a new@hyperframes/lintpackage.This is the payoff PR for #1749: a Node app (e.g. an agent harness) can now
import { lintProject } from "@hyperframes/lint"and lint a composition directory directly, instead of spawningnpx hyperframes lintand parsing stdout.Part 2 of 3 — stacks on #1755 (
@hyperframes/parsers). Merge A first.What moves
The entire linter moves to
@hyperframes/lint— the rule engine (hyperframeLinter, all 9rules/*,context,types,utils) and the project-orchestration layer (lintProject, directory walking, the render-gate decision). It's the single source of truth: core's compiler render-gate (staticGuard.ts) and the studio preview both consume the rule engine from@hyperframes/lint, so there's no second copy that can drift.API change worth calling out
lintProjectnow takes a directory string instead of a resolved project object:That's deliberate — it drops the CLI's
Projecttype from the public surface so the function is callable from any Node context with nothing but a path. All in-repo callers (lint,preview,publish,rendercommands) and tests are updated.Bundle footprint
dist/(unpacked)How
@hyperframes/corechangespackages/core/src/lint/index.tsbecomes a one-line@deprecatedre-export stub:So existing
@hyperframes/core/lintimports keep working, but new code points at the dedicated package.@hyperframes/lintdepends on@hyperframes/core+@hyperframes/parsers+postcss.Note:
lintHyperframeHtmlis no longer re-exported from core's main entry (@hyperframes/core) — only from the@hyperframes/core/lintsubpath stub and from@hyperframes/lint. Re-exporting it from the main entry would cycle core's index through the lint package (which imports core utilities back); keeping it behind the subpath stub matches how every other extracted package is surfaced.Test plan
bun run --filter @hyperframes/lint test— 257 tests passbun run --cwd packages/cli test— 1045 tests pass (callers updated for the new signature)bun run build— full monorepo build succeeds