@event4u/agent-config 14.17.0 → 14.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +207 -77
  5. package/README.md +72 -150
  6. package/dist/agent-src/README.md +1 -1
  7. package/dist/agent-src/commands/agent-handoff.md +2 -2
  8. package/dist/agent-src/commands/pr/merge.md +35 -44
  9. package/dist/agent-src/commands/review/changes.md +19 -19
  10. package/dist/agent-src/commands/review.md +2 -2
  11. package/dist/agent-src/contexts/execution/host-capability-manifest.md +30 -3
  12. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  13. package/dist/agent-src/rules/git-history-discipline.md +2 -2
  14. package/dist/agent-src/rules/guidelines.md +1 -1
  15. package/dist/agent-src/rules/session-canary.md +2 -2
  16. package/dist/agent-src/rules/token-efficiency.md +1 -1
  17. package/dist/agent-src/skills/accessibility-auditor/SKILL.md +2 -0
  18. package/dist/agent-src/skills/adversarial-review/SKILL.md +0 -8
  19. package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +27 -1
  20. package/dist/agent-src/skills/canvas-design/SKILL.md +1 -1
  21. package/dist/agent-src/skills/code-intelligence/SKILL.md +60 -12
  22. package/dist/agent-src/skills/complexity-first-planning/SKILL.md +27 -0
  23. package/dist/agent-src/skills/design-intelligence/SKILL.md +1 -1
  24. package/dist/agent-src/skills/design-intelligence/data/app-interface.csv +1 -1
  25. package/dist/agent-src/skills/design-intelligence/data/motion.csv +17 -17
  26. package/dist/agent-src/skills/design-intelligence/data/ux-guidelines.csv +2 -2
  27. package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +17 -0
  28. package/dist/agent-src/skills/design-intelligence/references/design-rules-checklist.md +4 -4
  29. package/dist/agent-src/skills/design-review/SKILL.md +3 -1
  30. package/dist/agent-src/skills/design-variations/SKILL.md +1 -1
  31. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +1 -1
  32. package/dist/agent-src/skills/fe-design/SKILL.md +32 -5
  33. package/dist/agent-src/skills/fe-design/references/craft-floor.md +7 -0
  34. package/dist/agent-src/skills/fe-design/references/design-patterns.md +47 -2
  35. package/dist/agent-src/skills/feature-planning/SKILL.md +34 -0
  36. package/dist/agent-src/skills/git-workflow/SKILL.md +37 -12
  37. package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +176 -0
  38. package/dist/agent-src/skills/laravel-mail/SKILL.md +44 -0
  39. package/dist/agent-src/skills/laravel-scheduling/SKILL.md +3 -1
  40. package/dist/agent-src/skills/project-analyzer/SKILL.md +0 -6
  41. package/dist/agent-src/skills/react-native-setup/SKILL.md +3 -3
  42. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
  43. package/dist/agent-src/skills/sequential-thinking/SKILL.md +18 -8
  44. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  45. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +40 -0
  46. package/dist/agent-src/skills/ui-apply-generic/SKILL.md +1 -1
  47. package/dist/agent-src/skills/ui-component-architect/SKILL.md +1 -1
  48. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +29 -2
  49. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +53 -8
  50. package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +3 -1
  51. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  52. package/dist/cli/commands/recordTriggerEval.js +2 -2
  53. package/dist/cli/main.js +1 -1
  54. package/dist/cli-delegate/{chunk-AKJGUQNO.js → chunk-GZTMRYCN.js} +405 -341
  55. package/dist/cli-delegate/{chunk-7RXIKF62.js → chunk-IDHUQIXP.js} +283 -132
  56. package/dist/cli-delegate/cmd_adr_effective.js +318 -81
  57. package/dist/cli-delegate/cmd_conformance.js +749 -669
  58. package/dist/cli-delegate/cmd_doctor.js +1 -1
  59. package/dist/cli-delegate/cmd_export.js +1 -1
  60. package/dist/cli-delegate/cmd_handoff.js +1 -1
  61. package/dist/cli-delegate/cmd_prune.js +1 -1
  62. package/dist/cli-delegate/cmd_route_audit.js +1 -1
  63. package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
  64. package/dist/cli-delegate/cmd_sync.js +1 -1
  65. package/dist/cli-delegate/cmd_uninstall.js +1 -1
  66. package/dist/cli-delegate/cmd_validate.js +1 -1
  67. package/dist/discovery/deprecation-report.md +1 -1
  68. package/dist/discovery/discovery-manifest.json +39 -38
  69. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  70. package/dist/discovery/discovery-manifest.summary.md +1 -1
  71. package/dist/discovery/dormancy-report.md +1 -1
  72. package/dist/discovery/orphan-report.md +1 -1
  73. package/dist/discovery/packs.json +2 -2
  74. package/dist/discovery/trust-report.md +1 -1
  75. package/dist/discovery/workspaces.json +2 -2
  76. package/dist/hooks/dispatch.js +2764 -2439
  77. package/dist/install/install.mjs +588 -527
  78. package/dist/mcp/registry-manifest.json +2 -2
  79. package/dist/mcp/server.json +2 -2
  80. package/dist/mcp/server.mjs +1381 -1138
  81. package/dist/scripts/_lib/model_tier.js +1 -1
  82. package/dist/shared/settingsConsent.js +1 -1
  83. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +1 -1
  84. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
  85. package/docs/guidelines/design-antipatterns.md +87 -64
  86. package/package.json +1 -1
  87. package/src/agent-src/README.md +1 -1
  88. package/src/config/ci-local-parity.yml +6 -0
  89. package/src/config/conformance-claim-baseline.json +9 -0
  90. package/src/config/gate-coverage.yml +170 -0
  91. package/src/config/gate-violation-baselines.json +4 -4
  92. package/src/config/release-gate-locality.yml +141 -0
  93. package/src/scripts/_cli/cmd_doctor.ts +2 -2
  94. package/src/scripts/_cli/cmd_handoff.ts +1 -1
  95. package/src/scripts/_cli/cmd_update.ts +1 -1
  96. package/src/scripts/_cli/cmd_versions.ts +1 -1
  97. package/src/scripts/_lib/adr_frontmatter.ts +44 -0
  98. package/src/scripts/_lib/consequence_objects.ts +152 -0
  99. package/src/scripts/_lib/exec_evidence.ts +7 -0
  100. package/src/scripts/_lib/global_deploy_inventory.ts +2 -2
  101. package/src/scripts/_lib/host_permission_checks.ts +251 -0
  102. package/src/scripts/_lib/model_tier.ts +1 -1
  103. package/src/scripts/_lib/obligation_frequency.ts +2 -2
  104. package/src/scripts/_lib/release_highlights.ts +265 -1
  105. package/src/scripts/_lib/release_material.ts +129 -0
  106. package/src/scripts/_lib/repeated_run.ts +77 -0
  107. package/src/scripts/_lib/runtime_journal.ts +26 -3
  108. package/src/scripts/_lib/runtime_wiring_checks.ts +12 -1
  109. package/src/scripts/_lib/session_register.ts +11 -6
  110. package/src/scripts/_lib/sqlite_schema.ts +22 -0
  111. package/src/scripts/_lib/surface.ts +86 -0
  112. package/src/scripts/_lib/tamper_vocabulary.ts +242 -0
  113. package/src/scripts/_lib/ui_authority.ts +116 -0
  114. package/src/scripts/_lib/unified_diff.ts +121 -0
  115. package/src/scripts/adr/regenerate_index.ts +54 -1
  116. package/src/scripts/adr_cite_check.ts +71 -15
  117. package/src/scripts/ai_council/clients.ts +1 -1
  118. package/src/scripts/autonomy_friction_corpus.ts +229 -0
  119. package/src/scripts/build_proof.ts +15 -2
  120. package/src/scripts/check_beta_review_markers.ts +141 -17
  121. package/src/scripts/check_enforcement_coverage.ts +1 -1
  122. package/src/scripts/check_enforcement_denominator.ts +121 -11
  123. package/src/scripts/check_finding_dispositions.ts +239 -6
  124. package/src/scripts/check_gate_coverage.ts +96 -0
  125. package/src/scripts/check_installed_hooks_fresh.ts +381 -0
  126. package/src/scripts/check_release_highlights.ts +99 -14
  127. package/src/scripts/check_skill_activation_claim.ts +179 -0
  128. package/src/scripts/check_suppression_hygiene.ts +11 -0
  129. package/src/scripts/check_trigger_evals.ts +1 -1
  130. package/src/scripts/code_graph/build.ts +230 -33
  131. package/src/scripts/code_graph/extract.ts +168 -33
  132. package/src/scripts/code_graph/types.ts +37 -2
  133. package/src/scripts/code_graph/validate.ts +12 -1
  134. package/src/scripts/conformance_scan.ts +45 -4
  135. package/src/scripts/detect_silent_catch.ts +403 -0
  136. package/src/scripts/detect_verification_tampering.ts +538 -0
  137. package/src/scripts/explain_run.ts +1 -1
  138. package/src/scripts/generate_capability_matrix.ts +1 -1
  139. package/src/scripts/git_authorization_hook.ts +228 -18
  140. package/src/scripts/hook_manifest.json +1 -1
  141. package/src/scripts/hook_manifest.yaml +47 -30
  142. package/src/scripts/hooks/augment-dispatcher.sh +1 -1
  143. package/src/scripts/hooks/block_no_verify.ts +3 -3
  144. package/src/scripts/hooks/category_a.ts +291 -0
  145. package/src/scripts/hooks/cline-dispatcher.sh +1 -1
  146. package/src/scripts/hooks/concern_registry.ts +2 -2
  147. package/src/scripts/hooks/cursor-dispatcher.sh +11 -5
  148. package/src/scripts/hooks/dispatch_hook.ts +17 -22
  149. package/src/scripts/hooks/evidence_independence.ts +21 -2
  150. package/src/scripts/hooks/{block_unauthorized_git.ts → git_command_classifier.ts} +175 -364
  151. package/src/scripts/hooks/host_lowering.ts +215 -0
  152. package/src/scripts/hooks/host_lowering.yaml +185 -0
  153. package/src/scripts/hooks/host_semantics.ts +105 -37
  154. package/src/scripts/hooks/journal_record_hook.ts +10 -0
  155. package/src/scripts/hooks/permission_gate.ts +117 -0
  156. package/src/scripts/hooks/push_settle_hook.ts +250 -0
  157. package/src/scripts/hooks/ship_diff_volume_hook.ts +1 -1
  158. package/src/scripts/hooks_status.ts +12 -10
  159. package/src/scripts/install-hooks.sh +134 -1
  160. package/src/scripts/install.ts +53 -82
  161. package/src/scripts/install_anthropic_key.sh +2 -2
  162. package/src/scripts/language_mirror_hook.ts +27 -5
  163. package/src/scripts/lint_design_antipattern_parity.ts +80 -0
  164. package/src/scripts/lint_evidence_artifacts.ts +45 -1
  165. package/src/scripts/lint_hook_manifest.ts +87 -3
  166. package/src/scripts/lint_motion_authority_drift.ts +731 -0
  167. package/src/scripts/lint_readme_size.ts +158 -12
  168. package/src/scripts/lint_reasoning_fixtures.ts +216 -0
  169. package/src/scripts/lint_skill_trigger_contradiction.ts +161 -0
  170. package/src/scripts/measure_sibling_naming.ts +150 -0
  171. package/src/scripts/redact_hook_capture.ts +1 -1
  172. package/src/scripts/release.ts +31 -143
  173. package/src/scripts/release_drill.ts +71 -1
  174. package/src/scripts/release_publication.ts +243 -3
  175. package/src/scripts/release_verify.ts +220 -0
  176. package/src/scripts/report_skill_activation.ts +319 -7
  177. package/src/scripts/schemas/review-findings.schema.json +6 -1
  178. package/src/scripts/schemas/skill.schema.json +12 -2
  179. package/src/scripts/self_review_gate.ts +10 -1
  180. package/src/scripts/session_canary_hook.ts +1 -1
  181. package/src/scripts/session_register_hook.ts +4 -3
  182. package/src/scripts/trigger_eval_grandfather.json +5 -1
  183. package/src/scripts/validate_agent_settings.ts +1 -1
  184. package/src/shared/settingsConsent.ts +1 -1
