ADHD v2 — Milestone 1: core + lint#10
Conversation
…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>
…ral; pin rename 1:1 guarantees (review fix)
…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>
…xup, dead import, $WORK cleanup
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>
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.
39 verified defects fixed. Highest-impact (each would have made
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/ Final state: v2 140/140 tests, typecheck clean, committed Remaining merge gate (unchanged)Not exercised against a live Figma file (no MCP in the build env). Before merge, run |
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
dist/(freshness-gated in CI), vitest, strict TS.globals.cssreader (per-mode aliases, no silent loss),adhd.config.json+adhd.lock.jsonloaders.figmaruntime harness.adhd figma-script extract+adhd lintwith a strict exit-code contract (0 / 1-check / 2-operational+fixup)./adhd:lintrewritten as a 44-line thin wrapper.Verification
Known merge gate
Not yet exercised against a live Figma file (no MCP in the build env): confirm
use_figmareturns theExtractChunkshape the CLI expects and thatserializeStyle's field names match the real Figma styles API. Run/adhd:lintonce against the real configured file before merging.🤖 Generated with Claude Code