Repository navigation
feat(user-interface): add layered config and /user-interface:setup - #7039
Conversation
user-experience's parser becomes a generated copy of lib/yaml-subset.mjs (ADR 0019); its tests move to lib/. Parser body unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
resolve() merges plugin defaults, userConfig, the user-global file, the project team file and the project personal file, later layers winning per key. It validates each key against the caller's JSON Schema subset, unions *.disable lists, accepts team-only keys in the team layer alone, reports per-key provenance, prose paths, per-layer state and legacy files, and reads files only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A literal key containing "." collided with the nested path of the same name in the merge, so it is now rejected with an error naming layer, file and key, and the lower layer keeps its value. A legacy file is reported once when the project root is the user home. Tests now cover later file layers winning on valid scalars and a __proto__ key under a schema that allows unknown keys, and legacy fixtures hold values no layer resolves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main released user-experience 0.1.1 for the test-selection declarations (#6457) while this branch also bumped to 0.1.1. Keep main's 0.1.1 entry and move this branch's yaml-subset entry to 0.1.2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t scope Three fixtures mapped to no suite, so a change to them alone ran nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The quote-opening test re-ran a regex over the growing line prefix for every quote that did not open, so a line holding a long run of quotes cost quadratic time. Track the last non-blank character instead; the opening rule is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The team and local layers read only regular files, never a symlink, and are invalid when a relative convention home resolves outside the project; every layer refuses a device or FIFO. __proto__, constructor and prototype are rejected as keys. Also: a rejected value is quoted once, an empty disable list does not credit its layer, and a tilde fence hides a config block from the legacy report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uite Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and convention-home helper Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…style files from detect.mjs detect.mjs gains --config (the five-layer cascade with provenance, the convention home from the project's pointer line), --team FILE and --user-config FILE seams, and project.browserslist, project.lint and project.style_files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and refuse an escaping --team home An unusable convention-home pointer now leaves the team and local layers unread (home null, home_error kept), and --team refuses the local layer when the real convention home resolves outside the project. A test pins each css_* userConfig default to reference/defaults.yaml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ma.json The routing validator moves from tests/routing.test.mjs to scripts/lib/routing-validate.mjs; detect.mjs uses it to drop team rows that break the bundled row schema and names each on the team layer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd keep scratch paths out of --team output A team routing row with every required field is validated against the whole row schema, allOf included; the --team seam reports an absent local file at its real path; and a convention home named like "..conf" no longer counts as leaving the project. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…onfig # Conflicts: # lib/config-cascade.mjs # lib/config-cascade.test.mjs # lib/yaml-subset.mjs # lib/yaml-subset.test.mjs # plugins/user-experience/CHANGELOG.md # scripts/shared-copies.txt
…ig layers check reports each setting with its source layer, layer errors, a broken convention home, duplicate personal keys, legacy files, the .local ignore state and a personal file missing from this worktree; apply writes one key in one layer. The README gains the five-layer config section, the fixed defaults, and the regenerated options table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ter and routing team-layer adopter The config-cascade Implementers row states the deviation from "What does not move" and ADR 0044, citing ADR 0061; the glance table is regenerated. The routing-as-data Adopters row records the team-only routing key and the structured personal-layer deviation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…point at upstream behavior A manifest userConfig default is not substituted, so an unset css_* option reads from a lower layer; setup now leaves empty or literal placeholder values out of the userConfig file. Volatile /config and chezmoi behavior becomes pointer records. --user runs the chezmoi managed check, by exit status, before writing the user-global file too, and check reports a null local path or a git check-ignore error without stopping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…without -v -v exits 0 on a negation match, so the status now comes from the plain command. --user takes the user-global path from config.layers, and eval 5 expects the README Options pointer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g per entry - detect.mjs reads .browserslistrc or browserslist only when its real path stays inside the project and it is at most 64 KiB, so a committed symlink cannot print a file outside the checkout into the agent's context. - Team routing is checked entry by entry: an unknown key drops only that key, a bad row, disable or deny entry drops alone, and an unknown routing version drops routing. A row that matches no bundled concern plus id needs the full row schema; disable entries need concern and id. - The plugin README and the routing-as-data Adopters row say team routing is validated but not yet applied by /user-interface:design (#7041). - /user-interface:setup quotes home_error instead of always blaming the pointer line, and apply stops when the target file is a symlink or resolves outside the project. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Claude finished @kyle-sexton's task in 2m 50s —— View job Security review complete
No security issues found in this PR's diff ( What I looked at and why it holds up:
I didn't re-flag the symlink/browserslist confinement or shell-metacharacter handling since both are already fixed and test-covered in this branch's head commit. |
|
Claude finished @kyle-sexton's task in 6m 55s —— View job Review of PR #7039
Scope: Code-review lane only (a No blocking or high-confidence findings. I read
I checked these against the existing test suite (
Not independently verified: I was not able to execute the test suite or any shell commands in this sandbox ( |
|
claude-security-review has reviewed this pull request through 42f55cd; a later push is reviewed from there. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f6a5a3ed8a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
claude-review has reviewed this pull request through 42f55cd; a later push is reviewed from there. |
- detect.mjs marks the `.styl` (Stylus) extension line for the spell checker. - The user-global fixture denies `figma` instead of a name typos flags. - setup's Arguments line leads with its argument-hint, per the argument-hint convention. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- setup states the check-ignore rule the right way round: without -v a negation match exits 1; with -v it exits 0. Adds the pointer record. - setup's userConfig handoff file carries the session id, so concurrent sessions sharing CLAUDE_PLUGIN_DATA cannot overwrite it, and is deleted when the skill finishes. - team.schema.json lets a team row set `style`, as a bundled row may; a test keeps the team row fields equal to the row schema's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 35s —— View job Code review complete
Scope: Incremental review of the diff since 1 finding posted inline on No issues found with the rest of the incremental diff:
|
|
Claude finished @kyle-sexton's task in 1m 59s —— View job Security review complete
No security issues found in the incremental diff ( Scope: per the incremental-review header, only the five files changed since the last security-review pass at What changed and why it's clean:
Instruction-surface check: no standing instruction (guardrail, allowlist/denylist, shell-avoidance rule) is deleted, narrowed, or softened by this diff — the session-scoped temp file change strictly adds isolation, and the Write-tool-only / no-shell-interpolation rule for userConfig values is unchanged. |
The user-interface copy makes scripts/lib/yaml-subset.mjs byte-identical in two plugins, which check-cross-plugin-source-drift.sh rejects unless it is registered. Its dedicated check is sync-shared-copies.sh --check, so it is registered with the cluster line the duplication audit reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 39s —— View job Security review complete
Scope: incremental lane, reviewing only what changed since No security issues found. The diff adds two lines registering the newly-canonical This is a static, plain-text registry with no executable content — it's consumed read-only by
No instruction-surface change, no new file I/O behavior, no injection or traversal surface — this is bookkeeping data only. |
|
Claude finished @kyle-sexton's task in 3m 2s —— View job Code review complete
Scope: Incremental review of the diff since No findings. The change (permalink) registers the new I verified this by reading
I also checked for adjacent gaps the same commit could have had: Every other file in this PR was reviewed at |
setup states only the decision (non-verbose exit status; -v to show the rule) and the pointer record, no restated check-ignore behavior, per the skill-bodies-state-current-rules rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 56s —— View job Security review complete
Scope: incremental lane, reviewing only what changed since No security issues found. The diff (permalink) removes a previously-incorrect restated claim about - `git -C "<project root>" check-ignore <path>` without `-v`: with `-v` it also exits 0 when a
- negation (`!`) rule matches, which means the path is not ignored. Run it with `-v` only to show
+ `git -C "<project root>" check-ignore <path>` run without `-v`; run it with `-v` only to show
the matching rule.This is a text-only documentation-accuracy fix with no code change:
|
|
Claude finished @kyle-sexton's task in 1m 21s —— View job Code review complete
Scope: incremental lane, reviewing only what changed since No findings. This commit ( - `git -C "<project root>" check-ignore <path>` without `-v`: with `-v` it also exits 0 when a
- negation (`!`) rule matches, which means the path is not ignored. Run it with `-v` only to show
+ `git -C "<project root>" check-ignore <path>` run without `-v`; run it with `-v` only to show
the matching rule. Pointer: <https://git-scm.com/docs/git-check-ignore>. As of: 2026-10-11.deletes the restated behavior entirely and keeps only the decision ("decide on the exit status of The unchanged |
Refs: #6594
Summary
user-interfaceadopts the five-layer config cascade from ADR 0061, read through the shared resolverlib/config-cascade.mjs(#6984). It also gains/user-interface:setupto show and change that config. Thecsskeys configure the CSS authoring skill that lands in the next PR. Until then they change no behaviour.Fix
Vendored copies (
scripts/shared-copies.txt, ADR 0019):lib/config-cascade.mjs,lib/yaml-subset.mjsandlib/resolve-convention-home.sh.Config layers, later wins:
reference/defaults.yamlcss_browser_target,css_important,css_layer,css_token_fallback~/docs/conventions/user-interface.{yaml,md}<home>/user-interface.{yaml,md}<home>/user-interface.local.{yaml,md}The schema is
reference/team.schema.json, withadditionalProperties: false.routingis accepted only in the team layer.scripts/detect.mjs:--configadds a top-levelconfigholding the resolver output with per-key provenance, plushome, andhome_errorwhen the pointer line is broken.--team FILEis a test seam.--user-config FILEpasses userConfig values as data, so they never reach a shell.projectobject gainsbrowserslist {query, source},lint {stylelint, eslint_css, config_paths}andstyle_files [kinds]. All existing fields are unchanged.docs/conventionsfile cannot load routing.scripts/lib/routing-validate.mjs, moved out oftests/routing.test.mjsunchanged. An unknown key drops only that key; a bad row,disableordenyentry drops alone. A re-rank of a bundled row (same concern and id) gets per-field checks; a new or complete row gets the whole row schema./user-interface:designdoes not apply team routing yet (feat(user-interface): apply team routing in /user-interface:design #7041); the README says so..browserslistrcorbrowserslistfile is read only when its real path stays inside the project and it is at most 64 KiB./user-interface:setup check|apply [--user|--local](disable-model-invocation: true):checkshows each value with its provenance, plus errors, legacy files, the.localignore state andhome_error. It warns when this worktree lacks a.localfile that the main checkout has.applyedits one key in one layer. With--userit edits the chezmoi source when chezmoi manages the file.Convention docs: a
user-interfaceImplementers row indocs/conventions/config-cascade/README.mdand an updated Adopters row indocs/conventions/routing-as-data/README.md. Both record the layer-location deviation that ADR 0061 decides.README: a config section and a fixed-defaults list. Changelog fragment under
.changes/user-interface/.Verification
tests/detect.test.sh57/57,tests/defaults.test.sh3/3,tests/routing.test.sh16/16.style, per-session userConfig handoff file) fixed in 7b16433; CI typos and argument-hint findings fixed in ea362a4..browserslistrcread outside the project; one bad routing entry dropping all ofrouting; new team rows anddisableentries passing without required fields; setup'shome_errorwording andapplysymlink stop). Team routing not applied by design: documented, follow-up feat(user-interface): apply team routing in /user-interface:design #7041.scripts/sync-shared-copies.sh --check: 411 copies match.--check-bump origin/mainpasses.check-skill.sh --require-evals plugins/user-interface/skillsexits 0.sync-config-cascade-semantics.py --checkexits 0. Fragment check, markdownlint and typos on the changed docs are clean.*.disableunion across all five layers are checked against a scratch project.css_importantandcss_token_fallbackdeclare manifest defaults equal todefaults.yaml, because anoptionsfield needs one. Manifest defaults are not substituted (docs/extensibility-contract-smoke-tests.md), so an unset key showsdefaultsprovenance. A test keeps the two default values equal.Related
/user-interface:write-css.🤖 Generated with Claude Code
PLAN.md
Brief
Restatement: CONFIRMED (user, 2026-10-10, "Confirmed.")
TLDR
/user-interface:write-css: CSS techniques absorbed from good-css (MIT, pinnedb645ab15b9fc), restructured by topic as the first source of a long-lived master CSS skill, with a read-only review mode/user-interface:setupskill and a five-layer config cascade (plugin defaults, userConfig scalar mirror, user-global, project team, project.local) read through one shared resolver; everything sensible is configurabledocs/upstream/good-css.mdattribution record,designdetector extension and routing row, presence-gated cross-references from prototype and visualizationGoal
Agents writing or reviewing CSS in any consuming project apply modern, self-adapting CSS techniques without fighting that project's own design system, stack, browser targets or conventions. The skill discovers each project's stack and styling setup, fills only the gaps the project leaves, says which of its own defaults it used, and lets teams and individuals reconfigure every behavior that a consumer could reasonably need to change.
Constraints
user-interfaceplugin; no new plugin (C7, Q2)/plugin:skillnames, presence-gated (C12, Q6, Q20)docs/upstream/good-css.mdand the CHANGELOG, never in skill files; MIT notice travels with copies, and the bundled linter keeps avendor/LICENSE(C4, C8, C13, Q5, Q21).localpersonal overlay; locations resolved through one shared resolver,docs/conventionsonly a default (C16, Q34, Q35, Q39, D14, R5)/planning:plan(Q8)Acceptance criteria
/user-interface:write-cssexists inplugins/user-interface, passes/skill-quality:check, and its description triggers on CSS authoring and review requests without overlapping/user-interface:designdocs/upstream/good-css.mdholds the pinned commit, recheck trigger (new commit toPRACTICES.mdor a Baseline change for a used technique), per-technique credits and the MIT notice, and the CHANGELOG entry carries the notice/context7:lookup,/discovery:research,/discovery:explore, else official docs), answer the six-question checklist, and verify compiled output when scoping or a build step rewrites CSStoken_fallback(defaultask)@supportsfallback or is skipped with a stated reason@layeronly when the project has no layer system; otherwise it uses the project's mechanism;!importantappears only with a stated reason@eslint/cssrules when present, and lists findings that contradict project conventions under a suppressed heading rather than dropping them## Design> Contracts,reference/team.schema.json) is configurable at the documented layers, read through one shared resolver with no hard-coded path in skill text; a written list names every fixed default and why it is fixed/user-interface:setupsupportscheckandapply, writes the team layer at the resolved convention home and the user-global layer with--userdesign's detector reports browserslist, token sources and lint config;design's routing data has acss-authoringrow pointing at the new skill/prototype:explore-directionsand/visualization:visualizereference the new skill presence-gatedCaptured assumptions
check-css.mjsstays dependency-free and MIT: revisit if the upstream license or the script changes at the recheck triggerfree-text(default): no convention-home pointer region in this repository's AGENTS.mdOut-of-scope
reviewplugin to consume (Q17)userConfigoptions beyond the four scalarcss_*keys (Q37, revised by R5)Deferred questions
check-css.mjsunmodified behind a wrapper or modified, defer until planning; arbiter: /planning:planDesign
Threads:
design/design-threads.md(D1-D14, plan-review fixes R1-R9). Full contracts:design/contracts.md.Module layout
Dependency direction: skills -> scripts/detect.mjs -> {lib/resolve-convention-home.sh, scripts/lib/config-cascade.mjs -> scripts/lib/yaml-subset.mjs}; write-css review mode -> scripts/review.mjs -> vendor/check-css.mjs, with the resolved config passed in as JSON (R4). The shared lib spawns nothing (R1). No plugin depends on another plugin's files; cross-plugin references are public
/plugin:skillnames only.Contracts
Config cascade, later wins per key:
Merge: scalars later-wins;
*.disablelists union; invalid value rejected with a message, lower layer keeps the key;routingaccepted only in layer 4, rejected in 2, 3 and 5 (R6); every value carries its source layer; prose layers 3-5 concatenate; the detected project design system outranks every configured default. Layers 3 and 5 deviate from docs/conventions/config-cascade/README.md "What does not move" (lines 30-36, 84-86); the deviation is recorded by the ADR in Phase 1 and the Implementers row (R8).Settings schema (
reference/team.schema.json,additionalProperties: false, block-style YAML; yaml-subset rejects{):userConfig (layer 2, R5):
css_browser_target(string),css_important(string, options avoid, utilities-only, allow),css_layer(string),css_token_fallback(string, options ask, propose-token, literal). Lists live only in files. The skill writes the${user_config.css_*}values to a temp JSON file with the Write tool and passes--user-config FILEto detect.mjs; substituted text never reaches a shell parser.Resolver:
homeis resolved by the caller (R1);teamOnlylists keys accepted only in layer 4, so the lib holds no plugin key names.Detector (R2, R3):
--configadds top-levelconfig(resolver output);--team FILEreplaces the resolved team file (test seam, user-experience meaning);--user-config FILEsupplies layer 2;projectgainsbrowserslist {query, source},lint {stylelint, eslint_css, config_paths},style_files [kinds].Review output (
scripts/review.mjs --config FILE|JSON <paths>; exit 0 clean, 1 findings, 2 usage or crash; never spawns detect.mjs):Setup:
/user-interface:setup check|apply [--user|--local]; check reports values with provenance, invalid keys, duplicate layer-2/3 keys, legacy files with a migrate offer,.localignore state, and warns when this worktree lacks<home>/user-interface.local.*while the main checkout has one; apply edits one key per layer, adds the gitignore entry, adds the user-global pointer after a yes (chezmoi source when managed), offers to save stack-checklist answers.Vendored upstream (pin
b645ab15b9fcd059c0227c21b6a144bb70639f1a,git hash-objectblob SHAs):scripts/check-css.mjs6fa72714ba18cf02f698fa8015f63df043b3af85->skills/write-css/vendor/check-css.mjs;LICENSE66a325d82e56f2f41ebd14812517d6c50f5b66da->skills/write-css/vendor/LICENSE;PRACTICES.md7d746e65d56ee4910572b75738051482f4735f38(drift source only). Local copies:.work/good-css/raw/.Routing row (JSON, in routing.json):
{"concern":"css-authoring","rank":1,"id":"/user-interface:write-css","kind":"skill","detect":"user-interface","account":"none","status":"confirmed","platforms":["windows","macos","linux"],"as_of":"2026-10-10"}.Variation verdicts
css:setting: varies, through the five-layer cascade (D3, D5, D14); lists only through files (R5)concern: css-authoring, team layer only (D11, R6)reference/configuration.mdand the README: topic split of reference files (merge target for later sources), review output sections and exit codes (contract for callers), the four-step process order (step 3 depends on 1-2), project-first precedence (C2)Conventions followed and deviations
<home>/<plugin>.yaml, routing rows)~/docs/conventions/, overlay<home>/*.local.*, userConfig scalar mirror), recorded in the Phase 1 ADR and the Implementers row (R8)--teamseam), plugins/user-experience/skills/setup/SKILL.md (team read and setup flow)plugins/*/skills/*/vendor/**)Plan
Approval: approved by the user in session on 2026-10-10 ("sounds good, approved, go with recommendations"), covering review changes R1-R9 and the Q37 reconfirmation
Scale: Large (an ADR, a new shared repo-level library vendored into plugins, a new config surface, two new skills, five pull requests).
Goal
What: Record the five-layer cascade decision, then ship
/user-interface:write-css(good-css techniques absorbed by topic, project-first, four-step stack discovery, read-only review mode), the/user-interface:setupskill and the layered config it manages, read through one shared resolverlib/config-cascade.mjs.Why: Agents writing or reviewing CSS should apply modern techniques without overriding a project's design system, stack, browser targets or conventions, and teams and individuals must be able to reconfigure every behavior a consumer could reasonably change (Brief: Goal, C2, C18).
Done when: the ADR PR and PRs A-D are merged with
ci-statusgreen; every Brief acceptance criterion maps to a phase Sanity Check below that passed; the three-arm eval run is recorded on PR C; the follow-up issue exists.Standards grounding
AGENTS.md: open as draft, Conventional Commits title, stop list, fragment changelog, scope test.claude/rules/pr-body-contract.md:Closes/Refs:/No related issue:first line, Summary/Fix/Verification/Related.claude/rules/skill-bodies-state-current-rules.md: pointer + as-of date + recheck trigger for volatile specifics (Baseline status),## Nextsection.claude/rules/eval-case-transcripts.md: no raw transcripts inevals/**.claude/rules/cost-claims.md: no prices; eval cost points at the evals plugin ceilingplugins/playbooks/skills/skill-authoring/reference/skill-criteria.md: Frontmatter, Descriptions, Body, Reference files, History and provenance, Measurementdocs/conventions/config-cascade/README.md: The layers (30-36), What does not move (84-86), Merge semantics, Deviations, Implementers (+ generated "Semantics at a glance" viascripts/sync-config-cascade-semantics.py)/architecture:record-decisionintodocs/adr/docs/conventions/routing-as-data/README.md: team file<home>/<plugin>.yaml, Adopters tablescripts/shared-copies.txt,scripts/sync-shared-copies.sh(--check,--check-bump)scripts/fragment-plugins.txt: user-interface (line 86), prototype (70), visualization (88) are fragment mode; user-experience is notdocs/upstream/humanlayer-skills.mdshape;.github/workflows/maintenance-check-upstream-drift.ymlmatrix rows (skill,script,args,drift,unverifiable).github/workflows/pr-require-checks.yml:809-816typos exclusionplugins/*/skills/*/vendor/**plugins/user-experience/skills/setup/SKILL.md(check/apply, per-key block YAML),plugins/developer-experience/skills/setup/SKILL.md(--userpointer flow, chezmoi check)Approach
The work ships as five pull requests, each on its own worktree and feature branch (C14), in dependency order. Q31 is resolved here. [EXEC-SHAPE]
docs/adr-five-layer-config-cascadedocs(adr): record the five-layer config cascadefeat/config-cascade-libfeat(lib): add shared config-cascade resolver and canonical yaml-subsetplugin.jsonversion bump +CHANGELOG.mdentry (not fragment mode; itsyaml-subset.mjsbecomes a generated copy and its yaml-subset suite moves tolib/)feat/user-interface-configfeat(user-interface): add layered config and /user-interface:setup.changes/user-interface/fragmentfeat/good-css-skill(this worktree)feat(user-interface): add /user-interface:write-css.changes/user-interface/fragment carrying the MIT noticefeat/write-css-pointersfeat: point prototype and visualization at /user-interface:write-css.changes/prototype/and.changes/visualization/Every PR opens as a draft and its body follows the PR body contract:
Refs: #6594for the ADR PR, A and B, whose config shape that issue may change; C and D open with aRefs:line to a tracking issue if a search finds one, elseNo related issue: <reason>; then## Summary,## Fix,## Verification,## Related. Ready-flip and merge follow AGENTS.md's stop list (ask the user). B and C each start from main after the previous PR merges, becausesync-shared-copies.sh --check-bump <base>compares canonicals against the base [EXEC-SHAPE]; stacking on the previous branch is the fallback if a PR stalls in review [FALLBACK].Q33 (bundle
check-css.mjsunmodified or modified) is resolved by Design D6: unmodified vendored copy behindscripts/review.mjs, atskills/write-css/vendor/(R7). Not re-decided.Dependencies
lib/resolve-convention-home.sh(existing canonical), user-experiencescripts/lib/yaml-subset.mjs(promoted), upstreamscripts/check-css.mjsandLICENSEatb645ab15b9fc(.work/good-css/raw/, data only).skills/design/SKILL.md,tests/detect.test.mjs(line 55 asserts the exactprojectkey set),README.md,prerequisites.jsonmentiondetect.mjs;tests/routing.test.mjsandrouting.schema.jsonreadrouting.json; user-experiencescripts/detect.mjsandtests/yaml-subset.test.mjsimportyaml-subset.mjs; CI runssync-shared-copies.sh --check,run-plugin-tests.sh,sync-config-cascade-semantics.py --check, typos (vendor excluded) and the changelog-fragment checks; the weekly drift workflow gains one row.Test strategy
Test-first (Red-Green-Refactor) for every script; evals before skill text (Brief AC). Test types per
/testing:planclassification: script logic gets deterministicnode --testsuites behind*.test.shwrappers (Design D10); skill behavior getsclaude plugin evalcases. Every detector run in tests and sanity checks passes the fixture seams (--home <scratch>,--plugin-list-json plugins/user-interface/tests/fixtures/plugin-list.json,--mcp-list plugins/user-interface/tests/fixtures/mcp-list.txt), so no liveclaudecall.Test boundaries (approval settles them):
lib/yaml-subset.mjsexported parse APIlib/yaml-subset.test.mjsmoved fromplugins/user-experience/tests/yaml-subset.test.mjs, red until the file exists; the user-experience suite is removedlib/config-cascade.mjsresolve({plugin, projectRoot, home, schema, defaults, userConfig, teamOnly})*.disableunion, invalid value keeps lower layer with an error,teamOnlykey rejected in layers 2, 3, 5, provenance per key, prose order 3-4-5,legacy[]report, absent layersstate: absent, no spawn of any scriptplugins/user-interface/reference/defaults.yamltests/defaults.test.mjsparses the shipped file with yaml-subset and validates it againstteam.schema.jsonplugins/user-interface/scripts/detect.mjsCLI JSONtests/detect.test.mjs:project.browserslist,project.lint,project.style_files;--configtop-levelconfigwith per-key provenance across layers 1-5;--team FILEseam;--user-config FILE; line 55 key-set assertion updatedreference/team.schema.jsonroutingrejected through the resolver testsreference/routing.jsontests/routing.test.mjsasserts acss-authoringrank-1 own-plugin rowskills/write-css/scripts/review.mjsCLI## Findings/## Suppressed/## Judgment, rule ids,rules.disablesuppression from--configJSON, exit 0 clean / 1 findings / 2 usage or crash (a missing path must exit 2, not 1)scripts/check-good-css-drift.sh --checktests/check-good-css-drift.test.shwith a stubbed fetch: same SHAs exit 0, changed SHA exit 1, fetch failure exit 2claude plugin evalcasesplugins/user-interface/evals/css-*Edge cases: empty and comment-only YAML layers; a key valid in layer 4 and invalid in layer 5; a symlinked or
.claude/convention home (FAIL, as in user-experience setup); a file with no<style>block; an uppercase.CSSsuffix; browserslist inpackage.jsonversus.browserslistrc; a userConfig value containing shell metacharacters (reaches detect.mjs only as file content).Eval sizing (Q32, resolved here) [EXEC-SHAPE]: 17 cases. Cases 1-8 run in three arms (body: the skill with the rule in SKILL.md; reference: a variant plugin copy under
.work/good-css/arm-reference/with that rule moved to its topic reference; without: no plugin) atruns: 5; cases 9-17 run with versus without atruns: 3, matching the existing ten cases (for exampleplugins/user-interface/evals/conflicting-request-project-wins/case.yaml).css-rule-hover,-logical,-outline,-ease-in,-color,-transition-all,-motion,-durationcss-scoped-stylescss-existing-tokenscss-no-tokenscss-token-fallback-asktoken_fallback: ask)css-project-convention-winscss-browser-target@supportsor a stated skipcss-research-tool-missingcss-review-modepath:linefindings, linter first, Suppressed headingcss-control-no-triggerKeep-in-body threshold (R9): an always-on rule stays in SKILL.md only when the body arm passes at least 4 of 5 runs and beats the reference arm by at least 2 runs; otherwise it moves to its topic reference (C6). The with-versus-without delta is still recorded. Basis: judgment inside the deferred Q32. Cost:
/evals:plugin-evalprints the estimate per arm and passes the evals plugin's configuredmax_cost_usdceiling; if the estimate exceeds it, run by tag in batches instead of raising the ceiling unasked. If the CLI cannot point a run at the variant copy, run the reference arm from a temporary branch holding the variant. [FALLBACK]Phase 1: Record the five-layer cascade decision (ADR PR, before PR A) [DONE] (ADR 0061, draft PR #6976; merge pending)
Review: architecture
/architecture:record-decisionfor the target five-layer cascade: user-global~/docs/conventions/<plugin>.{yaml,md}, project personal<home>/<plugin>.local.{yaml,md}, userConfig scalar mirror, one shared resolverlib/config-cascade.mjsthat takes the resolved home as input; user-interface as first adopter; links feat(conventions): one setup lifecycle for where plugins store team conventions and settings #6594 (which may supersede it); states the deviation from docs/conventions/config-cascade/README.md "What does not move" (lines 30-36, 84-86) and from ADR 0044's structured team config. [EXEC-SHAPE]Refs: #6594).ls docs/adr | grep -ci "config-cascade"returns at least 1;grep -c "6594" docs/adr/*config-cascade*.mdreturns at least 1;grep -ci "what does not move" docs/adr/*config-cascade*.mdreturns at least 1.Phase 2: Canonical yaml-subset (PR A, structural) [DONE] (2bd8e40)
Review: architecture
grep -rn "yaml-subset" plugins lib scriptsand record every importer (known: user-experiencescripts/detect.mjs,tests/yaml-subset.test.mjs); confirm none depends on a path other thanscripts/lib/yaml-subset.mjs.plugins/user-experience/tests/yaml-subset.test.{mjs,sh}tolib/yaml-subset.test.{mjs,sh}pointing atlib/yaml-subset.mjs; the tests live once.lib/yaml-subset.mjswith the user-experience file's body unchanged; registerlib/yaml-subset.mjs plugins/user-experience/scripts/lib/yaml-subset.mjsinscripts/shared-copies.txt; runscripts/sync-shared-copies.shso the user-experience file becomes a generated copy (header only). Migrating user-experience onto the resolver stays a follow-up (Design D4). [EXEC-SHAPE]plugin.jsonpatch version and add aCHANGELOG.mdentry.bash lib/yaml-subset.test.shexits 0;test ! -e plugins/user-experience/tests/yaml-subset.test.mjsexits 0;bash scripts/sync-shared-copies.sh --checkexits 0;diff <(git show main:plugins/user-experience/scripts/lib/yaml-subset.mjs) lib/yaml-subset.mjsexits 0.Phase 3: Shared resolver lib/config-cascade.mjs (PR A) [DONE] (b0c6e17, 7858090; verifier 14/14 PASS)
Review: architecture
lib/config-cascade.test.mjs+.test.shwith fixtures underlib/fixtures/config-cascade/covering every resolver edge case in the Test strategy table.resolve()per Design Contracts (five layers, merge rules,teamOnly, provenance,layers[]state,legacy[]);homeis an input, never resolved inside the lib (R1); no plugin-specific key names.bash lib/config-cascade.test.shexits 0;grep -cwE "user-interface|css" lib/config-cascade.mjsreturns 0;grep -cE "spawn|execFile|resolve-convention-home" lib/config-cascade.mjsreturns 0;bash scripts/run-plugin-tests.sh --list | grep -c "lib/config-cascade.test.sh"returns 1.Phase 4: user-interface config surface and detector (PR B) [DONE] (811dba8..953aa2c; verifiers 10/10 and 7/7 PASS; detect 51/51)
Review: code-design
grep -rn "detect.mjs\|routing.json" plugins/user-interface .github scriptsand list each parser of the detector JSON (known:skills/design/SKILL.md,tests/detect.test.mjsline 55 exact key set,README.md,prerequisites.json).scripts/shared-copies.txt:lib/config-cascade.mjsandlib/yaml-subset.mjsintoplugins/user-interface/scripts/lib/,lib/resolve-convention-home.shintoplugins/user-interface/lib/; run the sync script.reference/defaults.yaml(layer 1, block-style values from Design Contracts) withtests/defaults.test.mjsloading and validating it;reference/team.schema.json(additionalProperties: false,cssplus the existingroutingshape,routingteam-only);detect.mjsresolves the home throughlib/resolve-convention-home.sh(as user-experiencedetect.mjs:150) and passes it toresolve(), adds--config,--team FILEseam,--user-config FILE, andproject.browserslist,project.lint,project.style_files; tests intests/detect.test.mjsplus fixtures, line 55 updated..claude-plugin/plugin.jsonuserConfig:css_browser_target,css_important(options),css_layer,css_token_fallback(options), per R5.scripts/shared-copies.txtplugins/user-interface/scripts/lib/config-cascade.mjsplugins/user-interface/scripts/lib/yaml-subset.mjsplugins/user-interface/lib/resolve-convention-home.shplugins/user-interface/reference/defaults.yamlplugins/user-interface/reference/team.schema.jsonroutingteam-onlyplugins/user-interface/scripts/detect.mjs--config,--team,--user-config, project fieldsplugins/user-interface/tests/detect.test.mjsplugins/user-interface/tests/defaults.test.{mjs,sh}plugins/user-interface/tests/fixtures/<new>plugins/user-interface/.claude-plugin/plugin.jsoncss_*userConfig keysplugins/user-interface/skills/design/SKILL.mdbash plugins/user-interface/tests/detect.test.shexits 0;bash plugins/user-interface/tests/defaults.test.shexits 0;bash scripts/sync-shared-copies.sh --checkexits 0;node plugins/user-interface/scripts/detect.mjs --project plugins/user-interface/tests/fixtures/with-ds --home "$(mktemp -d)" --plugin-list-json plugins/user-interface/tests/fixtures/plugin-list.json --mcp-list plugins/user-interface/tests/fixtures/mcp-list.txt --config | node -e 'const j=JSON.parse(require("fs").readFileSync(0));process.exit(["browserslist","lint","style_files"].every(k=>k in j.project)&&j.config&&"provenance" in j.config?0:1)'exits 0;node -e 'const u=require("./plugins/user-interface/.claude-plugin/plugin.json").userConfig;process.exit(Object.keys(u).filter(k=>k.startsWith("css_")).sort().join()==="css_browser_target,css_important,css_layer,css_token_fallback"?0:1)'exits 0.Phase 5: /user-interface:setup and convention rows (PR B) [TODO]
plugins/user-interface/skills/setup/SKILL.md,disable-model-invocation: true,check|apply [--user|--local]per Design Contracts, modeled on/user-experience:setupand the/developer-experience:setup--userflow (edit the chezmoi source when the file is managed); writes userConfig values to a temp file with the Write tool, never into a shell command;checkwarns when this worktree has no<home>/user-interface.local.*while the main checkout (first entry ofgit worktree list --porcelain) has one.plugins/user-interface/skills/setup/evals/evals.json: cases for check output showing provenance per key, the missing-.local-in-worktree warning, and apply writing one key.docs/conventions/config-cascade/README.mdImplementers row for user-interface recording the deviation from "What does not move" and citing the Phase 1 ADR; runscripts/sync-config-cascade-semantics.py.docs/conventions/routing-as-data/README.mdAdopters row for user-interface: team-layer implementer withcsskeys, routing team-only, and the structured personal-layer deviation.scripts/new-changelog-fragment.sh; open PR B as a draft.bash plugins/skill-quality/scripts/check-skill.sh --require-evals plugins/user-interface/skillsexits 0;python3 scripts/sync-config-cascade-semantics.py --checkexits 0;grep -c "plugins/user-interface" docs/conventions/config-cascade/README.mdreturns at least 1;grep -c "provenance" plugins/user-interface/skills/setup/evals/evals.jsonreturns at least 1;ls .changes/user-interface/ | wc -lreturns at least 1.Phase 6: Eval suite before skill text (PR C) [TODO]
plugins/user-interface/evals/css-*/(case.yaml,scaffold.sh,graders/*.md,samples/*.json) andplugins/user-interface/skills/write-css/evals/evals.json; cases 1-8 atruns: 5, cases 9-17 atruns: 3; no raw transcripts (eval-case rule). [EXEC-SHAPE]/evals:plugin-eval; record per-case pass counts in the PR C body.plugins/user-interface/evals/css-rule-{hover,logical,outline,ease-in,color,transition-all,motion,duration}/plugins/user-interface/evals/css-scoped-styles/plugins/user-interface/evals/css-existing-tokens/plugins/user-interface/evals/css-no-tokens/plugins/user-interface/evals/css-token-fallback-ask/token_fallbackplugins/user-interface/evals/css-project-convention-wins/plugins/user-interface/evals/css-browser-target/plugins/user-interface/evals/css-research-tool-missing/plugins/user-interface/evals/css-review-mode/plugins/user-interface/evals/css-control-no-trigger/plugins/user-interface/skills/write-css/evals/evals.jsonpython3 -I plugins/evals/skills/validate/scripts/validate-cases.py plugins/user-interface/evalsexits 0;ls -d plugins/user-interface/evals/css-* | wc -lreturns 17;grep -L "runs: 5" plugins/user-interface/evals/css-rule-*/prompt.mdprints nothing (prompt-only cases carryruns:in prompt.md frontmatter); every other css-* case setsruns: 3in its case.yaml or prompt.md.Phase 7: Review script and vendored linter (PR C) [DONE] (0645a5d, 57a3f5f, 7a50241; verifier 10/10 PASS; review.test.sh 17/17)
Review: security
scripts/check-css.mjsandLICENSEunmodified from.work/good-css/raw/toplugins/user-interface/skills/write-css/vendor/(Design D6, R7, Q33).plugins/user-interface/tests/review.test.mjs+.test.shwith CSS and HTML fixtures and a config JSON fixture (each rule id, arules.disablesuppression, a missing path, a crash path).skills/write-css/scripts/review.mjsimportscheck()/checkFile()from../vendor/check-css.mjs, expands globs, reads the resolved config from--config FILE|JSON(never spawns detect.mjs, R4), prints the three sections with rule ids, exits 0/1/2.bash plugins/user-interface/tests/review.test.shexits 0;git hash-object plugins/user-interface/skills/write-css/vendor/check-css.mjsprints6fa72714ba18cf02f698fa8015f63df043b3af85;git hash-object plugins/user-interface/skills/write-css/vendor/LICENSEprints66a325d82e56f2f41ebd14812517d6c50f5b66da;node plugins/user-interface/skills/write-css/scripts/review.mjs --config '{}' does-not-exist.css; test $? -eq 2exits 0;grep -cE "detect\.mjs|spawn" plugins/user-interface/skills/write-css/scripts/review.mjsreturns 0.Phase 8: write-css skill, references, routing, attribution and drift row (PR C) [TODO]
Review: code-design
grep -rn "concern" plugins/user-interface/tests/routing.test.mjs plugins/user-interface/reference/routing.schema.jsonto confirm the row shape the schema and tests accept.SKILL.md: precedence, four-step process (config read throughdetect.mjs --config, userConfig values written to a temp file with the Write tool), six-question checklist, always-on rules (provisional until the eval result), Gotchas from upstream "Left out on purpose", review summary (detect.mjs --configthenreview.mjs --config), arguments (--no-lint,--show-suppressed, browser target),## Next; no credits or source links.routing.jsoncss-authoringrow (Design Contracts) + atests/routing.test.mjscase;reference/types/web.mdconcern list and "When nothing covers the concern" point at write-css;skills/design/SKILL.mdroutes css-authoring.reference/configuration.md: topic split, review output sections and exit codes, four-step process order, project-first precedence);docs/upstream/good-css.md(pin, blob SHAs from Design Contracts, recheck trigger, per-technique credits, MIT notice, in the humanlayer record shape); a fragment carrying the MIT notice.plugins/user-interface/scripts/check-good-css-drift.sh(--check; exit 0 none, 1 drift, 2 unverifiable; modeled onplugins/playwright/skills/playwright/scripts/update.sh) +tests/check-good-css-drift.test.sh; add its row to.github/workflows/maintenance-check-upstream-drift.yml(skill: user-interface:write-css,args: --check,drift: "exit:1",unverifiable: "exit:2") coveringscripts/check-css.mjs,LICENSEandPRACTICES.md.plugins/user-interface/skills/write-css/SKILL.mdplugins/user-interface/skills/write-css/reference/{foundations,layout,spacing-and-shape,text-and-media,interaction,motion,show-and-hide,scroll-and-viewport}.mdplugins/user-interface/skills/write-css/reference/{stack-discovery,review,configuration}.mdplugins/user-interface/reference/routing.jsoncss-authoringrowplugins/user-interface/tests/routing.test.mjsplugins/user-interface/reference/types/web.mdplugins/user-interface/skills/design/SKILL.mdplugins/user-interface/README.mdplugins/user-interface/scripts/check-good-css-drift.shplugins/user-interface/tests/check-good-css-drift.test.sh.github/workflows/maintenance-check-upstream-drift.ymldocs/upstream/good-css.md.changes/user-interface/<fragment>.mdR=plugins/user-interface/skills/write-css/referenceandU=plugins/user-interface:bash plugins/skill-quality/scripts/check-skill.sh --require-evals plugins/user-interface/skillsexits 0;bash $U/tests/routing.test.shexits 0;bash $U/tests/check-good-css-drift.test.shexits 0.awk -F'|' '/^\| [0-9]+ \|/{gsub(/ /,"",$3);print $3}' .work/good-css/upstream-map.md | wc -lreturns 47, andawk -F'|' '/^\| [0-9]+ \|/{gsub(/ /,"",$3);print $3}' .work/good-css/upstream-map.md | while read -r s; do grep -rqwF -- "$s" $R || echo "MISSING $s"; doneprints nothing.for s in oklch-color fluid-sizes-with-clamp content-grid-with-breakouts stack-layers-with-grid carousel-on-native-scroll no-rubber-band-bounce-on-desktop; do grep -rhA40 -wF -- "$s" $R | grep -qi "baseline" || echo "NO POINTER $s"; doneprints nothing.grep -rnE "^#+ Next\b" $Rprints nothing;grep -rlE "animation-timeline: *view\(|base-select" plugins/user-interface/skills/write-cssprints nothing.for k in "topic split" "exit code" "four-step" "project-first"; do grep -qi -- "$k" $U/README.md && grep -qi -- "$k" $R/configuration.md || echo "MISSING $k"; doneprints nothing.grep -rciE "vojta|holik|good-css\.com|github\.com/vojtaholik" plugins/user-interface/skills/write-css | grep -v ':0$'prints nothing;grep -rcE "(Chrome|Firefox|Safari) [0-9]+" $R | grep -v ':0$'prints nothing;grep -c "b645ab15b9fc" docs/upstream/good-css.mdreturns at least 1;grep -cE "6fa72714ba18cf02f698fa8015f63df043b3af85|66a325d82e56f2f41ebd14812517d6c50f5b66da" docs/upstream/good-css.mdreturns at least 2.bash plugins/skill-quality/scripts/check-listing-budget.sh plugins/user-interface/skills | grep -c "WARN"returns 0.grep -c "user-interface:write-css" .github/workflows/maintenance-check-upstream-drift.ymlreturns 1;bash $U/scripts/check-good-css-drift.sh --checkexits 0 or 1 (2 means it could not verify).Phase 9: Presence-gated pointers (PR D) [TODO]
/user-interface:write-cssreference each inplugins/prototype/skills/explore-directions/SKILL.mdandplugins/visualization/skills/visualize/SKILL.md; one fragment per plugin. One PR for both: the same one-line change, and each plugin keeps its own fragment. [EXEC-SHAPE]grep -c "/user-interface:write-css" plugins/prototype/skills/explore-directions/SKILL.md plugins/visualization/skills/visualize/SKILL.mdreturns at least 1 for each;bash plugins/skill-quality/scripts/check-skill.sh plugins/prototype/skills plugins/visualization/skillsexits 0.Phase 10: Follow-up issue (close-out after PR A merges) [DONE] (#7027 filed after an empty search; linked in #6984 ## Related)
gh issue list --state all --search 'user-experience config-cascade resolver in:title,body' --json number,title,state.## Relatedand skip creation (comment on it only if this session opened it).gh issue create --title 'refactor(user-experience): read team config through lib/config-cascade.mjs' --body '<scope, Refs: #6594, PR A link>'; the body also names the twin parsersplugins/code-metrics/scripts/yaml_subset.pyandplugins/multi-agent/scripts/yaml-subset.awkas candidates to retire ontolib/yaml-subset.mjsor keep with a reason. [FALLBACK]gh pr view <A> --json body -q .body | grep -cE "#[0-9]+"returns at least 1;gh issue view <N> --json body -q .body | grep -cE "yaml_subset.py|yaml-subset.awk"returns at least 2.Files affected
Listed per phase above. Totals: 1-2 files in the ADR PR; about 12 in PR A (lib, moved tests, registry, the user-experience copy, version, changelog); about 18 in PR B (adds defaults test and setup evals); about 85 in PR C (17 eval case directories of roughly four files each, 11 references, the skill, scripts, vendor, drift script and row, routing, docs); 4 in PR D.
Alternatives considered
resolve-convention-home.shitself--teamas the resolved-config output switch--team FILE(R3)*.disable) in userConfigmultipleoptions are hidden from /config, so the value would be invisible to the user (R5)multipleoptions in /configroutingin every layerskills/write-css/scripts/vendor/plugins/*/skills/*/vendor/**; diverges from playwright (R7)yaml-subset.mjsunregistered (two hand copies)--check-bumpcost on user-experience proves blocking; then move it to the follow-up issuecheck-css.mjsruns: 3for the rule casesRisks and mitigations
sync-shared-copies.sh --checkfor every PRlegacy[]gives a migrate path; the ADR states #6594 may supersede itruns: 5)detect.mjsconsumer asserts the full key settests/detect.test.mjs:55updated in Phase 4; pre-flight lists any otherBlast radius
Blast radius: MEDIUM. One ADR, a new repo-level canonical lib and four
shared-copies.txtregistrations under a repo-wide CI gate, one new row in the informational weekly drift workflow, and one other plugin (user-experience) gets a header-only change, a moved test suite and a version bump; every behavior change is additive inside user-interface, the resolver has no existing consumer, and each PR reverts on its own.Execution shape
Dependency graph: 1 (ADR PR) → 2 → 3 (PR A) → 4 → 5 (PR B) → {6, 7} → 8 (PR C) → 9 (PR D); 10 after PR A merges. Phases 6 and 7 are file-disjoint (evals versus scripts, vendor and tests) and independent, so they run as Wave A in parallel; everything else is sequential. Cost note: two parallel workers roughly double token use for that wave versus sequential. [EXEC-SHAPE]
plugins/user-interface/evals/css-*/**,plugins/user-interface/skills/write-css/evals/evals.jsonplugins/user-interface/skills/write-css/scripts/**,plugins/user-interface/skills/write-css/vendor/**,plugins/user-interface/tests/review.test.*,plugins/user-interface/tests/fixtures/review/**Each agent is FORBIDDEN any file outside its ALLOWED list, PLAN.md, and commit or push (the main session commits). Each brief carries the plan template's divergence-escalation clause verbatim. Sequential fallback: on a scope-fence violation or a cannot-complete report, abort that worker and run 6 → 7 in the main session.
Decisions made (gate-passed)
docs/conventions/config-cascade/README.md:30-36, 84-86scripts/sync-shared-copies.sh:7-10:--check-bump <ref>compares canonicals against a base refresolve()takeshome; detect.mjs resolves it through the plugin'slib/resolve-convention-home.shplugins/user-experience/scripts/detect.mjs:150;scripts/sync-shared-copies.sh:13-15project, resolved config under top-levelconfigplugins/user-interface/tests/detect.test.mjs:55--team FILEstays a test seam; output switch is--configplugins/user-experience/scripts/detect.mjs:150css_*keys; values travel through a Write-tool temp file; OQ1 and OQ2 closed, Brief Out-of-scope and AC lines revisedplugins/source-control/skills/worktree/context/create.md"Create the worktree"routingaccepted only in layer 4docs/conventions/routing-as-data/README.md:105-108skills/write-css/vendor/.github/workflows/pr-require-checks.yml:809-816; playwright precedentruns: 5; keep-in-body when body beats reference by ≥2 of 5yaml-subset.mjsbecomes a generated copy and its suite moves tolib/in PR Ascripts/fragment-plugins.txthas nouser-experienceline; user-interface (86), prototype (70), visualization (88) are listedlib/<name>.test.{mjs,sh}lib/installed.test.mjsprecedent;scripts/run-plugin-tests.sh:5runslib/**/*.test.shgh api repos/vojtaholik/good-css/contents/<path>?ref=b645ab15...andgit hash-object .work/good-css/raw/*agree (this session)check-good-css-drift.sh --check.github/workflows/maintenance-check-upstream-drift.yml:11-15, 36-65(rows name a script with an exit-code contract); skill files carry no source links (Brief AC)plugins/skill-quality/scripts/check-listing-budget.sh:41(always exits 0 on a report)max_cost_usdceiling; batch by tag if exceededplugins/evals/.claude-plugin/plugin.jsonmax_cost_usd;plugins/evals/skills/plugin-eval/SKILL.md:186-194plugins/code-metrics/scripts/yaml_subset.py,plugins/multi-agent/scripts/yaml-subset.awkOpen questions
None. OQ1 (userConfig keys) and OQ2 (acceptance-criterion wording) are resolved by R5.
Handoff to implementation
Surface for confirmation before executing: the [FALLBACK] items (stacking a PR on the previous branch, running the reference arm from a temporary branch, the follow-up issue create path), and any change to an acceptance criterion. Every ready-flip and merge asks the user.