@@ -150,22 +150,49 @@ against — a reader then cannot tell whether a capability was tested.
150
150
  exactly the distinction, readable from `routing:doctor` without opening the
151
151
  file.
152
152
 
153
- ### The four-part citation — a row without all four is not admissible
153
+ ### The five-part citation — a row without all five is not admissible
154
154
 
155
155
  1. **the host** — the platform key from `hook_manifest.yaml`;
156
156
  2. **the host version** at the time of observation;
157
157
  3. **the transcript or artefact reference** the observation is read from;
158
- 4. **the date**.
158
+ 4. **the date**;
159
+ 5. **the expiry** — the date after which the observation stops being admissible.
159
160
 
160
161
  Worked example, which a later row can be pattern-matched against:
161
162
 
162
163
  ```
163
- // subagent_spawn: OBSERVED — claude (Claude Code, Opus 5 1M session, 2026-08-22)
164
+ // subagent_spawn: OBSERVED — claude (Claude Code, Opus 5 1M session, 2026-08-22,
165
+ // expires 2027-08-22)
164
166
  // agents/runtime/state/subagent-ledger/2026-08.jsonl: 445 `subagent_start`
165
167
  // records and 420 stops carrying a measured `duration_ms`, i.e. 420 child legs
166
168
  // whose start and stop pair. A measurable child duration is the criterion.
167
169
  ```
168
170
 
171
+ ### Why an expiry, and not just a date
172
+
173
+ ```
174
+ A HOST FACT WITHOUT AN EXPIRY IS A CLAIM NOBODY EVER RE-CHECKS.
175
+ AN EXPIRED FACT IS TREATED EXACTLY AS AN UNOBSERVED ONE.
176
+ ```
177
+
178
+ The date says when it was true. Only the expiry says when someone must look
179
+ again — and a fact with no such date is one nobody looks at again, which is how
180
+ the same stale host claims survived three correction rounds in this tree. An
181
+ observation is about a host version that ships on its own cadence, so its
182
+ shelf-life is a property of the observation and belongs beside it.
183
+
184
+ The expiry is **not** a claim that the capability lapses on that date. It is the
185
+ date after which this tree stops treating the observation as current, which is a
186
+ statement about the record, never about the host.
187
+
188
+ The machine-readable twin of this field is `verified.expires` in
189
+ `src/scripts/hooks/host_lowering.yaml`, the same discipline applied to hook
190
+ lowering: `lint_hook_manifest` refuses an expired row that still carries a
191
+ blocking binding and warns on every other lapse, and `host_semantics.ts` reads
192
+ an expired row exactly as `verified: null`. A registry row here has no such
193
+ gate — the criteria are applied by a human, deliberately (see above), so the
194
+ expiry here is a written obligation and its enforcement is the reviewer.
195
+
169
196
  ### Pre-state, pinned so the Phase 2 diff reads as an addition
170
197
 
171
198
  At the commit this protocol landed on, the registry was exactly:
@@ -67,7 +67,7 @@ ALLOWLIST > 20 ENTRIES IN ONE SESSION = THE LINTER IS WRONG.
67
67
  STOP. PROPOSE LINTER REDESIGN OR REMOVAL. DO NOT EXPAND THE ALLOWLIST FURTHER.
