wizz-method 2.21.0 → 2.22.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 (80) hide show
  1. package/THIRD-PARTY-NOTICES.md +18 -0
  2. package/package.json +4 -2
  3. package/skills-registry.yaml +41 -0
  4. package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +1 -1
  5. package/src/bmm-skills/4-implementation/wizz-code-review/SKILL.md +1 -1
  6. package/src/bmm-skills/4-implementation/wizz-create-story/SKILL.md +1 -1
  7. package/src/bmm-skills/4-implementation/wizz-dev-story/SKILL.md +8 -1
  8. package/src/bmm-skills/4-implementation/wizz-quick-dev/SKILL.md +2 -2
  9. package/src/bmm-skills/4-implementation/wizz-quick-dev/step-03-implement.md +2 -0
  10. package/src/bmm-skills/4-implementation/wizz-sprint-status/SKILL.md +1 -1
  11. package/src/core-skills/wizz-party-mode/customize.toml +3 -3
  12. package/src/modules/lowticket/skills/wizz-lowticket-trafego/references/reguas.md +2 -0
  13. package/src/modules/wizz/_shared/cerebro.md +1 -1
  14. package/src/modules/wizz/_shared/organizacao.md +2 -2
  15. package/src/modules/wizz/agents/wizz-copy/SKILL.md +1 -0
  16. package/src/modules/wizz/agents/wizz-copy/customize.toml +5 -0
  17. package/src/modules/wizz/agents/wizz-copy/knowledge/playbook.md +2 -1
  18. package/src/modules/wizz/agents/wizz-social/knowledge/blueprint-roteiro-viral.md +14 -0
  19. package/src/modules/wizz/agents/wizz-social/knowledge/legenda-instagram.md +12 -0
  20. package/src/skills-lib/wizz-cerebro/SKILL.md +18 -5
  21. package/src/skills-lib/wizz-design-needs/SKILL.md +7 -0
  22. package/src/skills-lib/wizz-design-needs/references/design-state.md +34 -2
  23. package/src/skills-lib/wizz-design-needs/references/gap-analysis.md +1 -1
  24. package/src/skills-lib/wizz-design-needs/scripts/design_state.py +305 -0
  25. package/src/skills-lib/wizz-design-needs/scripts/tests/test_design_state.py +143 -0
  26. package/src/skills-lib/wizz-impeccable/references/design-rules.md +18 -0
  27. package/src/skills-lib/wizz-motion-3d-director/references/motion-paths.md +2 -0
  28. package/src/skills-lib/wizz-paid-ads/SKILL.md +2 -1
  29. package/src/skills-lib/wizz-premium-landing-ui-researcher/SKILL.md +6 -2
  30. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/audit-protocol.md +7 -34
  31. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/component-sources.md +2 -0
  32. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/design-sources.yaml +252 -0
  33. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/mandatory-process.md +1 -1
  34. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/mining-rubric.md +123 -0
  35. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-first-protocol.md +10 -4
  36. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/web-access-and-evidence.md +1 -1
  37. package/src/skills-lib/wizz-premium-landing-ui-researcher/scripts/design_sources.py +221 -0
  38. package/src/skills-lib/wizz-premium-landing-ui-researcher/scripts/tests/test_design_sources.py +130 -0
  39. package/src/skills-lib/wizz-router/references/routing-table-flat.md +2 -0
  40. package/src/skills-lib/wizz-taste-skill/references/anti-slop-tells.md +8 -0
  41. package/src/skills-lib/wizz-ui-component-curator/SKILL.md +8 -7
  42. package/src/skills-lib/wizz-video-effects/LICENSE-motionmaxxing.txt +202 -0
  43. package/src/skills-lib/wizz-video-effects/SKILL.md +5 -2
  44. package/src/skills-lib/wizz-video-effects/assets/chrome/collect_layout.js +113 -0
  45. package/src/skills-lib/wizz-video-effects/assets/effects.config.template.json +35 -0
  46. package/src/skills-lib/wizz-video-effects/references/data-contract.md +30 -1
  47. package/src/skills-lib/wizz-video-effects/references/gates.md +3 -1
  48. package/src/skills-lib/wizz-video-effects/scripts/check_chrome.py +376 -0
  49. package/src/skills-lib/wizz-video-effects/scripts/check_dead_frames.py +193 -0
  50. package/src/skills-lib/wizz-video-effects/scripts/config.py +13 -0
  51. package/src/skills-lib/wizz-video-effects/scripts/test_chrome.py +181 -0
  52. package/src/skills-lib/wizz-video-effects/scripts/test_dead_frames.py +171 -0
  53. package/src/skills-lib/wizz-video-styles/SKILL.md +6 -2
  54. package/src/skills-lib/wizz-video-styles/references/motion-laws.md +62 -0
  55. package/src/skills-lib/wizz-video-styles/references/styles/desafio-numero.md +1 -0
  56. package/src/skills-lib/wizz-video-styles/scripts/blind_review.py +159 -0
  57. package/src/skills-lib/wizz-video-styles/scripts/taste.mjs +110 -0
  58. package/src/skills-lib/wizz-video-styles/scripts/test_blind_review.py +82 -0
  59. package/src/skills-lib/wizz-vsl/LICENSE +49 -0
  60. package/src/skills-lib/wizz-vsl/SKILL.md +122 -0
  61. package/src/skills-lib/wizz-vsl/references/assembly-qa.md +123 -0
  62. package/src/skills-lib/wizz-vsl/references/blocks-and-leads.md +149 -0
  63. package/src/skills-lib/wizz-vsl/references/diagnosis.md +95 -0
  64. package/src/skills-lib/wizz-vsl/references/intake.md +200 -0
  65. package/src/skills-lib/wizz-vsl/references/mechanisms.md +138 -0
  66. package/src/skills-lib/wizz-vsl/references/niches.md +40 -0
  67. package/src/skills-lib/wizz-vsl/references/offer.md +91 -0
  68. package/src/skills-lib/wizz-vsl/references/production.md +75 -0
  69. package/tools/hooks/README.md +10 -9
  70. package/tools/hooks/wizz-arrumar.js +1 -0
  71. package/tools/hooks/wizz-artifact-guard.js +31 -5
  72. package/tools/hooks/wizz-cerebro-lib.js +36 -13
  73. package/tools/hooks/wizz-feature-lib.js +4 -1
  74. package/tools/hooks/wizz-feature.js +66 -76
  75. package/tools/hooks/wizz-sprint-archive-lib.js +311 -0
  76. package/tools/installer/core/hooks-settings.js +1 -0
  77. package/tools/installer/core/installer.js +6 -0
  78. package/tools/installer/modules/core-seed.js +72 -0
  79. package/tools/installer/modules/mcp-config.js +3 -0
  80. package/tools/installer/ui.js +16 -26
