Skip to content

ADHD v2 — Milestone 1: core + lint#10

Open
hhff wants to merge 35 commits into
mainfrom
worktree-adhd+v2
Open

ADHD v2 — Milestone 1: core + lint#10
hhff wants to merge 35 commits into
mainfrom
worktree-adhd+v2

Conversation

@hhff

@hhff hhff commented Jul 6, 2026

Copy link
Copy Markdown
Member

What this is

Milestone 1 of the ground-up ADHD v2 rebuild (spec: docs/superpowers/specs/2026-07-05-adhd-v2-rebuild-design.md, plan: docs/superpowers/plans/2026-07-05-adhd-v2-m1-core-and-lint.md).

v2 replaces v1's LLM-as-plumbing skills and triplicated hand-rolled parsers with a single bundled TypeScript CLI. This milestone lands the core + a working /adhd:lint.

What's in it

  • Toolchain: esbuild bundle to committed dist/ (freshness-gated in CI), vitest, strict TS.
  • Core: one FNV hash (Figma-sandbox-safe), one css-var↔figma-path bijection, one culori color engine (oklch first-class — kills v1's phantom-conflict bug), canonical snapshot + content hash, postcss globals.css reader (per-mode aliases, no silent loss), adhd.config.json + adhd.lock.json loaders.
  • Figma: cursor-chunked extract script shipped as a compiled constant, tested against a fake-figma runtime harness.
  • Rules: STRUCT001–010 ported from v1 with every false-positive exemption; off-system value scanner.
  • Pipelines: three-class drift diff with definite+probable rename detection; byte-stable report formatter.
  • CLI: adhd figma-script extract + adhd lint with a strict exit-code contract (0 / 1-check / 2-operational+fixup).
  • Skill: /adhd:lint rewritten as a 44-line thin wrapper.

Verification

  • v2: 95/95 tests, typecheck clean, dist fresh.
  • v1 untouched: 283/283 tests still green.
  • Every task individually spec+quality reviewed; final whole-branch review passed with fixes applied.

Known merge gate

Not yet exercised against a live Figma file (no MCP in the build env): confirm use_figma returns the ExtractChunk shape the CLI expects and that serializeStyle's field names match the real Figma styles API. Run /adhd:lint once against the real configured file before merging.

🤖 Generated with Claude Code

hhff and others added 30 commits July 5, 2026 21:03
…tecture

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ths, manifest in M5

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eview fix)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements core/tokens.ts with Domain, Mode, Token, StyleShell, Snapshot types and domainOf() function; core/snapshot.ts with canonicalize() and contentHash() for deterministic snapshot hashing. All tests pass (3/3).

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… wording (review fix)

- Fix 1: resolveCssEntry anchors cfg.cssEntry to dir with join() and verifies existence with existsSync()
- Fix 2: Validator accepts cssEntry: null explicitly (was throwing before)
- Fix 3: Improved error messaging for fileKeyFromUrl and replaced readFileSync probes with existsSync

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (review fix)

Rogue-collection variables now get path "rogue:<collection>/<name>" instead of
their plain name, so a variable in a non-Primitives/Semantic collection can
never key-collide with a real primitives token under the ${collection}/${path}
matching convention (snapshot.ts, css.ts). Real token paths are lowercase
alnum/hyphen/slash only and can never contain ":", so this makes collision
impossible.

Also adds minimal inline-doc tests to extract.test.ts covering: corrupted
final-chunk hash, dangling alias, unknown Semantic mode name, and
case-insensitive collection/mode name matching — without touching the large
committed recording fixture.

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

Faithful TS port of lib/lint-engine/structure-checker.js's visit() walker,
push() helper, deepLink(), AUTO_NAME_RE, SINGLE_CHILD_SHAPE_EXEMPT, and
isVisiblePaint(), carrying every hard-won exemption (STRUCT001 single-shape
exemption; STRUCT002/003/005 skip COMPONENT_SET; invisible paints/effects
excluded; empty boundVariables{} isn't a binding; STRUCT004 accepts style OR
fontSize; auto-derived variant names exempt from STRUCT009).

Deliberate change: STRUCT009 now uses core/naming's strict caseMatches
(no more loose /^[a-z0-9-/.]+$/ fallback). Path-like identifiers (variant
property names, e.g. "avatar/size") are split on '/' and '=' and each
segment case-checked independently via a new caseMatchesPath helper.

Test: transcribed v1's struct-fixtures.test.js fixture->rule table verbatim
into test/struct.test.ts against the copied real-Figma fixtures; all 11
cases pass unchanged (verified against v1's checker directly as ground
truth — no expectation had to move for the strictness change).
…suites

Removing "type": "module" from plugins/adhd/package.json fixes a cross-cutting
regression where Node treats v1's CommonJS files under plugins/adhd/lib/** as
ESM, causing the v1 test suites to fail. Nothing in v2 requires this field:
build.mjs is explicitly ESM via its .mjs extension, vitest handles TS/ESM itself,
and dist/ has its own {"type":"commonjs"} marker.

Verified: v1 tests 283/283 pass (96+103+67+17), v2 tests 53/53 pass, typecheck 0,
build fresh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…full kind coverage (review fix)

- Standalone hex now only counts inside string-literal spans on the line
  (computed via a line-local scanner), never in comments/JSX braces/URLs.
- tokensByColor/tokensByDimension Maps were built but never consulted;
  replaced with real precomputed { path, rgba } / { path, px } arrays that
  findNearestToken compares against directly (no re-parsing per match).
- Collapsed duplicated exact->near lookups into matchColorToken + rgbaClose,
  and removed the dead color->dimension fallthrough (color parse now
  returns early).