68
68
  ```
69
69
 
70
- Crossing 20 entries counts as the 3rd validation-target failure — fix the tool shape, not the list. Enforced at tool-call time by the `block-config-weakening` PreToolUse guard (`src/scripts/hooks/block_config_weakening.ts`) **on `claude`, the one host that both binds `pre_tool_use` and honours a deny**: it counts allowlist entries added per session, warns from 5, and blocks past 20. It is *bound* on augment and cowork as well, and ignored there — `host_semantics.ts` verifies claude alone, and both trampolines discard dispatcher output and `exit 0` unconditionally. Everywhere else the cap is model-carried and "enforced at tool-call time" is not a claim this rule can make. **Corrected 2026-08-17 in both directions:** this sentence used to certify augment, claude and cowork as the enforcing set (an over-claim of two hosts) and to explain the rest with "the guard has nowhere to bind" (false for cursor, cline and gemini, whose native pre-tool events `native_event_aliases` already maps onto `pre_tool_use` — unbound, not unbindable; only windsurf and copilot have no pre-tool surface). The four states are tabulated once in [`hook-architecture-v1 § Which hosts carry pre_tool_use`](../docs/contracts/hook-architecture-v1.md). Run `agent-config hooks:status` to see which slots are bound on the host you are actually on — the sibling rules `git-history-discipline` and `evaluator-independence` qualify the identical slot, and an unqualified claim here would read as a guarantee the manifest does not give. Baselines and budget thresholds warn only — a rising count there may be a legitimate ratchet reset, which the edit alone cannot distinguish. Verify with the narrowest tool that proves the target green (a single `curl` / Playwright spec for HTTP/UI, the test runner with a `--filter`, an `xdebug` step-through) — never a meta-pipeline as per-iteration probe. Failed-attempt detail, suppression-sweep equivalents, probe-efficiency detail, and adaptive effort & stop (RDP): [`autonomy-mechanics § Validation-loop budget`](../contexts/execution/autonomy-mechanics.md).
70
+ Crossing 20 entries counts as the 3rd validation-target failure — fix the tool shape, not the list. Enforced at tool-call time by the `block-config-weakening` PreToolUse guard (`src/scripts/hooks/block_config_weakening.ts`) **on `claude`, the one host that both binds `pre_tool_use` and honours a deny**: it counts allowlist entries added per session, warns from 5, and blocks past 20. It is *bound* on augment and cowork as well, and ignored there — `host_semantics.ts` verifies claude alone, and both trampolines discard dispatcher output and `exit 0` unconditionally. Everywhere else the cap is model-carried and "enforced at tool-call time" is not a claim this rule can make. **Corrected 2026-08-17 in both directions:** this sentence used to certify augment, claude and cowork as the enforcing set (an over-claim of two hosts) and to explain the rest with "the guard has nowhere to bind" (false for cursor, cline and gemini, whose native pre-tool events `native_event_aliases` already maps onto `pre_tool_use` — unbound, not unbindable; only windsurf and copilot have no alias row). The four states are tabulated once in [`hook-architecture-v1 § Which hosts carry pre_tool_use`](../docs/contracts/hook-architecture-v1.md). Run `agent-config hooks:status` to see which slots are bound on the host you are actually on — the sibling rules `git-history-discipline` and `evaluator-independence` qualify the identical slot, and an unqualified claim here would read as a guarantee the manifest does not give. Baselines and budget thresholds warn only — a rising count there may be a legitimate ratchet reset, which the edit alone cannot distinguish. Verify with the narrowest tool that proves the target green (a single `curl` / Playwright spec for HTTP/UI, the test runner with a `--filter`, an `xdebug` step-through) — never a meta-pipeline as per-iteration probe. Failed-attempt detail, suppression-sweep equivalents, probe-efficiency detail, and adaptive effort & stop (RDP): [`autonomy-mechanics § Validation-loop budget`](../contexts/execution/autonomy-mechanics.md).
71
71
 
72
72
  Body migrated to `contexts/execution/autonomy-mechanics.md` (per P4 of `road-to-kernel-and-router.md`) — opt-in detection summary, task-scope shapes table, N=3 mechanics, allowlist-antipattern detail, probe efficiency, adaptive effort (RDP).
73
73
  Trigger-set above activates this routing on demand, independent of the discipline profile (ADR-110).
@@ -86,6 +86,6 @@ Trigger-set above activates this routing on demand, independent of the disciplin
86
86
  - [`skill:git-workflow`](../skills/git-workflow/SKILL.md) — Safe Squash-After-Push protocol and Divergent-State Recovery decision tree.
87
87
  - `git … --no-verify` / `core.hooksPath` overrides — deterministically blocked by the `block-no-verify` PreToolUse guard (`src/scripts/hooks/block_no_verify.ts`, registered in `hook_manifest.yaml`) **on `claude`, the one host that both binds `pre_tool_use` and honours a deny**. It is *bound* on augment and cowork too, and ignored there. Everywhere else the Iron Laws above are model-carried and "deterministically blocked" is not a claim this rule can make. To check the host you are on rather than trusting this sentence, run `agent-config hooks:status`.
88
88
 
89
- **Corrected 2026-08-17, in both directions, because this line was wrong on each side.** It read "the guard has nowhere to bind" on cursor, cline, windsurf, gemini and copilot — refuted for three of the five by the manifest's own `native_event_aliases` table, which already maps `preToolUse`, `PreToolUse` and `BeforeTool` onto `pre_tool_use`: there the guard is **unbound, not unbindable**, and only windsurf and copilot carry no pre-tool surface. And it certified augment, claude and cowork as the enforcing set — `host_semantics.ts` verifies **claude alone**, while the augment and cowork trampolines discard dispatcher output and `exit 0` unconditionally, so a guard bound there runs and is then ignored. Both halves are the same failure: a host-capability claim this tree never established. The four states are tabulated once in [`hook-architecture-v1 § Which hosts carry pre_tool_use`](../docs/contracts/hook-architecture-v1.md).
89
+ **Corrected 2026-08-17, in both directions, because this line was wrong on each side.** It read "the guard has nowhere to bind" on cursor, cline, windsurf, gemini and copilot — refuted for three of the five by the manifest's own `native_event_aliases` table, which already maps `preToolUse`, `PreToolUse` and `BeforeTool` onto `pre_tool_use`: there the guard is **unbound, not unbindable**, and only windsurf and copilot carry no pre-tool alias row here. And it certified augment, claude and cowork as the enforcing set — `host_semantics.ts` verifies **claude alone**, while the augment and cowork trampolines discard dispatcher output and `exit 0` unconditionally, so a guard bound there runs and is then ignored. Both halves are the same failure: a host-capability claim this tree never established. The four states are tabulated once in [`hook-architecture-v1 § Which hosts carry pre_tool_use`](../docs/contracts/hook-architecture-v1.md).
90
90
 
91
- The frequency join in `check_enforcement_coverage.ts` reports this obligation uncovered on **four** platforms — cursor, cline, windsurf, gemini. It skips `fallback_only` platforms before computing, so copilot never appears in its output and must not be quoted as if it did; copilot is uncovered for the separate reason that it has no hook surface at all.
91
+ The frequency join in `check_enforcement_coverage.ts` reports this obligation uncovered on **four** platforms — cursor, cline, windsurf, gemini. It skips `fallback_only` platforms before computing, so copilot never appears in its output and must not be quoted as if it did; copilot is uncovered because this package binds nothing there.
@@ -21,7 +21,7 @@ Coding guidelines live under `docs/guidelines/` organized by language. **Always
21
21
 
22
22
  - **Guidelines** = detailed coding conventions (reference material, read on demand).
23
23
  - **Rules** = always-active behavior constraints (auto-loaded every conversation).
24
- - **Skills** = agent capabilities and workflows (matched by topic).
24
+ - **Skills** = agent capabilities and workflows (named or trigger-matched).
25
25
 
26
26
  Guidelines are the "how to write code" docs. Rules enforce critical subsets automatically. Skills reference guidelines when performing related tasks.
27
27
 
@@ -157,8 +157,8 @@ audit should not re-open it.
157
157
  `user_prompt_submit` slot; its `stop` fires *after* the reply, so injecting
158
158
  there could not shape the reply the reminder is for, and counting it would be
159
159
  exactly the over-credit the frequency audit exists to remove. Augment therefore
160
- reports as an open gap rather than as covered. **Copilot** has no hook surface at
161
- all — this rule is the only carrier there; re-read it when the trigger fires.
160
+ reports as an open gap rather than as covered. **Copilot** carries no binding here —
161
+ this rule is the only carrier there; re-read it when the trigger fires.
162
162
 
163
163
  ## See also
164
164
 
@@ -53,7 +53,7 @@ Exempt — the file set is enumerated **before** the first read and each read is
53
53
  - the members of a directory listing or grep result being opened in turn;
54
54
  - a declared read protocol under [`context-hygiene`](context-hygiene.md).
55
55
 
56
- Not exempt, and still the failure this rule exists to catch: re-reading the *same* file hoping for a different answer, re-running a failing command unchanged, or widening a grep by one word at a time instead of thinking. The discriminator is **did the previous call change what I know** — not the tool name.
56
+ Not exempt, and still the failure this rule exists to catch: re-reading the *same* file hoping for a different answer, re-running a failing command unchanged, or widening a grep by one word at a time instead of thinking. The discriminator is **did the previous call change what I know** — not the tool name. Nor `cd X && …`: `git -C`.
57
57
 
58
58
  ## Fresh Output Over Memory
59
59
 
@@ -5,6 +5,8 @@ description: "Use when reviewing UI for accessibility — WCAG 2.2 AA, keyboard
5
5
  personas:
6
6
  - frontend-engineer
7
7
  domain: quality
8
+ enforced_by:
9
+ - "test:tests/contracts/accessibility_wcag_version_claim.test.ts"
8
10
  workspaces:
9
11
  - engineering
10
12
  packs:
@@ -142,14 +142,6 @@ logic, ≥ 3 explicit must/must-not constraints, stateful operations,
142
142
  irreversibility} **and** estimated work ≥ ~1k tokens. Token length alone never
143
143
  triggers it. See [`rdp-gate`](../../contexts/execution/rdp-gate.md) (L12).
144
144
 
145
- ## Auto-trigger keywords
146
-
147
- - adversarial review
148
- - self-review
149
- - challenge plan
150
- - review my approach
151
- - sanity check
152
-
153
145
  ### Validate
154
146
 
155
147
  - Confirm each identified risk has a concrete mitigation or explicit acceptance.
@@ -47,7 +47,7 @@ Run the row(s) for what you touched. The grep is a fast authoring-time backstop
47
47
  | **HTTP endpoint / route** | authorization (this principal may act on this resource — not just authenticated); tenant scope; **the three negative tests** (unauth→401, non-owner→403/404, cross-tenant→403/404); input validation at the boundary; rate limit actually wired; state-changing → CSRF + audit log | `broken-access-control`, `authz-review`, `threat-modeling` |
48
48
  | **DB query / ORM** | parameterized, never string-built; tenant predicate present; no `SELECT *` across a serialization boundary; N+1 avoided (eager load) | `source-discovery`, `security` |
49
49
  | **Migration** | reversible (`down`); expand-contract for drop/rename (never a bare `DROP COLUMN` before code stops reading it); transaction; index on new FK/filter column | `engineering-safety-floor`, `migration-architect` |
50
- | **User-controlled render (FE)** | output-encoded; no `dangerouslySetInnerHTML`/`v-html`/`innerHTML`/`eval` on non-constant input; no secret/token in client code; token not in `localStorage` | `frontend-render-security` |
50
+ | **User-controlled render (FE)** | output-encoded; no `dangerouslySetInnerHTML`/`v-html`/`innerHTML`/`eval` on non-constant input; no secret/token in client code; token not in `localStorage`. **This surface also carries the four completeness rows below — they are part of it, not an optional extra.** | `frontend-render-security` |
51
51
  | **Third-party asset in shipped markup / CSS** | delivery is self-hosted through the project's own route (framework font/asset primitive, bundled package, or locally-served file) — a font/icon/stylesheet CDN link transmits the **visitor's IP** to that third party on every page view; a hotlink only on a stated consumer opt-in; `integrity` + `crossorigin` if a CDN link stays | `design-fidelity-mechanics` § Asset & imagery discipline (owner), `typography-system` |
52
52
  | **File / outbound fetch** | path confined to an allowed base; SSRF allow-list + private-IP block on user-supplied URLs; size limit; **explicit timeout** (no unbounded/default-infinite wait); server-side validation (never client-only) | `security`, `defense-in-depth` |
53
53
  | **Infra / IaC** | least-privilege (no `Action:*`/`Resource:*`); encryption at rest; no `0.0.0.0/0` to mgmt/DB ports; no hardcoded creds; scanner-verified, not `plan`-verified | `engineering-safety-floor`, `terraform`, `secrets-management` |
@@ -57,6 +57,32 @@ Run the row(s) for what you touched. The grep is a fast authoring-time backstop
57
57
  | **Concurrency / shared state** | check-then-act guarded (DB lock / atomic op / transaction — not a read-modify-write race); retried write carries an idempotency key; no unbounded parallel fan-out; shared mutable state synchronized | `source-discovery`, `security` |
58
58
  | **Test** | asserts general behavior; expected derived from inputs/seeded data, not hardcoded; boundary + error + abuse cases, not only happy path | `testing-anti-patterns`, `test-driven-development` |
59
59
 
60
+ ### Render surface — the completeness rows
61
+
62
+ Every row above is a **security** control. These four are the **completeness**
63
+ controls for the same surface — the states an agent most reliably omits, because
64
+ the prompt named the happy path and nothing else. A render that ships only the
65
+ populated state is not finished; it is one of four states written.
66
+
67
+ **The grep polarity is INVERTED here, and reading it the other way is the failure
68
+ mode this table has.** Above, zero hits is the pass. Below, **zero hits next to a
69
+ render that fetches or iterates is the prompt to look** — the state is probably
70
+ absent. A hit is not a pass either: the column is a `heuristic`, a locator for
71
+ code to read, never proof the state behaves. A framework abstraction can
72
+ implement all four correctly and match none of these patterns, and a codebase can
73
+ match all four while shipping a broken empty state.
74
+
75
+ | State | Assert on the render you wrote | Backstop grep (heuristic — zero hits is the prompt) |
76
+ |---|---|---|
77
+ | **Empty** | the zero-rows / no-results case renders a deliberate state, not a bare frame or a collapsed layout; it says what is missing and what the user can do next | `rg -n -e '\.length\s*[=<]' -e '\bisEmpty\b' -e 'count\(\)\s*[=<]' -e '\bempty\b'` — over the file that maps the collection |
78
+ | **Loading** | the pending case renders (skeleton, spinner, disabled control); no layout shift on arrival; the state is reachable from the actual async call, not only defined | `rg -n -e 'isLoading' -e 'isPending' -e 'isFetching' -e 'Skeleton' -e 'Spinner' -e 'aria-busy'` |
79
+ | **Error** | the failed case renders a recoverable state with a retry or a next step; the message carries no stack, secret or PII (the `frontend-render-security` row owns that half) | `rg -n -e 'isError' -e 'onError' -e 'ErrorBoundary' -e '\.catch\(' -e 'role="alert"'` |
80
+ | **Keyboard path** | every interactive element is reachable and operable by keyboard alone, with a visible focus ring; a click handler on a non-button element carries a role, `tabindex` and a key handler | `rg -n -e 'onClick' -e '@click' -e 'v-on:click'` lists the handlers, then `rg -n -e 'tabIndex' -e 'tabindex' -e 'onKeyDown' -e '@keydown' -e 'role='` on the same file — a handler with no match on the second is the prompt |
81
+
82
+ Route to `accessibility-auditor` for the keyboard row when the surface is more
83
+ than a handful of controls — this row asserts the path exists, it is not a WCAG
84
+ pass.
85
+
60
86
  ### Backstop greps (authoring-time, cross-stack)
61
87
 
62
88
  ```bash
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: inherit
3
3
  name: canvas-design