@@ -0,0 +1,252 @@
1
+ # Design sources, as data. Read by scripts/design_sources.py (list by need tag,
2
+ # cache freshness, refresh) so the miner does not load the whole prose catalogue
3
+ # to pick one source. The prose in component-sources.md stays as the reference
4
+ # for policy and nuance (Clone Policy, Paid Source Policy, visual inspiration
5
+ # rules); this file is the one to query.
6
+ #
7
+ # Fields:
8
+ # kind code | registry | engine | visual | taste | extractor
9
+ # (code/registry = evidence bar of code; visual = pattern only, rebuilt)
10
+ # license what was read, with the date of the reading in `checked`; null = none
11
+ # access free | free+paid | paid | login
12
+ # need_tags what the source answers; matched by `design_sources.py list --need`
13
+ # registry shadcn namespace and item URL template, when it has one
14
+ # cache folder name under ~/.claude/design-sources/, when there is a local clone
15
+ # study_only true when the code must never be copied (no license)
16
+ version: 1
17
+ cache_root: "~/.claude/design-sources"
18
+ max_cache_age_days: 30
19
+ registry_index: "https://ui.shadcn.com/r/registries.json"
20
+
21
+ sources:
22
+ - id: react-bits
23
+ kind: registry
24
+ url: "https://github.com/DavidHDev/react-bits.git"
25
+ registry: { namespace: "@react-bits" }
26
+ license: "MIT + Commons Clause"
27
+ checked: 2026-09-06
28
+ access: free+paid
29
+ cache: react-bits
30
+ need_tags: [text-animation, background, scroll, hover, card, micro-interaction, loader, hero, particles, cursor]
31
+ note: "React Bits Pro is never a mandatory dependency."
32
+
33
+ - id: cult-ui
34
+ kind: registry
35
+ url: "https://github.com/nolly-studio/cult-ui"
36
+ docs: "https://www.cult-ui.com/docs"
37
+ registry: { namespace: "@cult-ui" }
38
+ license: null
39
+ checked: null
40
+ access: free+paid
41
+ cache: cult-ui
42
+ need_tags: [marketing-section, hero, button, card, landing-section]
43
+ note: "Read the license of the item; public/open resources only unless paid access is confirmed."
44
+
45
+ - id: ali-imam
46
+ kind: visual
47
+ url: "https://github.com/aliimam-in/aliimam.git"
48
+ license: null
49
+ checked: 2026-10-15
50
+ access: free
51
+ cache: aliimam
52
+ study_only: true
53
+ need_tags: [shader, liquid, ripple, border-glow, bento, typewriter, canvas, experimental]
54
+ note: "No license: study the technique, never copy the code."
55
+
56
+ - id: watermelon
57
+ kind: registry
58
+ url: "https://github.com/WatermelonCorp/watermellon-registry.git"
59
+ registry: { namespace: "@watermelon", template: "https://registry.watermelon.sh/r/{name}.json" }
60
+ license: null
61
+ checked: null
62
+ access: free
63
+ cache: watermelon
64
+ need_tags: [dashboard, saas, card, app-section, data-viz, product-ui]
65
+
66
+ - id: styleui
67
+ kind: registry
68
+ url: "https://github.com/heyfabrika/styleui.git"
69
+ registry: { namespace: "@styleui" }
70
+ license: null
71
+ checked: 2026-10-15
72
+ access: free
73
+ cache: styleui
74
+ need_tags: [layout, template, landing-section, base-page]
75
+ note: "Marked degraded in the shadcn index on 2026-10-15: check health before relying on it."
76
+
77
+ - id: componentry
78
+ kind: registry
79
+ url: "https://componentry.dev/"
80
+ registry: { namespace: "@componentry", template: "https://componentry.fun/r/{name}.json" }
81
+ license: null
82
+ checked: null
83
+ access: free
84
+ need_tags: [text-animation, background, hover, micro-interaction, animated-component]
85
+
86
+ - id: bklit-ui
87
+ kind: registry
88
+ url: "https://github.com/bklit/bklit-ui"
89
+ registry: { namespace: "@bklit" }
90
+ license: MIT
91
+ checked: null
92
+ access: free
93
+ need_tags: [chart, data-viz, dashboard, metrics]
94
+ note: "Install from the registry, do not clone."
95
+
96
+ - id: skiper-ui
97
+ kind: registry
98
+ url: null
99
+ registry: { namespace: "@skiper-ui" }
100
+ license: null
101
+ checked: null
102
+ access: free+paid
103
+ need_tags: [distinctive-effect, premium-section, advanced-animation]
104
+ note: "Must not be cloned: npx shadcn add @skiper-ui/<item>. Premium only with confirmed access."
105
+
106
+ - id: shadcn-ui
107
+ kind: registry
108
+ url: "https://ui.shadcn.com/"
109
+ license: MIT
110
+ checked: null
111
+ access: free
112
+ need_tags: [base, form, dialog, navigation, carousel, layout]
113
+
114
+ - id: embla
115
+ kind: code
116
+ url: "https://www.embla-carousel.com/"
117
+ license: MIT
118
+ checked: null
119
+ access: free
120
+ need_tags: [carousel]
121
+
122
+ - id: animmaster
123
+ kind: code
124
+ url: "https://animmasterlib.dev/"
125
+ license: "commercial"
126
+ checked: null
127
+ access: paid
128
+ need_tags: [animated-component]
129
+ note: "Only with authorized access; what the user already bought counts as local material."
130
+
131
+ - id: gsap
132
+ kind: engine
133
+ url: "https://github.com/greensock/GSAP.git"
134
+ license: "free since v3.13, all plugins"
135
+ checked: null
136
+ access: free
137
+ need_tags: [scroll-storytelling, scroll, timeline, text-reveal, svg-morph]
138
+
139
+ - id: animejs
140
+ kind: engine
141
+ url: "https://github.com/juliangarnier/anime.git"
142
+ license: MIT
143
+ checked: null
144
+ access: free
145
+ need_tags: [micro-interaction, stagger, svg, counter]
146
+
147
+ - id: design-motion-principles
148
+ kind: taste
149
+ url: "https://github.com/kylezantos/design-motion-principles.git"
150
+ license: null
151
+ checked: null
152
+ access: free
153
+ need_tags: [motion-principles, easing, timing]
154
+
155
+ - id: vibe-code-components
156
+ kind: visual
157
+ url: "https://vibecodecomponents.com/"
158
+ license: null
159
+ checked: null
160
+ access: free
161
+ need_tags: [visual-direction, section-idea]
162
+
163
+ - id: landing-love
164
+ kind: visual
165
+ url: "https://www.landing.love/"
166
+ license: null
167
+ checked: null
168
+ access: free
169
+ need_tags: [visual-direction, scroll, hero, landing-section]
170
+
171
+ - id: recent
172
+ kind: visual
173
+ url: "https://recent.design/"
174
+ license: null
175
+ checked: 2026-10-15
176
+ access: free
177
+ need_tags: [visual-direction, typography, editorial, art-direction]
178
+
179
+ - id: design-spells
180
+ kind: visual
181
+ url: "https://www.designspells.com/"
182
+ license: null
183
+ checked: null
184
+ access: free
185
+ need_tags: [micro-interaction, delight, hover]
186
+
187
+ - id: spline
188
+ kind: visual
189
+ url: "https://spline.design/"
190
+ license: null
191
+ checked: null
192
+ access: free+paid
193
+ need_tags: [3d, hero]
194
+
195
+ - id: unicorn-studio
196
+ kind: visual
197
+ url: "https://www.unicorn.studio/"
198
+ license: null
199
+ checked: null
200
+ access: free+paid
201
+ need_tags: [webgl, background, shader, particles]
202
+
203
+ - id: refero
204
+ kind: visual
205
+ url: "https://refero.design/"
206
+ license: null
207
+ checked: 2026-10-15
208
+ access: login
209
+ need_tags: [ux-flow, dashboard, pricing, onboarding]
210
+ note: "Refero MCP is paid."
211
+
212
+ - id: refero-styles
213
+ kind: taste
214
+ url: "https://styles.refero.design/"
215
+ license: null
216
+ checked: null
217
+ access: free
218
+ need_tags: [design-md, typography, palette, spacing]
219
+
220
+ - id: awesome-design-md
221
+ kind: taste
222
+ url: "https://github.com/VoltAgent/awesome-design-md"
223
+ license: MIT
224
+ checked: 2026-10-15
225
+ access: free
226
+ need_tags: [design-md, typography, palette, spacing]
227
+
228
+ - id: mobbin
229
+ kind: visual
230
+ url: "https://mobbin.com/"
231
+ license: null
232
+ checked: 2026-10-15
233
+ access: login
234
+ need_tags: [ux-flow, onboarding, pricing, dashboard, mobile]
235
+ note: "Mobbin MCP is paid."
236
+
237
+ - id: screensdesign
238
+ kind: visual
239
+ url: "https://screensdesign.com/"
240
+ license: null
241
+ checked: 2026-10-15
242
+ access: login
243
+ need_tags: [mobile, onboarding, paywall, subscription]
244
+
245
+ - id: dembrandt
246
+ kind: extractor
247
+ url: "https://github.com/dembrandt/dembrandt"
248
+ license: MIT
249
+ checked: 2026-10-15
250
+ access: free
251
+ need_tags: [typography, palette, spacing, design-md]
252
+ note: "npx -y dembrandt@0.39.2 <url> --design-md: measures a site the user named."
@@ -8,7 +8,7 @@ Always follow this process. **Steps 6, 7, and 8 are SOURCE CHECKPOINTS and must
8
8
  4. Define the visual direction.