- Added test coverage for inline-hex, mixed arbitrary-class + inline-hex
  lines, near-match epsilon, and hex-outside-any-string (no finding).
…, pretest build (review fix)

- readChunks wraps readdirSync/readFileSync/JSON.parse and rethrows AdhdError
  naming the failing path + cause, with a "check --figma-chunks points at a
  directory of valid chunk JSON files" fixup; an empty dir gets the same
  treatment ("no chunk files found in <dir>").
- runLint catches assembleChunks' fixup-less AdhdErrors (e.g. the contiguity
  check on unpadded chunk filenames) and fills in a zero-padded-naming fixup
  so the CLI always prints a → hint.
- Off-system file reads (git ls-files result) are now defended per-file: an
  unreadable tracked file is skipped with a stderr note instead of crashing
  the whole lint run.
- package.json: added "pretest": "npm run build" so vitest's subprocess tests
  always run against a freshly built dist/.
…eview fix)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hhff and others added 5 commits July 6, 2026 17:01
Important:
- diff/css: compare bare semantic color tokens (--background etc., domain 'other') as colors, not exact strings — kills false value-drift on oklch↔hex
- payload: flag off-grammar Figma variable names unsyncable (was CI-breaking existence drift)
- payload: flag Primitives collections with >1 mode unsyncable (was silent loss)
- cli: route non-AdhdError failures + --out write errors through exit-2 contract
- off-system test: actually exercise the adhd:off-system skip

Minor:
- payload: flag unresolved bound-primitive ids on the style shell
- cli: drop dead 'export' on fail(); remove 3 unused test imports
- report test: assert no timestamp instead of tautological double-call
- example: add adhd.config.json so /adhd:lint works from the demo consumer

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Important:
- css: @theme inline (Layer-3 exposure vars) no longer modeled as primitives —
  kills ~7 false existence errors on the example's own globals.css
- diff: tighten probable-rename pairing (equal length, one differing segment) —
  stops false-pairing same-valued primitives like color/black vs color/ink
- off-system: normalizeDimension parses rem (x16) — nearest-token suggestions
  now fire for Tailwind's rem-based scale
- lint/report: surface StyleShell.unsyncable reasons into the Cannot-sync section
  (were computed but silently dropped); note git-unavailable off-system skips
- struct: guard node.name before split() — a nameless node no longer crashes the
  whole lint run
- diff test: exercise colorsEqual with a real oklch value (was hollow a===b)

Minor: duplicate-collection-name map collision -> cannotSync; describeValue renders
mixed alias/literal modes; struct severity assertion; dead exports dropped;
bindingless catch in lock.ts; unreachable ternary in payload.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Important:
- diff: unify typography dimensions (rem↔px FLOAT) like spacing/radius —
  idiomatic Tailwind text-size/leading tokens no longer false-drift
- css: guard normalizeColor against bare numbers — culori parses '500' as
  #550000; font-weight/unitless-spacing tokens were being corrupted to hex
- diff: a lock-driven definite rename now still reports coincident value/
  structural drift instead of silently swallowing it

Minor:
- lock: validate nested lock shape, throw the friendly 'Delete adhd.lock.json
  and re-sync' error instead of a downstream cryptic TypeError

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Important:
- diff: make dimension-equivalence a UNIVERSAL fallback instead of a domain
  whitelist — border-width/size/blur/etc. dimension tokens no longer false-drift
  (px<->rem<->unitless unified for any domain; retires the recurring theme)

Minor:
- css: classify() detects @theme inline by token membership, so combined
  modifiers (@theme inline static/reference) are correctly treated as exposure
- lock/config: a present-but-unreadable file (EACCES/EISDIR) now raises a real
  IO error instead of being misreported as 'file not found'

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Minor (robustness): a chunk file containing bare `null` is valid JSON but not
a chunk — readChunks now rejects non-object parsed values, and assembleChunks
guards defensively, so it surfaces as a clean AdhdError with the chunk-dir
fixup instead of a raw 'Cannot read properties of null' TypeError.

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

hhff commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

Adversarial review campaign — converged ✅

Ran a 6-round adversarial review loop: each round fanned out 6 skeptical finders (dead-code, correctness, integration, no-silent-loss, test-quality, robustness) over the whole branch, and every finding was independently verified by a second agent trying to refute it before it counted. Only reproducible defects (concrete input → wrong output) or provably-dead code survived.

Round Confirmed findings
1 13
2 16
3 5
4 4
5 1
6 0 (all six dimensions empty)

39 verified defects fixed. Highest-impact (each would have made /adhd:lint fail on the example app's own token file):

  • Bare semantic color tokens (--background, shadcn-style) were compared as exact strings → oklch(1 0 0) vs #ffffff reported false drift.
  • @theme inline (Tailwind Layer-3 exposure) was modeled as primitives → ~7 phantom existence errors.
  • Typography/dimension tokens in rem weren't unified with Figma's px FLOATs — now a universal dimension-equivalence fallback (no more per-domain whitelist).
  • normalizeColor corrupted bare numbers (500#550000) because culori parses hex-looking digits without #.

Also fixed: off-grammar Figma names / extra Primitives modes / unresolved style bindings now surface in the Cannot-sync report instead of being silently dropped; exit-2 operational contract closed for unreadable/empty/misordered/null chunks and --out write failures; checkStructure no longer crashes on a nameless node; all dead code removed.

Final state: v2 140/140 tests, typecheck clean, committed dist/ matches source, zero unused locals/params in src; v1 283/283 still green (untouched).

Remaining merge gate (unchanged)

Not exercised against a live Figma file (no MCP in the build env). Before merge, run /adhd:lint once against the real configured file to confirm use_figma returns the ExtractChunk shape and serializeStyle's field names match the real Figma styles API.

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.

1 participant