4
- description: "Use when creating static visual art — posters, marketing visuals, brand assets, PDF/PNG design pieces — even if the user just says 'design a poster' or 'mach uns ein Visual'."
4
+ description: "Creating static visual art — posters, marketing visuals, brand assets, PDF/PNG design pieces — on 'design a poster' or 'mach uns ein Visual'. Grounded by design-intelligence."
5
5
  domain: product
6
6
  workspaces:
7
7
  - small-business
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: inherit
3
3
  name: code-intelligence
4
- description: "Route codebase-structure questions (who calls X, where used, what imports, change-impact) to a code-graph first, grep fallback. Triggers 'who calls', 'where is this used', 'call graph'."
4
+ description: "Route codebase-structure questions (who calls X, where is this used, what imports, change-impact) to an existing code-graph first: cheaper, never more precise; grep stays routine. Also 'call graph'."
5
5
  domain: engineering
6
6
  workspaces:
7
7
  - engineering
@@ -14,11 +14,14 @@ requires_skills:
14
14
  # code-intelligence
15
15
 
16
16
  > For a **structure** question — *who calls X*, *where is Y used*, *what
17
- > imports Z*, *what breaks if I change this symbol* — a code-graph answers far
18
- > more precisely than a blind `grep`, at a fraction of the tokens. This skill
19
- > routes such questions to the native code-graph engine (ADR-124, Class A) or a
20
- > consumer-shipped index first, and falls back to grep **with a stated reason**
21
- > when the graph cannot answer. It is the executable side of
17
+ > imports Z*, *what breaks if I change this symbol* — ask an index that already
18
+ > exists before rebuilding the relationship by hand: it is already built, its
19
+ > answer is structured, and it costs a fraction of the tokens. It is **not** more
20
+ > precise than a blind `grep` — measured on this repository's own source, zero
21
+ > classes met the win bar (§ Measured twice) — so grep is the arm you fall back
22
+ > to routinely and **with a stated reason**, not grudgingly. This skill routes
23
+ > such questions to the native code-graph engine (ADR-124, Class A) or a
24
+ > consumer-shipped index first. It is the executable side of
22
25
  > [`external-code-graph-interop`](../../rules/external-code-graph-interop.md):
23
26
  > *orchestrator first, owner where it wins*.
24
27
 
@@ -50,9 +53,15 @@ code-graph and no appetite to build one — plain `grep`/read is right there.
50
53
  - `agent-config code-graph path <a> <b>` — how two symbols connect.
51
54
  - `agent-config code-graph explain <symbol>` — 2-hop neighbourhood.
52
55
  Pass `--budget <tokens>` to cap output.
53
- 4. **Read the confidence.** `EXTRACTED` = syntactic fact; `INFERRED` =
54
- hierarchy-resolved; `AMBIGUOUS` = dynamic dispatch / facade, carries
56
+ 4. **Read the confidence.** `EXTRACTED` = syntactic fact — a symbol declared in
57
+ the file, or a name bound to the module specifier the file names; `INFERRED`
58
+ = resolved by hierarchy, or by a repo-wide same-name lookup with no binding
59
+ in the file to justify it; `AMBIGUOUS` = dynamic dispatch / facade, carries
55
60
  candidates — treat its target as *one of* the candidates, never as certain.
61
+ Two target shapes are **not nodes** and resolve to nothing you can read:
62
+ `symbol:<name>` (a name this repository does not declare) and
63
+ `external:<module>` / `external:<module>#<name>` (a name imported from
64
+ outside the indexed root — `external:node:path`).
56
65
  5. **Fall back honestly.** If the graph has no entry for the symbol, grep — and
57
66
  say so: *"the graph has no entry for X, so I grepped."* If this command is
58
67
  not available, grep and say so.
@@ -72,10 +81,22 @@ Every answer built with this skill MUST:
72
81
  - **A stale graph lies confidently.** If `detect` reports the index is N commits
73
82
  behind, rebuild (`agent-config code-graph build --update`) before trusting
74
83
  relationship answers, or say the answer is from a stale index.
75
- - **Dynamic dispatch is honestly ambiguous.** On a Laravel/JS codebase most
76
- method-call edges are `AMBIGUOUS` (facades, injected services, `$obj->m()`).
77
- That is the engine being honest, not broken — do not "resolve" them yourself
78
- by guessing.
84
+ - **Dynamic dispatch is honestly ambiguous — where there is anything to be
85
+ ambiguous ABOUT.** A `$obj->m()` whose name matches a method declared
86
+ somewhere in the repo is `AMBIGUOUS` and carries those candidates: the engine
87
+ being honest, not broken — do not "resolve" them yourself by guessing. A
88
+ dynamic call whose name matches **no** in-repo method (`xs.push()`,
89
+ `map.get()`) is not emitted at all, and the count is published as
90
+ `suppressed_edge_counts.dynamic_no_candidate`. It used to be 39 % of this
91
+ engine's own graph, pointing at `symbol:push`, which is not a node and which
92
+ no query verb could reach.
93
+ - **The AMBIGUOUS share is a property of the code, not a constant.** Measured
94
+ 2026-09-04 over the three roots the v2 benchmark uses: `AMBIGUOUS` is 0 of 495
95
+ edges under the package's own graph-engine root, 0 of 181 under `src/shared`, and 121 of
96
+ 4,002 (3.0 %) under its council root — class-free TypeScript has
97
+ almost no in-repo method to be ambiguous between. A Laravel codebase, where
98
+ facades and injected services dispatch onto real in-repo methods, is the
99
+ opposite case. Read the counts in the graph rather than a remembered ratio.
79
100
 
80
101
  ## Measured twice, and it has not won (v2, 2026-08-29)
81
102
 
@@ -113,6 +134,33 @@ engine on this repository's own code. The rule now gives the reasons that surviv
113
134
  measurement — an index that exists is already built and structured, so it is the
114
135
  cheap first question — and drops the precision claim its own benchmark refuted.
115
136
 
137
+ ## Re-run after the extractor repair (2026-09-04) — still no class won
138
+
139
+ `internal/bench/reports/code-graph-vs-grep-inrepo-v2-rerun-2026-09-04.md`. The
140
+ SAME registration, the same corpus SHA, the same per-class bars and the same
141
+ arm-B verbs, re-run after the import-binding repair. The 2026-08-29 report is
142
+ untouched; this is a second report beside it, which is how v1 was handled too.
143
+
144
+ | Class | graph R then | graph R now | graph P then | graph P now | verdict then → now |
145
+ |---|---|---|---|---|---|
146
+ | `callers` | 1.000 | 1.000 | 0.667 | 0.667 | TIE → TIE |
147
+ | `transitive-impact` | 0.500 | **0.611** | 0.667 | **1.000** | **NULL → TIE** |
148
+ | `path-between` | 1.000 | 1.000 | 1.000 | 1.000 | TIE → TIE |
149
+ | `references` | 0.333 | **1.000** | 0.333 | **1.000** | **NULL → TIE** |
150
+
151
+ **Zero classes met the win bar, again.** Two NULLs became TIEs and nothing
152
+ regressed, so the routing verdict below is unchanged — a TIE is not a win, and
153
+ no bar was renegotiated after the repair.
154
+
155
+ Read with two caveats the report states in full. A measured root is live source:
156
+ `src/shared` is byte-identical between the runs, the council root moved
157
+ by 27 files on `main` in between, and the graph-engine root IS the engine, so
158
+ its content necessarily moves whenever the engine does — which is also why the
159
+ GREP arm's macro precision moved (0.806 → 0.764) in a run that changed nothing
160
+ about grep. Both classes that changed verdict did so on rows whose root did not
161
+ drift: `references` moved on the graph-engine and shared roots, `transitive-impact` on
162
+ `shared` alone.
163
+
116
164
  **No class is graph-first.** Query the index first because an index that already