9
9
  5. Decide whether it needs a landing page, dashboard, 3D/WebGL, or a combination.
10
10
  6. **CHECKPOINT: Inventory of the user's sources.** List and inspect (ls + minimal reading of READMEs/package.json) the current project and the models at the paths provided by the user; reuse the memory summary from the handoff. Identify which components/effects the user already has that can be reused before any external source.
11
- 7. **CHECKPOINT: Source-First Protocol (mandatory).** Run [source-first-protocol](source-first-protocol.md): search public sources, open demos and code/registry, check revision, license, and dependencies. Log evidence and failures in the research artifact; show 1-3 real candidates per component.
11
+ 7. **CHECKPOINT: Source-First Protocol (mandatory).** Run [source-first-protocol](source-first-protocol.md): search public sources, open demos and code/registry, check revision, license, and dependencies. Log evidence and failures in the research artifact; judge each verified candidate with [mining-rubric](mining-rubric.md) and show the selected one per need, with at most two runners-up and their verdicts.
12
12
  8. **CHECKPOINT: Choose sources.** Recommend the appropriate combination. With direction and implementation already authorized, proceed; only ask if a material decision is missing.
13
13
  9. Recommend components and animations (already curated in the previous checkpoints).
14
14
  10. Write copy and structure.
@@ -0,0 +1,123 @@
1
+ # Mining rubric: entry conditions, score, verdict, stop and adaptation
2
+
3
+ Shared by `wizz-premium-landing-ui-researcher` and `wizz-ui-component-curator`. Read it before judging the first candidate of a need. It turns "this looks good" into a verdict that the next session, the implementer and QA can read back from `design-state.yaml`.
4
+
5
+ The discipline is ported from the offer miner (`wizz-lowticket-minerador`): conditions checked before any score, a 0-10 score with a cut at 6, an enumerated verdict, a stop rule per need, observation kept apart from inference, and a fixed read order that rebuilds instead of copying.
6
+
7
+ Order of work for each candidate, always the same:
8
+
9
+ 1. **Evidence gate** ([web-access-and-evidence](web-access-and-evidence.md)). Missing any of the five fields = `evidence: unverified`. Unverified is not scored and never selected; it can be verified later.
10
+ 2. **Dedup.** Its `pattern_key` is already `selected` in the project → `status: rejected`, `dup_of: <that discovery>`. Not scored.
11
+ 3. **Entry conditions.** One unmet = `verdict: DESCARTAR` with the code in `disqualifiers`. Not scored.
12
+ 4. **Score** the five criteria, sum, read the verdict from the band.
13
+ 5. **Select or keep searching**, by the selection and stop rules below.
14
+ 6. **Adapt** the selected one with the five-step protocol.
15
+
16
+ ## Entry conditions
17
+
18
+ Each one is a condition of use, not a ban: the line says what makes the candidate eligible. A candidate that does not meet a condition is recorded with the code and the reason, so the next round does not open it again.
19
+
20
+ | Code | The candidate is eligible when | Unmet means |
21
+ |---|---|---|
22
+ | `licenca` | a code source (`kind: code` or `registry`) has a license that allows use in this project | no license or an incompatible one. Reclassify it as `kind: visual` and it is scored as a pattern to rebuild, never as code to copy |
23
+ | `acesso_pago` | paid access is already authorized by the user | access or cost not confirmed (the user decides, it is a faixa C item) |
24
+ | `dependencia` | its dependencies fit the stack and the bundle budget of `design-state.stack` | it pulls a dependency outside the stack or above the budget, and the user has not approved going over |
25
+ | `reduced_motion` | it honours `prefers-reduced-motion`, or the adaptation plan writes that path | it ignores reduced motion and nobody wrote the alternative |
26
+ | `contraste` | its contrast passes AA once the project's tokens are applied | the effect itself breaks AA (text over moving image with no scrim, glow under body text) |
27
+ | `nivel` | the motion is allowed at the site level ([site-levels](site-levels.md)) | the level lists that motion as one to avoid |
28
+ | `proibido` | it does not land on a capability in the `proibidos` block | it does. A reference the user chose already moved its item out of `proibidos` (derivation rule 4.4 of `wizz-design-needs`), so it is eligible |
29
+
30
+ ## Score
31
+
32
+ Five criteria, summed to 0-10. Score only what was observed in this session; a criterion that rests on an inference is scored from what was seen and the inference is written in `inferred`.
33
+
34
+ | Criterion | Range | Top of the range | Bottom of the range |
35
+ |---|---|---|---|
36
+ | `aderencia` | 0-3 | answers the need's `question` and fits the style DNA and brief signals as is | adjacent to the need, does not answer it |
37
+ | `execucao` | 0-2 | polished in the live demo: timing, states, resize, mobile | broken or janky. Inspected static only: at most 1 |
38
+ | `custo_adaptacao` | 0-2, inverted | token-level adaptation (colour, type, spacing) | most of it has to be rewritten |
39
+ | `custo_performance` | 0-2, inverted | no new dependency, no layout thrash | heavy runtime (WebGL, video, large library) for what it adds |
40
+ | `originalidade` | 0-1 | not the default look of the stack or the catalogue | reads as template |
41
+
42
+ Middle values: `aderencia` 2 = answers the question with visible restyling, 1 = answers part of it; `execucao` 1 = works with rough edges; `custo_adaptacao` 1 = markup or API changes; `custo_performance` 1 = a new dependency inside the budget, or a measurable runtime cost.
43
+
44
+ ## Verdict
45
+
46
+ | Verdict | Score | Use |
47
+ |---|---|---|
48
+ | `ASSINATURA` | 9-10 | the piece that defines the surface. At most one selected per surface |
49
+ | `FORTE` | 7-8 | enters |
50
+ | `UTILITARIO` | 6 | enters only when the need has nothing better |
51
+ | `DESCARTAR` | 0-5, or an unmet entry condition | does not enter; the reason stays recorded |
52
+
53
+ The cut is 6. A need's `floor` can raise it for that need, never lower it: a 7 on a need with `floor: 8` stays `FORTE` and is not selected for that need.
54
+
55
+ The signature cap is what stops a page from becoming a collage of impressive effects. When a second candidate on the same surface scores 9 or 10, it is recorded as `FORTE` with `reason: "teto de assinatura"`.
56
+
57
+ ## Selection
58
+
59
+ Per need, one discovery is selected: the highest-scoring verified, eligible candidate at or above the need's floor.
60
+
61
+ - **Faixa A, silent:** the first leads the second by 2 points or more and the need is not `signature: true`. Select and move on.
62
+ - **Faixa B, announced:** a tie within 1 point, or the signature piece of a surface. Select, then say which one won, the runner-up and why. The swap costs one edit to the state file.
63
+ - `UTILITARIO` is selected only when no `FORTE` or `ASSINATURA` is eligible for that need.
64
+ - Everything else on the need stays `proposed` or goes to `rejected` with a `reason`.
65
+
66
+ ## Stop conditions
67
+
68
+ The miner stops on the first one that applies. Each stop writes the need's status and reason to the state file.
69
+
70
+ | Scope | Stops when | Need status |
71
+ |---|---|---|
72
+ | Need, early | an eligible candidate scores 9 or more | `selected` |
73
+ | Need, budget | the sources opened reach `budget.fontes` | `selected` if anything eligible, else `unmet` |
74
+ | Need, diminishing returns | 2 sources in a row with nothing eligible at or above the floor | `unmet`, with what was tried. A result, not a failure |
75
+ | Dedup | the need's question is already answered by a selected discovery on another surface | `dropped`, `reason: "resolvido por <need-id>"` |
76
+ | Surface | the selected behaviour discoveries reach the level's behaviour ceiling (derivation rule 3 of `wizz-design-needs`) | the rest `deferred` |
77
+ | Global | every need is `selected`, `unmet`, `dropped` or `deferred` | mining is over. This is "I have enough information" |
78
+
79
+ Count the spend on the need itself (`spent.fontes`, `spent.secas`) so a reopened need starts from what was already paid.
80
+
81
+ ## Observation and inference
82
+
83
+ Every discovery keeps two lists. `observed`: what was actually seen in this session (a duration in the code, an import, the demo jank at 375 px). `inferred`: the reading of it ("probably uses GSAP ScrollTrigger"). A score rests on `observed`. An inference written as an observation is the failure mode this split exists to catch.
84
+
85
+ ## Adaptation protocol
86
+
87
+ Fixed read order, the design twin of the offer miner's "Demanda → Hook → Promessa…":
88
+
89
+ **Pattern → Why it works → Invariant → Variable → Reimplementation**
90
+
91
+ - **Pattern** (`padrao`): what happens mechanically, in one sentence, with no brand name.
92
+ - **Why it works** (`por_que`): the perceptual or conversion reason. If it cannot be written, the discovery was not understood: it goes back to evaluation and is not selected.
93
+ - **Invariant** (`invariante`): what cannot change without breaking the effect.
94
+ - **Variable** (`variavel`): what is free (easing, duration, direction, colour, density).
95
+ - **Reimplementation** (`reimplementacao`): the plan in the project's tokens and vocabulary.
96
+
97
+ Closing rule, in the same spirit as the offer miner's: **a visual reference gives structure and principle, never CSS.** A code source with a license can be copied with attribution; a visual source is rebuilt.
98
+
99
+ ## Machine-readable block
100
+
101
+ The eval (`evals/design-mining/`) and the state checker (`wizz-design-needs/scripts/design_state.py`) read the numbers from this block. Change a band here and both follow; nothing else holds a copy.
102
+
103
+ ```yaml
104
+ # mining-rubric:v1
105
+ cut: 6
106
+ criteria:
107
+ aderencia: [0, 3]
108
+ execucao: [0, 2]
109
+ custo_adaptacao: [0, 2]
110
+ custo_performance: [0, 2]
111
+ originalidade: [0, 1]
112
+ verdicts:
113
+ - { id: ASSINATURA, min: 9, max: 10 }
114
+ - { id: FORTE, min: 7, max: 8 }
115
+ - { id: UTILITARIO, min: 6, max: 6 }
116
+ - { id: DESCARTAR, min: 0, max: 5 }
117
+ disqualifiers: [licenca, acesso_pago, dependencia, reduced_motion, contraste, nivel, proibido]
118
+ signature_per_surface: 1
119
+ signature_cap_reason: "teto de assinatura"
120
+ faixa_a_lead: 2
121
+ early_stop_score: 9
122
+ dry_sources_to_unmet: 2
123
+ ```
@@ -12,7 +12,7 @@ Log the real paths of the resources found. If there are no local models, proceed
12
12
 