117
165
  exists is cheap to ask and its answer is structured — not because it answers
118
166
  better. When it returns nothing, that is the common case, and grep remains the
@@ -62,6 +62,33 @@ unknown to resolve), or when the user has already fixed the sequence.
62
62
  A short ordered plan that leads with the load-bearing unknown + how it will be
63
63
  proven, then the dependent steps. One recommendation, not a survey.
64
64
 
65
+ ## The four plan criteria — what a reviewer fails the ordered plan on
66
+
67
+ Risk-first ordering is one of four things a plan is judged on. All four are
68
+ stated so a reviewer can **fail** a plan on them — each names what failing looks
69
+ like. This skill owns the third; the other three are stated here because a plan
70
+ reviewed on ordering alone passes while being unnecessary, insufficient, or
71
+ built on verbs that do not exist. The same four are carried in
72
+ `feature-planning` (pack `product-basic`), which is pruned under `projection.mode: scoped` and so is named rather than linked.
73
+
74
+ 1. **Necessity** — every step traces to a stated problem or acceptance criterion.
75
+ **Fails when** the plan carries work nothing asked for — a spike for an
76
+ unknown that is not load-bearing is the version of this failure this skill
77
+ causes.
78
+ 2. **Sufficiency** — every requirement is discharged by at least one step.
79
+ **Fails when** a requirement owns no step, or its only step has no verifiable
80
+ outcome. Walk the requirements, not the steps.
81
+ 3. **Ordering** — the load-bearing unknown is resolved before anything that
82
+ assumes it, and no step consumes what a later step creates. **Fails when** the
83
+ plan front-loads the easy parts, or ranks by effort instead of dependency
84
+ blast radius (see Gotchas). This is the criterion this skill exists for.
85
+ 4. **Groundedness** — every command, skill and tool the plan tells someone to run
86
+ exists. **Fails when** the plan names a verb that is not in the index. Check
87
+ it against [`CAPABILITIES.yaml`](../../../CAPABILITIES.yaml) at the repository
88
+ root — the generated capability index of every shipped skill and command —
89
+ plus the project's own script/task entry points. It is the only one of the
90
+ four decidable without judgement, so it is never waved through.
91
+
65
92
  ## Do NOT
66
93
 
67
94
  - Build the easy parts first to show progress, then discover the hard part breaks them.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: inherit
3
3
  name: design-intelligence
4
- description: "Grounded design brief from the adopted corpus — style, WCAG-checked color tokens, typography, layout pattern, anti-patterns. Use on ui-design-brief or any which-style/palette/font/chart decision."
4
+ description: "Grounded design brief from the corpus — style, WCAG color tokens, typography, layout, anti-patterns. Use on ui-design-brief or any which-style/palette/font/chart call; feeds fe-design."
5
5
  domain: engineering
6
6
  personas:
7
7
  - frontend-engineer
@@ -21,7 +21,7 @@ No,Category,Issue,Keywords,Platform,Description,Do,Don't,Code Example Good,Code
21
21
  20,Performance,Virtualize Long Lists,FlatList SectionList virtualization,iOS/Android/React Native,Use FlatList/SectionList for lists over ~50 items,Use keyExtractor and initialNumToRender appropriately,Render hundreds of items with ScrollView,"<FlatList data={items} renderItem={...} />","<ScrollView>{items.map(renderItem)}</ScrollView>",High
22
22
  21,Performance,Image Size & Cache,Image resize cache,iOS/Android/React Native,Use correctly sized and cached images,Use Image component with proper resizeMode and caching,Load full-resolution images everywhere,"<Image source={{uri}} resizeMode=""cover"" />","<Image source={require('4k.png')} /> // small avatar",Medium
23
23
  22,Performance,Debounce High-Freq Events,debounce scroll search,iOS/Android/React Native,Debounce scroll/search callbacks to avoid jank,Wrap handlers with debounce/throttle,Run heavy logic on every event,"onScroll={debouncedHandleScroll}","onScroll={handleScrollHeavy}",Medium
24
- 23,Animation,Duration & Easing,animation duration easing,iOS/Android/React Native,Micro-interactions should be 150–300ms with native-like easing,Use ease-out for enter/ease-in for exit,Use long or linear animations for core UI,"Animated.timing(..., { duration: 200, easing: Easing.out(Easing.quad) })","Animated.timing(..., { duration: 800, easing: Easing.linear })",Medium
24
+ 23,Animation,Duration & Easing,animation duration easing,iOS/Android/React Native,"Micro-interactions follow the per-interaction bands in design-patterns.md § Motion, with native-like easing",Use ease-out for both enter and exit,Use long or linear animations for core UI,"Animated.timing(..., { duration: 200, easing: Easing.out(Easing.quad) })","Animated.timing(..., { duration: 800, easing: Easing.linear })",Medium
25
25
  24,Animation,Respect Reduced Motion,reduced motion accessibility,iOS/Android/React Native,Respect OS reduced-motion accessibility setting,Check reduceMotionEnabled and simplify animations,Ignore user motion preferences,"if (reduceMotionEnabled) skipAnimation()","Always run complex parallax animations",Critical
26
26
  25,Animation,Limited Continuous Motion,loop animation loader,iOS/Android/React Native,Reserve infinite animations for loaders and live data,Use looping only where necessary,Keep decorative elements looping forever,"Animated.loop(loaderAnim) for ActivityIndicator","Animated.loop(bounceAnim) on background icons",Medium
27
27
  26,Typography,Base Font Size,fontScale dynamic type,iOS/Android/React Native,Body text must be readable and support Dynamic Type,Use platform fontScale and at least 14–16pt base,Render critical text below 12pt,"<Text style={{ fontSize: 16 }}>Body</Text>","<Text style={{ fontSize: 10 }}>Body</Text>",High
@@ -1,17 +1,17 @@
1
- No,Category,Intensity Tier,Keywords,Trigger,Duration,Easing,GSAP Snippet,Framework Notes,Do,Don't,Performance Notes
2
- 1,Hover Micro-interaction,Subtle,"hover, button, opacity, lift, press feedback",hover,150-200ms,power1.out,"gsap.to(el, { y: -1, opacity: 0.9, duration: 0.15, ease: 'power1.out' });",Bind on mouseenter/mouseleave; in React wrap in a ref + useEffect (or onMouseEnter/onMouseLeave props directly calling gsap.to),Keep displacement under 2px so it reads as feedback not motion,Don't animate layout-affecting props (width/height/margin) on hover,Runs on transform/opacity only so it stays on the compositor thread
3
- 2,Hover Micro-interaction,Standard,"hover, card, scale, tilt, cursor feedback",hover,200-300ms,power2.out,"gsap.to(el, { y: -4, scale: 1.02, boxShadow: '0 12px 24px rgba(0,0,0,0.12)', duration: 0.25, ease: 'power2.out' });","Use gsap.quickTo(el, 'y') for cards with many hover targets to avoid re-creating tweens every event",Pair with a matching mouseleave tween that reverses the same properties,Don't leave the hover state stuck if the pointer leaves fast; always attach the reverse tween,quickTo() avoids GC churn on lists with 20+ hoverable cards
4
- 3,Hover Micro-interaction,Complex,"hover, magnetic, cursor follow, 3d tilt",hover + mousemove,300-500ms,"elastic.out(1,0.4)","const xTo = gsap.quickTo(el, 'x', { duration: 0.4, ease: 'elastic.out(1,0.4)' }); const yTo = gsap.quickTo(el, 'y', { duration: 0.4, ease: 'elastic.out(1,0.4)' }); el.addEventListener('mousemove', (e) => { const r = el.getBoundingClientRect(); xTo((e.clientX - r.left - r.width/2) * 0.3); yTo((e.clientY - r.top - r.height/2) * 0.3); });",Debounce is not needed since quickTo interpolates; remove listeners on component unmount in React/Vue to avoid leaks,Clamp the pull strength (e.g. * 0.3) so the element never fully leaves its hit box,Don't apply magnetic effect to more than 1-2 focal elements per screen; it becomes noisy,Use will-change: transform on the target element for smoother compositing
5
- 4,Scroll Reveal,Subtle,"scroll, fade in, reveal, on view",scroll (viewport enter),300-400ms,power1.out,"gsap.from(el, { opacity: 0, y: 12, duration: 0.35, ease: 'power1.out', scrollTrigger: { trigger: el, start: 'top 90%', toggleActions: 'play none none reverse' } });",Requires the ScrollTrigger plugin registered once via gsap.registerPlugin(ScrollTrigger),"Keep the y offset small (8-16px) so it reads as a fade, not a slide",Don't reveal below-the-fold content needed for SEO/crawlers as invisible-by-default without a no-JS fallback,toggleActions 'play none none reverse' avoids re-triggering on every scroll direction change
6
- 5,Scroll Reveal,Standard,"scroll, slide up, staggered section, reveal",scroll (viewport enter),400-600ms,power2.out,"gsap.from(el.children, { opacity: 0, y: 24, duration: 0.5, stagger: 0.08, ease: 'power2.out', scrollTrigger: { trigger: el, start: 'top 85%' } });","In React use useGSAP(() => {...}, { scope: containerRef }) from @gsap/react to auto-cleanup on unmount",Scope the ScrollTrigger to the section container so it doesn't re-scan the whole page,Don't stagger more than ~8 children; beyond that the last items feel laggy,Set scroller/markers: false in production; markers is dev-only
7
- 6,Scroll Reveal,Complex,"scroll, pin, scrub, storytelling, scrollytelling",scroll (continuous scrub),tied to scroll position,none (scrub-driven),"gsap.timeline({ scrollTrigger: { trigger: section, start: 'top top', end: '+=150%', scrub: 1, pin: true } }).from('.headline', { opacity: 0, y: 40 }).to('.bg-layer', { yPercent: -20 }, '<');",Pinning needs the section to have deterministic height; recalc ScrollTrigger.refresh() after images/fonts load,Use scrub: true or a small number (0.5-1.5) instead of instant jumps so it feels tied to the scrollbar,Don't pin more than 1-2 sections per page; excessive pinning fights native scroll feel and hurts mobile UX,"Pinning forces layout reflow; test on mid-tier mobile devices, not just desktop"
8
- 7,Stagger List,Subtle,"list, stagger, cards, grid entrance",load or scroll,250-350ms,power1.out,"gsap.from('.list-item', { opacity: 0, y: 8, duration: 0.3, stagger: 0.03 });",Select items with a stable class/data-attribute (not array index) so re-renders in React don't break targeting,Keep per-item stagger delay small (0.02-0.04s) for lists longer than 10 items,Don't stagger by more than 0.1s per item on long lists; total reveal time becomes sluggish,"For virtualized lists, only animate items currently mounted in the DOM"
9
- 8,Stagger List,Standard,"grid, bento, cards, staggered scale",load or scroll,300-450ms,back.out(1.4),"gsap.from('.grid-item', { opacity: 0, scale: 0.92, y: 16, duration: 0.4, stagger: { each: 0.06, from: 'start', grid: 'auto' }, ease: 'back.out(1.4)' });",grid: 'auto' lets GSAP infer rows/columns from a CSS grid layout for a natural wave stagger,Combine with from: 'center' for a bento-grid layout to draw the eye inward first,Don't use back.out on dense data tables; the overshoot reads as sloppy on informational UI,Group DOM writes; avoid interleaving layout reads (getBoundingClientRect) between staggered tweens
10
- 9,Stagger List,Complex,"stagger, wave, text reveal, split text",load or scroll,400-700ms,expo.out,"const split = new SplitText(headline, { type: 'chars' }); gsap.from(split.chars, { opacity: 0, y: 20, rotateX: -40, duration: 0.6, stagger: 0.015, ease: 'expo.out' });",SplitText is a GSAP Club/paid plugin; confirm license before shipping and provide a plain fade fallback if unavailable,Revert SplitText on unmount/cleanup (split.revert()) to restore original text nodes for accessibility tools,Don't split-animate long paragraphs; reserve for short headlines (under ~8 words),Splitting text creates one element per character; keep it to headline-length copy only for DOM size
11
- 10,Page Transition,Subtle,"route change, fade, page transition",route change,200-300ms,power1.inOut,"gsap.to(main, { opacity: 0, duration: 0.2, onComplete: () => { navigate(); gsap.fromTo(main, { opacity: 0 }, { opacity: 1, duration: 0.2 }); } });","Pair with the router's transition hooks (Next.js App Router transitions, React Router's useNavigate, Vue Router's beforeEach/afterEach)",Preload the destination route's critical assets before the exit tween finishes,Don't block navigation on animation; cap exit duration at ~250ms so the app never feels unresponsive,Exit animation should always resolve faster than entrance (asymmetric timing) so back/forward feels snappy
12
- 11,Page Transition,Standard,"route change, slide, overlay wipe",route change,400-600ms,power2.inOut,"const tl = gsap.timeline(); tl.to('.transition-overlay', { yPercent: 0, duration: 0.4, ease: 'power2.inOut' }).call(navigate).to('.transition-overlay', { yPercent: -100, duration: 0.4, ease: 'power2.inOut', delay: 0.1 });",Keep the overlay element mounted at the layout root (outside the page component) so it survives the route swap,Show a lightweight loading indicator if the destination route's data fetch outlasts the overlay,Don't tie the overlay's reveal directly to data-fetch completion without a max-wait timeout; a slow API stalls the whole transition,Prefer CSS transform (yPercent) over top/left to keep the overlay animation on the compositor thread
13
- 12,Page Transition,Complex,"shared element, morph, hero transition",route change,500-800ms,expo.inOut,"const state = Flip.getState('.hero-image'); navigate(); Flip.from(state, { duration: 0.6, ease: 'expo.inOut', absolute: true, zIndex: 100 });",Requires the GSAP Flip plugin; the 'from' and 'to' route must render the same element with a shared data-flip-id,Verify the shared element exists in both DOM states before calling Flip.from to avoid a silent no-op,Don't use shared-element transitions across more than one element pair per navigation; compounding Flips are hard to time correctly,Flip recalculates layout (FLIP technique) so test on low-end devices for jank
14
- 13,Parallax Scroll,Subtle,"parallax, background, depth, scroll speed",scroll (continuous),tied to scroll position,linear (scrub),"gsap.to('.bg-layer', { yPercent: 10, ease: 'none', scrollTrigger: { trigger: section, scrub: true } });","Apply parallax to background/decorative layers only, never to text or interactive controls",Keep the yPercent delta small (5-15) so foreground and background never desync distractingly,Don't parallax body copy; it hurts reading comfort and can trigger motion sickness,will-change: transform on the parallax layer only; remove it after scroll settles to free GPU memory
15
- 14,Parallax Scroll,Standard,"multi-layer parallax, depth, hero background",scroll (continuous),tied to scroll position,linear (scrub),"gsap.utils.toArray('.parallax-layer').forEach((layer, i) => { gsap.to(layer, { yPercent: (i + 1) * -8, ease: 'none', scrollTrigger: { trigger: layer.parentElement, scrub: 0.5 } }); });",Layer count beyond 3-4 has diminishing visual return and multiplies scroll-listener cost,"Vary speed per layer (background slowest, foreground fastest) to sell the depth illusion",Don't let parallax layers overflow their container; clip with overflow: hidden on the wrapper,Batch all layers under one ScrollTrigger container where possible instead of one per layer
16
- 15,Loading / Skeleton,Subtle,"loading, skeleton, shimmer, pulse",on mount / async wait,1200-1600ms loop,sine.inOut,"gsap.to('.skeleton', { backgroundPosition: '200% 0', duration: 1.4, ease: 'sine.inOut', repeat: -1 });",Kill the loop tween (tween.kill()) as soon as real content mounts to avoid orphaned repeating animations,Use a CSS gradient background-position sweep rather than opacity pulsing; reads as 'loading' more clearly,Don't run more than one shimmer loop per skeleton group; sync them under one timeline so the wave reads as a single unit,repeat: -1 tweens are cheap but must be explicitly killed on unmount or they leak in SPA route changes
17
- 16,Loading / Skeleton,Standard,"progress, spinner, morphing loader",on mount / async wait,800-1200ms loop,power1.inOut,"gsap.timeline({ repeat: -1 }).to('.loader-dot', { y: -8, duration: 0.4, stagger: { each: 0.15, yoyo: true, repeat: 1 } });",Wrap the whole loop timeline in useGSAP with { revertOnUpdate: false } in React so it isn't rebuilt every render,Cap total loop duration under ~1.5s so long waits don't feel like the UI froze on a single beat,Don't use elaborate loaders for sub-300ms waits; they flash and feel worse than no indicator,Pause the timeline (tl.pause()) when the loading tab/view is not visible to save CPU on background tabs
1
+ No,Category,Intensity Tier,Keywords,Trigger,Duration,Easing,GSAP Snippet,Framework Notes,Do,Don't,Performance Notes,Override Condition
2
+ 1,Hover Micro-interaction,Subtle,"hover, button, opacity, lift, press feedback",hover,100-160ms,power1.out,"gsap.to(el, { y: -1, opacity: 0.9, duration: 0.15, ease: 'power1.out' });",Bind on mouseenter/mouseleave; in React wrap in a ref + useEffect (or onMouseEnter/onMouseLeave props directly calling gsap.to),Keep displacement under 2px so it reads as feedback not motion,Don't animate layout-affecting props (width/height/margin) on hover,Runs on transform/opacity only so it stays on the compositor thread,
3
+ 2,Hover Micro-interaction,Standard,"hover, card, scale, tilt, cursor feedback",hover,200-300ms,power2.out,"gsap.to(el, { y: -4, scale: 1.02, boxShadow: '0 12px 24px rgba(0,0,0,0.12)', duration: 0.25, ease: 'power2.out' });","Use gsap.quickTo(el, 'y') for cards with many hover targets to avoid re-creating tweens every event",Pair with a matching mouseleave tween that reverses the same properties,Don't leave the hover state stuck if the pointer leaves fast; always attach the reverse tween,quickTo() avoids GC churn on lists with 20+ hoverable cards,
4
+ 3,Hover Micro-interaction,Complex,"hover, magnetic, cursor follow, 3d tilt",hover + mousemove,300-500ms,"elastic.out(1,0.4)","const xTo = gsap.quickTo(el, 'x', { duration: 0.4, ease: 'elastic.out(1,0.4)' }); const yTo = gsap.quickTo(el, 'y', { duration: 0.4, ease: 'elastic.out(1,0.4)' }); el.addEventListener('mousemove', (e) => { const r = el.getBoundingClientRect(); xTo((e.clientX - r.left - r.width/2) * 0.3); yTo((e.clientY - r.top - r.height/2) * 0.3); });",Debounce is not needed since quickTo interpolates; remove listeners on component unmount in React/Vue to avoid leaks,Clamp the pull strength (e.g. * 0.3) so the element never fully leaves its hit box,Don't apply magnetic effect to more than 1-2 focal elements per screen; it becomes noisy,Use will-change: transform on the target element for smoother compositing,"M1 override — declared playful register: the magnetic pull is the affordance itself, not decoration on a state change"
5
+ 4,Scroll Reveal,Subtle,"scroll, fade in, reveal, on view",scroll (viewport enter),300-400ms,power1.out,"gsap.from(el, { opacity: 0, y: 12, duration: 0.35, ease: 'power1.out', scrollTrigger: { trigger: el, start: 'top 90%', toggleActions: 'play none none reverse' } });",Requires the ScrollTrigger plugin registered once via gsap.registerPlugin(ScrollTrigger),"Keep the y offset small (8-16px) so it reads as a fade, not a slide",Don't reveal below-the-fold content needed for SEO/crawlers as invisible-by-default without a no-JS fallback,toggleActions 'play none none reverse' avoids re-triggering on every scroll direction change,
6
+ 5,Scroll Reveal,Standard,"scroll, slide up, staggered section, reveal",scroll (viewport enter),400-600ms,power2.out,"gsap.from(el.children, { opacity: 0, y: 24, duration: 0.5, stagger: 0.08, ease: 'power2.out', scrollTrigger: { trigger: el, start: 'top 85%' } });","In React use useGSAP(() => {...}, { scope: containerRef }) from @gsap/react to auto-cleanup on unmount",Scope the ScrollTrigger to the section container so it doesn't re-scan the whole page,Don't stagger more than ~8 children; beyond that the last items feel laggy,Set scroller/markers: false in production; markers is dev-only,
7
+ 6,Scroll Reveal,Complex,"scroll, pin, scrub, storytelling, scrollytelling",scroll (continuous scrub),tied to scroll position,none (scrub-driven),"gsap.timeline({ scrollTrigger: { trigger: section, start: 'top top', end: '+=150%', scrub: 1, pin: true } }).from('.headline', { opacity: 0, y: 40 }).to('.bg-layer', { yPercent: -20 }, '<');",Pinning needs the section to have deterministic height; recalc ScrollTrigger.refresh() after images/fonts load,Use scrub: true or a small number (0.5-1.5) instead of instant jumps so it feels tied to the scrollbar,Don't pin more than 1-2 sections per page; excessive pinning fights native scroll feel and hurts mobile UX,"Pinning forces layout reflow; test on mid-tier mobile devices, not just desktop",
8
+ 7,Stagger List,Subtle,"list, stagger, cards, grid entrance",load or scroll,250-350ms,power1.out,"gsap.from('.list-item', { opacity: 0, y: 8, duration: 0.3, stagger: 0.03 });",Select items with a stable class/data-attribute (not array index) so re-renders in React don't break targeting,Keep per-item stagger delay small (0.02-0.04s) for lists longer than 10 items,Don't stagger by more than 0.1s per item on long lists; total reveal time becomes sluggish,"For virtualized lists, only animate items currently mounted in the DOM",
9
+ 8,Stagger List,Standard,"grid, bento, cards, staggered scale",load or scroll,300-450ms,back.out(1.4),"gsap.from('.grid-item', { opacity: 0, scale: 0.92, y: 16, duration: 0.4, stagger: { each: 0.06, from: 'start', grid: 'auto' }, ease: 'back.out(1.4)' });",grid: 'auto' lets GSAP infer rows/columns from a CSS grid layout for a natural wave stagger,Combine with from: 'center' for a bento-grid layout to draw the eye inward first,Don't use back.out on dense data tables; the overshoot reads as sloppy on informational UI,Group DOM writes; avoid interleaving layout reads (getBoundingClientRect) between staggered tweens,"M1 override — declared playful register: the overshoot is the bento-grid entrance affordance; refused on data tables, per this row's Don't"
10
+ 9,Stagger List,Complex,"stagger, wave, text reveal, split text",load or scroll,400-700ms,expo.out,"const split = new SplitText(headline, { type: 'chars' }); gsap.from(split.chars, { opacity: 0, y: 20, rotateX: -40, duration: 0.6, stagger: 0.015, ease: 'expo.out' });",SplitText is a GSAP Club/paid plugin; confirm license before shipping and provide a plain fade fallback if unavailable,Revert SplitText on unmount/cleanup (split.revert()) to restore original text nodes for accessibility tools,Don't split-animate long paragraphs; reserve for short headlines (under ~8 words),Splitting text creates one element per character; keep it to headline-length copy only for DOM size,
11
+ 10,Page Transition,Subtle,"route change, fade, page transition",route change,200-300ms,power1.inOut,"gsap.to(main, { opacity: 0, duration: 0.2, onComplete: () => { navigate(); gsap.fromTo(main, { opacity: 0 }, { opacity: 1, duration: 0.2 }); } });","Pair with the router's transition hooks (Next.js App Router transitions, React Router's useNavigate, Vue Router's beforeEach/afterEach)",Preload the destination route's critical assets before the exit tween finishes,Don't block navigation on animation; cap exit duration at ~250ms so the app never feels unresponsive,Exit animation should always resolve faster than entrance (asymmetric timing) so back/forward feels snappy,
12
+ 11,Page Transition,Standard,"route change, slide, overlay wipe",route change,300-500ms,power2.inOut,"const tl = gsap.timeline(); tl.to('.transition-overlay', { yPercent: 0, duration: 0.4, ease: 'power2.inOut' }).call(navigate).to('.transition-overlay', { yPercent: -100, duration: 0.4, ease: 'power2.inOut', delay: 0.1 });",Keep the overlay element mounted at the layout root (outside the page component) so it survives the route swap,Show a lightweight loading indicator if the destination route's data fetch outlasts the overlay,Don't tie the overlay's reveal directly to data-fetch completion without a max-wait timeout; a slow API stalls the whole transition,Prefer CSS transform (yPercent) over top/left to keep the overlay animation on the compositor thread,
13
+ 12,Page Transition,Complex,"shared element, morph, hero transition",route change,300-500ms,expo.inOut,"const state = Flip.getState('.hero-image'); navigate(); Flip.from(state, { duration: 0.45, ease: 'expo.inOut', absolute: true, zIndex: 100 });",Requires the GSAP Flip plugin; the 'from' and 'to' route must render the same element with a shared data-flip-id,Verify the shared element exists in both DOM states before calling Flip.from to avoid a silent no-op,Don't use shared-element transitions across more than one element pair per navigation; compounding Flips are hard to time correctly,Flip recalculates layout (FLIP technique) so test on low-end devices for jank,
14
+ 13,Parallax Scroll,Subtle,"parallax, background, depth, scroll speed",scroll (continuous),tied to scroll position,linear (scrub),"gsap.to('.bg-layer', { yPercent: 10, ease: 'none', scrollTrigger: { trigger: section, scrub: true } });","Apply parallax to background/decorative layers only, never to text or interactive controls",Keep the yPercent delta small (5-15) so foreground and background never desync distractingly,Don't parallax body copy; it hurts reading comfort and can trigger motion sickness,will-change: transform on the parallax layer only; remove it after scroll settles to free GPU memory,
15
+ 14,Parallax Scroll,Standard,"multi-layer parallax, depth, hero background",scroll (continuous),tied to scroll position,linear (scrub),"gsap.utils.toArray('.parallax-layer').forEach((layer, i) => { gsap.to(layer, { yPercent: (i + 1) * -8, ease: 'none', scrollTrigger: { trigger: layer.parentElement, scrub: 0.5 } }); });",Layer count beyond 3-4 has diminishing visual return and multiplies scroll-listener cost,"Vary speed per layer (background slowest, foreground fastest) to sell the depth illusion",Don't let parallax layers overflow their container; clip with overflow: hidden on the wrapper,Batch all layers under one ScrollTrigger container where possible instead of one per layer,
16
+ 15,Loading / Skeleton,Subtle,"loading, skeleton, shimmer, pulse",on mount / async wait,1200-1600ms loop,sine.inOut,"gsap.to('.skeleton', { backgroundPosition: '200% 0', duration: 1.4, ease: 'sine.inOut', repeat: -1 });",Kill the loop tween (tween.kill()) as soon as real content mounts to avoid orphaned repeating animations,Use a CSS gradient background-position sweep rather than opacity pulsing; reads as 'loading' more clearly,Don't run more than one shimmer loop per skeleton group; sync them under one timeline so the wave reads as a single unit,repeat: -1 tweens are cheap but must be explicitly killed on unmount or they leak in SPA route changes,
17
+ 16,Loading / Skeleton,Standard,"progress, spinner, morphing loader",on mount / async wait,800-1200ms loop,power1.inOut,"gsap.timeline({ repeat: -1 }).to('.loader-dot', { y: -8, duration: 0.4, stagger: { each: 0.15, yoyo: true, repeat: 1 } });",Wrap the whole loop timeline in useGSAP with { revertOnUpdate: false } in React so it isn't rebuilt every render,Cap total loop duration under ~1.5s so long waits don't feel like the UI froze on a single beat,Don't use elaborate loaders for sub-300ms waits; they flash and feel worse than no indicator,Pause the timeline (tl.pause()) when the loading tab/view is not visible to save CPU on background tabs,
@@ -6,13 +6,13 @@ No,Category,Issue,Platform,Description,Do,Don't,Code Example Good,Code Example B
6
6
  5,Navigation,Deep Linking,All,URLs should reflect current state for sharing,Update URL on state/view changes,Static URLs for dynamic content,Use query params or hash,Single URL for all states,Medium