13
13
  ## Phase 2: Search and inspect public sources
14
14
 
15
- Choose sources based on the effect needed, using [component-sources](component-sources.md) and [source-links](source-links.md):
15
+ Choose sources by querying the catalogue as data instead of reading the whole prose: `uv run --no-cache scripts/design_sources.py list --need <tag>` (run from this skill's folder; tags such as `hover`, `scroll`, `card`, `dashboard`, `chart`, `shader`, `typography`) returns the matching sources from [design-sources.yaml](design-sources.yaml) with kind, access, license and `study_only`. Without `uv`, read the YAML directly. [component-sources](component-sources.md) keeps the policy and nuance (clone, paid, visual inspiration) and [source-links](source-links.md) the links; open them when a source needs that detail. The quick map, for orientation:
16
16
 
17
17
  | Need | Initial sources |
18
18
  |---|---|
@@ -50,7 +50,7 @@ Most sources are shadcn registries. Read them live instead of cloning:
50
50
  2. **Read the item.** Replace `{name}` in the URL template and fetch the JSON: it carries the files, dependencies and registry dependencies, which is the evidence the gate asks for. When the shadcn MCP is configured (`npx shadcn@latest mcp init`), it does the same lookup for you.
51
51
  3. **Repos outside the index** (and offline work) use the local cache.
52
52
 
53
- An existing design cache (for example `~/.claude/design-sources/`) can be inspected. Don't treat it as up to date just because it exists:
53
+ An existing design cache (for example `~/.claude/design-sources/`) can be inspected. Don't treat it as up to date just because it exists. One command answers it: `uv run --no-cache scripts/design_sources.py freshness` gives each cached clone's revision, last sync and `stale` past 30 days (`--remote` also compares with origin). A stale cache is still a source for a pattern, never for an API. `design_sources.py refresh` fast-forwards only the clones with no local changes and reports the rest; run it when the user agrees to update. The rules below still hold:
54
54
 
55
55
  - Check origin, branch, revision, and local changes before updating.
56
56
  - Check the remote revision when there's network access; the folder's modification date is not proof of being up to date.
@@ -70,10 +70,16 @@ Log in the existing design artifact (or create `design-research.md` in the proje
70
70
 
71
71
  Also include searches with no results and unavailable sources. Never fill in component names, paths, license, or test results by assumption. A row missing any of the five fields in [web-access-and-evidence](web-access-and-evidence.md) is UNVERIFIED and cannot be the recommendation.
72
72
 
73
- Show 1-3 compatible candidates per component, with a recommendation and adaptation cost. If the user has already authorized the implementation and defined the direction, choose the best option within that scope and proceed. Only ask when a material decision about direction, paid access, or scope change is missing.
73
+ Judge every verified candidate with [mining-rubric](mining-rubric.md): dedup, entry conditions, the 0-10 score with the cut at 6, the verdict, and the selection rule. With a `design-state.yaml`, each candidate is written as a discovery with its need id, scores, verdict and status; the table above is the human-readable view of the same rows.
74
+
75
+ Show the selected candidate per need and at most two runners-up, each with its verdict and score. If the user has already authorized the implementation and defined the direction, the selection rule decides and you proceed. Only ask when a material decision about direction, paid access, or scope change is missing.
74
76
 
75
77
  ## Phase 5: Adapt and verify
76
78
 
77
- Adapt tokens, typography, spacing, content, and motion to the project. Verify responsiveness, keyboard navigation, focus, contrast, reduced-motion, and rendering cost as appropriate for the component. Link the result back to the logged sources.
79
+ Write the adaptation in the five-step order of [mining-rubric](mining-rubric.md) (pattern, why it works, invariant, variable, reimplementation) before touching code. Then adapt tokens, typography, spacing, content, and motion to the project. Verify responsiveness, keyboard navigation, focus, contrast, reduced-motion, and rendering cost as appropriate for the component. Link the result back to the logged sources.
78
80
 
79
81
  Building from scratch is acceptable for trivial primitives or when the documented research finds no compatible option and the implementation is authorized. Explain the reason. Don't claim something doesn't exist in any catalog; only report the sources actually researched.
82
+
83
+ ## Phase 6: Stop
84
+
85
+ Mining stops by rule, not by patience. Per need: an eligible candidate at 9 or more, the source budget spent, or 2 sources in a row with nothing eligible (`unmet`, with what was tried). Per surface: the level's behaviour ceiling. Globally: every need in a terminal status. The table and the status each stop writes are in [mining-rubric](mining-rubric.md). Report which stop closed each need.
@@ -36,6 +36,6 @@ Every candidate needs all five, collected in this session:
36
36
  | Dependencies | Read from the actual imports or registry JSON | Guessed from the component's name |
37
37
  | Source read | The code file was opened and its imports checked | The demo alone |
38
38
 
39
- A candidate missing any field is **UNVERIFIED**. UNVERIFIED items go in a separate section of the report, with what is missing and why. They are never recommended and never placed in the top 1-3, and the implementation plan never depends on them. If every candidate for a need is UNVERIFIED, the need is reported as `unmet`, not filled.
39
+ A candidate missing any field is **UNVERIFIED**. UNVERIFIED items go in a separate section of the report, with what is missing and why. They are never recommended and never scored or selected by the [mining-rubric](mining-rubric.md), and the implementation plan never depends on them. If every candidate for a need is UNVERIFIED, the need is reported as `unmet`, not filled.
40
40
 
41
41
  Before closing, check the delivery against this gate once: every recommended item has its five fields in the research log (`design-research.md` or the project's design artifact), and the access line from Step 0 is in the report.
@@ -0,0 +1,221 @@
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = ["ruamel.yaml>=0.18"]
4
+ # ///
5
+ """Query the design-source catalogue and keep the local cache honest.
6
+
7
+ Prints ONLY JSON to stdout. Reads ../references/design-sources.yaml.
8
+
9
+ Subcommands:
10
+ list Sources that answer a need tag (--need, repeatable), optionally
11
+ filtered by --kind. Paid sources are listed with their access so
12
+ the miner can apply the paid-source condition; study_only sources
13
+ say so.
14
+ freshness For every source with a `cache` folder: revision, last commit,
15
+ last sync with the remote (FETCH_HEAD, else the clone date), age
16
+ of that sync in days, local changes, and `stale` when the age
17
+ passes max_cache_age_days. --remote also compares HEAD with
18
+ origin (network). Exit 0 always; the JSON carries the verdict.
19
+ refresh Fast-forward the cached clones that have no local changes
20
+ (`git fetch` + `git merge --ff-only`). A clone with local changes
21
+ is skipped and reported, never reset. --id limits it to one source.
22
+
23
+ Run with `uv run --no-cache scripts/design_sources.py <subcommand>`: uv
24
+ provisions ruamel.yaml from the header above.
25
+ """
26
+
27
+ import argparse
28
+ import datetime as dt
29
+ import json
30
+ import os
31
+ import subprocess
32
+ import sys
33
+ from pathlib import Path
34
+
35
+ HERE = Path(__file__).resolve().parent
36
+ CATALOGUE = HERE.parent / "references" / "design-sources.yaml"
37
+
38
+
39
+ def emit(payload, code=0):
40
+ print(json.dumps(payload, ensure_ascii=False, indent=2, default=str))
41
+ sys.exit(code)
42
+
43
+
44
+ def load_catalogue(path):
45
+ from ruamel.yaml import YAML
46
+
47
+ yaml = YAML(typ="safe")
48
+ with open(path, encoding="utf-8") as handle:
49
+ data = yaml.load(handle)
50
+ if not isinstance(data, dict) or not isinstance(data.get("sources"), list):
51
+ raise ValueError(f"{path}: expected a mapping with a `sources` list")
52
+ return data
53
+
54
+
55
+ def cmd_list(data, needs, kind):
56
+ wanted = {tag.lower() for tag in needs}
57
+ rows = []
58
+ for source in data["sources"]:
59
+ tags = {str(tag).lower() for tag in source.get("need_tags") or []}
60
+ if wanted and not wanted & tags:
61
+ continue
62
+ if kind and source.get("kind") != kind:
63
+ continue
64
+ rows.append(
65
+ {
66
+ "id": source["id"],
67
+ "kind": source.get("kind"),
68
+ "access": source.get("access"),
69
+ "license": source.get("license"),
70
+ "study_only": bool(source.get("study_only")),
71
+ "matches": sorted(wanted & tags) if wanted else [],
72
+ "url": source.get("url"),
73
+ "registry": source.get("registry"),
74
+ "note": source.get("note"),
75
+ }
76
+ )
77
+ rows.sort(key=lambda row: (-len(row["matches"]), row["access"] != "free", row["id"]))
78
+ return {"need": sorted(wanted), "count": len(rows), "sources": rows}
79
+
80
+
81
+ def git(repo, *args):
82
+ result = subprocess.run(
83
+ ["git", "-C", str(repo), *args], capture_output=True, text=True, timeout=120
84
+ )
85
+ if result.returncode != 0:
86
+ raise RuntimeError(result.stderr.strip() or f"git {' '.join(args)} failed")
87
+ return result.stdout.strip()
88
+
89
+
90
+ def cache_root(data, override):
91
+ root = override or data.get("cache_root") or "~/.claude/design-sources"
92
+ return Path(os.path.expanduser(root))
93
+
94
+
95
+ def last_sync(repo):
96
+ """When the clone last talked to its remote.
97
+
98
+ FETCH_HEAD is rewritten by every fetch. A clone that was never fetched has
99
+ no FETCH_HEAD, so the last HEAD reflog entry (the clone itself, or the last
100
+ pull) is used. The .git folder's mtime is not: `git status` touches it.
101
+ """
102
+ git_dir = repo / ".git"
103
+ marker = git_dir / "FETCH_HEAD"
104
+ if marker.exists():
105
+ return dt.date.fromtimestamp(marker.stat().st_mtime)
106
+ reflog = git_dir / "logs" / "HEAD"
107
+ if reflog.exists():
108
+ lines = [line for line in reflog.read_text(encoding="utf-8").splitlines() if line.strip()]
109
+ if lines:
110
+ identity = lines[-1].split("\t", 1)[0].split()
111
+ return dt.date.fromtimestamp(int(identity[-2]))
112
+ return None
113
+
114
+
115
+ def remote_status(repo, revision):
116
+ try:
117
+ line = git(repo, "ls-remote", "origin", "HEAD")
118
+ except RuntimeError as error:
119
+ return {"remote": f"unreachable: {error}"}
120
+ remote = line.split()[0] if line else ""
121
+ return {"remote": "current" if remote.startswith(revision) else "behind", "remote_revision": remote[:8]}
122
+
123
+
124
+ def inspect_cache(repo, max_age, today, check_remote):
125
+ if not (repo / ".git").exists():
126
+ return {"present": repo.is_dir(), "error": None if not repo.is_dir() else "not a git clone"}
127
+ try:
128
+ revision = git(repo, "rev-parse", "--short", "HEAD")
129
+ committed = git(repo, "log", "-1", "--format=%cs")
130
+ dirty = bool(git(repo, "status", "--porcelain"))
131
+ except RuntimeError as error:
132
+ return {"present": True, "error": str(error)}
133
+ synced = last_sync(repo)
134
+ if synced is None:
135
+ return {"present": True, "revision": revision, "last_commit": committed, "error": "no fetch or reflog record"}
136
+ age = (today - synced).days
137
+ row = {
138
+ "present": True,
139
+ "revision": revision,
140
+ "last_commit": committed,
141
+ "last_sync": synced.isoformat(),
142
+ "age_days": age,
143
+ "local_changes": dirty,
144
+ "stale": age > max_age,
145
+ }
146
+ if check_remote:
147
+ row.update(remote_status(repo, revision))
148
+ return row
149
+
150
+
151
+ def cmd_freshness(data, root, today, check_remote=False):
152
+ max_age = int(data.get("max_cache_age_days") or 30)
153
+ rows = []
154
+ for source in data["sources"]:
155
+ if not source.get("cache"):
156
+ continue
157
+ row = {"id": source["id"], "path": str(root / source["cache"])}
158
+ row.update(inspect_cache(root / source["cache"], max_age, today, check_remote))
159
+ rows.append(row)
160
+ stale = [row["id"] for row in rows if row.get("stale")]
161
+ return {"max_age_days": max_age, "today": today.isoformat(), "stale": stale, "caches": rows}
162
+
163
+
164
+ def cmd_refresh(data, root, only_id):
165
+ rows = []
166
+ for source in data["sources"]:
167
+ if not source.get("cache") or (only_id and source["id"] != only_id):
168
+ continue
169
+ repo = root / source["cache"]
170
+ row = {"id": source["id"], "path": str(repo)}
171
+ if not repo.is_dir():
172
+ row["result"] = "absent"
173
+ else:
174
+ try:
175
+ if git(repo, "status", "--porcelain"):
176
+ row["result"] = "skipped: local changes"
177
+ else:
178
+ before = git(repo, "rev-parse", "--short", "HEAD")
179
+ git(repo, "fetch", "--quiet")
180
+ git(repo, "merge", "--ff-only", "--quiet", "@{u}")
181
+ after = git(repo, "rev-parse", "--short", "HEAD")
182
+ row["result"] = "updated" if after != before else "already current"
183
+ row["revision"] = after
184
+ except RuntimeError as error:
185
+ row["result"] = f"error: {error}"
186
+ rows.append(row)
187
+ return {"caches": rows}
188
+
189
+
190
+ def main(argv=None):
191
+ parser = argparse.ArgumentParser(prog="design_sources.py")
192
+ parser.add_argument("--catalogue", default=str(CATALOGUE))
193
+ parser.add_argument("--cache-root", default=None)
194
+ sub = parser.add_subparsers(dest="command", required=True)
195
+ listing = sub.add_parser("list")
196
+ listing.add_argument("--need", action="append", default=[])
197
+ listing.add_argument("--kind", default=None)
198
+ fresh = sub.add_parser("freshness")
199
+ fresh.add_argument("--today", default=None, help="YYYY-MM-DD, for tests")
200
+ fresh.add_argument("--remote", action="store_true", help="compare HEAD with origin (network)")
201
+ refresh = sub.add_parser("refresh")
202
+ refresh.add_argument("--id", default=None)
203
+ try:
204
+ args = parser.parse_args(argv)
205
+ except SystemExit as error:
206
+ emit({"error": "invalid arguments"}, int(error.code or 2))
207
+ try:
208
+ data = load_catalogue(args.catalogue)
209
+ except (OSError, ValueError, ImportError) as error:
210
+ emit({"error": str(error)}, 1)
211
+ root = cache_root(data, args.cache_root)
212
+ if args.command == "list":
213
+ emit(cmd_list(data, args.need, args.kind))
214
+ if args.command == "freshness":
215
+ today = dt.date.fromisoformat(args.today) if args.today else dt.date.today()
216
+ emit(cmd_freshness(data, root, today, args.remote))
217
+ emit(cmd_refresh(data, root, args.id))
218
+
219
+
220
+ if __name__ == "__main__":
221
+ main()