7
7
  6,Navigation,Breadcrumbs,Web,Show user location in site hierarchy,Use for sites with 3+ levels of depth,Use for flat single-level sites,Home > Category > Product,Only on deep nested pages,Low
8
8
  7,Animation,Excessive Motion,All,Too many animations cause distraction and motion sickness,Animate 1-2 key elements per view maximum,Animate everything that moves,Single hero animation,animate-bounce on 5+ elements,High
9
- 8,Animation,Duration Timing,All,Animations should feel responsive not sluggish,Use 150-300ms for micro-interactions,Use animations longer than 500ms for UI,transition-colors duration-200,transition-all duration-1000,Medium
9
+ 8,Animation,Duration Timing,All,Animations should feel responsive not sluggish,Use the per-interaction bands in design-patterns.md § Motion,Use animations longer than 500ms for UI,transition-colors duration-200,transition-all duration-1000,Medium
10
10
  9,Animation,Reduced Motion,All,Respect user's motion preferences,Check prefers-reduced-motion media query,Ignore accessibility motion settings,@media (prefers-reduced-motion: reduce),No motion query check,High
11
11
  10,Animation,Loading States,All,Show feedback during async operations,Use skeleton screens or spinners,Leave UI frozen with no feedback,animate-pulse skeleton,Blank screen while loading,High
12
12
  11,Animation,Hover vs Tap,All,Hover effects don't work on touch devices,Use click/tap for primary interactions,Rely only on hover for important actions,onClick handler,onMouseEnter only,High
13
13
  12,Animation,Continuous Animation,All,Infinite animations are distracting,Use for loading indicators only,Use for decorative elements,animate-spin on loader,animate-bounce on icons,Medium
14
14
  13,Animation,Transform Performance,Web,Some CSS properties trigger expensive repaints,Use transform and opacity for animations,Animate width/height/top/left properties,transform: translateY(),top: 10px animation,Medium
15
- 14,Animation,Easing Functions,All,Linear motion feels robotic,Use ease-out for entering ease-in for exiting,Use linear for UI transitions,ease-out,linear,Low
15
+ 14,Animation,Easing Functions,All,Linear motion feels robotic,Use ease-out in both directions entering and exiting,Use linear for UI transitions,ease-out,linear,Low
16
16
  15,Layout,Z-Index Management,Web,Stacking context conflicts cause hidden elements,Define z-index scale system (10 20 30 50),Use arbitrary large z-index values,z-10 z-20 z-50,z-[9999],High
17
17
  16,Layout,Overflow Hidden,Web,Hidden overflow can clip important content,Test all content fits within containers,Blindly apply overflow-hidden,overflow-auto with scroll,overflow-hidden truncating content,Medium
18
18
  17,Layout,Fixed Positioning,Web,Fixed elements can overlap or be inaccessible,Account for safe areas and other fixed elements,Stack multiple fixed elements carelessly,Fixed nav + fixed bottom with gap,Multiple overlapping fixed elements,Medium
@@ -78,6 +78,23 @@ config, not a vibe — never re-infer when `DESIGN.md` already sets them
78
78
  | default / unstated | 5–6 | 3–4 | 4–5 |
79
79
  | data-dense / dashboard / admin / cockpit | 4–6 | 2–3 | 7–9 |
80
80
  | bold / playful / expressive / awards / Dribbble | 8–10 | 7–10 | 3–5 |
81
+ | **DISQUALIFIER** — brief `frequency: high` or `initiation: keyboard` | — | **cap 3** | — |
82
+
83
+ The Motion row is the one dial with a **disqualifier**, and it outranks the
84
+ brief signal above it rather than averaging with it. The two brief fields it
85
+ reads are `frequency` and `initiation` ([`fe-design`](../../fe-design/SKILL.md)
86
+ § The loop, step 2): a surface used 100-plus times a day, or reached by
87
+ keyboard, does not get a high Motion dial however expressive the rest of the
88
+ brief reads. A keyboard-initiated surface is reached by someone who already
89
+ knows where they are going, so the animation is latency they pay on every
90
+ invocation — the decision tree in
91
+ [`design-patterns.md`](../../fe-design/references/design-patterns.md) § Motion
92
+ answers "should this animate at all" with *no* for exactly this case, and a
93
+ dial that could still reach 9 would contradict it.
94
+
95
+ A `bold / playful` brief on a command palette therefore resolves to `V 8–10 ·
96
+ M ≤ 3 · D 3–5`, and the cap is stated in the Design Read line so the user can
97
+ see it fired rather than wondering why the motion is flat.
81
98
 
82
99
  **Dial → downstream levers** (how a dial value changes generation):
83
100
 
@@ -25,7 +25,7 @@
25
25
  | 4 | Style Selection | HIGH | `style`, `product` | Match product type, Consistency, SVG icons (no emoji) | Mixing flat & skeuomorphic randomly, Emoji as icons |
26
26
  | 5 | Layout & Responsive | HIGH | `ux` | Mobile-first breakpoints, Viewport meta, No horizontal scroll | Horizontal scroll, Fixed px container widths, Disable zoom |
27
27
  | 6 | Typography & Color | MEDIUM | `typography`, `color` | Base 16px, Line-height 1.5, Semantic color tokens | Text &lt; 12px body, Gray-on-gray, Raw hex in components |
28
- | 7 | Animation | MEDIUM | `ux` | Duration 150–300ms, Motion conveys meaning, Spatial continuity | Decorative-only animation, Animating width/height, No reduced-motion |
28
+ | 7 | Animation | MEDIUM | `ux` | Duration within the authority's per-interaction bands, Motion conveys meaning, Spatial continuity | Decorative-only animation, Animating width/height, No reduced-motion |
29
29
  | 8 | Forms & Feedback | MEDIUM | `ux` | Visible labels, Error near field, Helper text, Progressive disclosure | Placeholder-only label, Errors only at top, Overwhelm upfront |
30
30
  | 9 | Navigation Patterns | HIGH | `ux` | Predictable back, Bottom nav ≤5, Deep linking | Overloaded nav, Broken back behavior, No deep links |
31
31
  | 10 | Charts & Data | LOW | `chart` | Legends, Tooltips, Accessible colors | Relying on color alone to convey meaning |
@@ -146,16 +146,16 @@
146
146
 
147
147
  ### 7. Animation (MEDIUM)
148
148
 
149
- - `duration-timing` - Use 150–300ms for micro-interactions; complex transitions ≤400ms; avoid >500ms (MD)
149
+ - `duration-timing` - Use the per-interaction bands in `design-patterns.md` § Motion; never above 500ms (MD)
150
150
  - `transform-performance` - Use transform/opacity only; avoid animating width/height/top/left
151
151
  - `loading-states` - Show skeleton or progress indicator when loading exceeds 300ms
152
152
  - `excessive-motion` - Animate 1-2 key elements per view max
153
- - `easing` - Use ease-out for entering, ease-in for exiting; avoid linear for UI transitions
153
+ - `easing` - Use ease-out in both directions, entering and exiting; avoid linear for UI transitions
154
154
  - `motion-meaning` - Every animation must express a cause-effect relationship, not just be decorative (Apple HIG)
155
155
  - `state-transition` - State changes (hover / active / expanded / collapsed / modal) should animate smoothly, not snap
156
156
  - `continuity` - Page/screen transitions should maintain spatial continuity (shared element, directional slide) (Apple HIG)
157
157
  - `parallax-subtle` - Use parallax sparingly; must respect reduced-motion and not cause disorientation (Apple HIG)
158
- - `spring-physics` - Prefer spring/physics-based curves over linear or cubic-bezier for natural feel (Apple HIG fluid animations)
158
+ - `spring-physics` - Prefer critically damped spring curves (no overshoot) over linear for natural feel (Apple HIG fluid animations)
159
159
  - `exit-faster-than-enter` - Exit animations shorter than enter (~60–70% of enter duration) to feel responsive (MD motion)
160
160
  - `stagger-sequence` - Stagger list/grid item entrance by 30–50ms per item; avoid all-at-once or too-slow reveals (MD)
161
161
  - `shared-element-transition` - Use shared element / hero transitions for visual continuity between screens (MD, HIG)
@@ -1,8 +1,10 @@
1
1
  ---
2
2
  model_tier: high
3
3
  name: design-review
4
- description: "When the user says \"review the design\", \"check the UI\", or wants a comprehensive UI/UX review. Uses a 7-phase methodology covering interaction, responsiveness, accessibility and more."
4
+ description: "When the user says \"review the design\", \"check the UI\", or wants a comprehensive UI/UX review. 7 phases: interaction, responsiveness, accessibility. Inventory via existing-ui-audit."
5
5
  domain: quality
6
+ enforced_by:
7
+ - "instruction-only: the Phase 4 sweep claims WCAG 2.1 AA and its criteria table is a deliberate SUBSET — depth is delegated to accessibility-auditor, which claims 2.2 AA and carries the resolver. No resolver checks this table against 2.1, so the claim is model-carried and the gap is recorded rather than implied."
6
8
  workspaces:
7
9
  - engineering
8
10
  packs:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: inherit
3
3
  name: design-variations
4
- description: "Produce 3+ substantively distinct hi-fi design variations — basic to bold, one file with tweak controls — when the user asks for options, alternatives, or \"show me a few takes\"."
4
+ description: "Produce 3+ distinct hi-fi design variations — basic to bold, one file with tweak controls — when the user asks for options, alternatives, \"show me a few takes\". Extends fe-design."
5
5
  domain: product
6
6
  personas: []
7
7
  workspaces:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: high
3
3
  name: existing-ui-audit
4
- description: "Use BEFORE writing or editing any non-trivial UI — inventories components, design tokens, shadcn primitives, and reusable patterns into state.ui_audit. Hard gate for the ui directive set."
4
+ description: "Use BEFORE writing or editing any non-trivial UI — inventories components, design tokens, shadcn primitives, and reusable patterns into state.ui_audit. Hard gate for fe-design."
5
5
  personas:
6
6
  - frontend-engineer
7
7
  domain: discovery