wizz-method 2.21.0 → 2.23.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 (179) hide show
  1. package/THIRD-PARTY-NOTICES.md +24 -0
  2. package/package.json +4 -2
  3. package/removals.txt +3 -0
  4. package/skills-registry.yaml +133 -4
  5. package/src/bmm-skills/1-analysis/research/wizz-market-research/steps/step-06-research-completion.md +27 -0
  6. package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +1 -1
  7. package/src/bmm-skills/4-implementation/wizz-code-review/SKILL.md +1 -1
  8. package/src/bmm-skills/4-implementation/wizz-create-story/SKILL.md +1 -1
  9. package/src/bmm-skills/4-implementation/wizz-dev-story/SKILL.md +8 -1
  10. package/src/bmm-skills/4-implementation/wizz-quick-dev/SKILL.md +2 -2
  11. package/src/bmm-skills/4-implementation/wizz-quick-dev/step-03-implement.md +2 -0
  12. package/src/bmm-skills/4-implementation/wizz-sprint-status/SKILL.md +1 -1
  13. package/src/core-skills/wizz-party-mode/customize.toml +3 -3
  14. package/src/modules/lowticket/README.md +2 -1
  15. package/src/modules/lowticket/module.yaml +1 -1
  16. package/src/modules/lowticket/skills/wizz-clone-do-expert/SKILL.md +86 -0
  17. package/src/modules/lowticket/skills/wizz-clone-do-expert/references/clone-prompt-template.md +21 -0
  18. package/src/modules/lowticket/skills/wizz-lowticket-trafego/references/reguas.md +2 -0
  19. package/src/modules/wizz/_shared/cerebro.md +1 -1
  20. package/src/modules/wizz/_shared/organizacao.md +2 -2
  21. package/src/modules/wizz/_shared/video-pipeline.md +2 -0
  22. package/src/modules/wizz/agents/wizz-copy/SKILL.md +1 -0
  23. package/src/modules/wizz/agents/wizz-copy/customize.toml +5 -0
  24. package/src/modules/wizz/agents/wizz-copy/knowledge/playbook.md +3 -2
  25. package/src/modules/wizz/agents/wizz-growth/customize.toml +7 -2
  26. package/src/modules/wizz/agents/wizz-growth/knowledge/playbook.md +3 -2
  27. package/src/modules/wizz/agents/wizz-social/knowledge/blueprint-roteiro-viral.md +14 -0
  28. package/src/modules/wizz/agents/wizz-social/knowledge/legenda-instagram.md +12 -0
  29. package/src/skills-lib/wizz-cerebro/SKILL.md +18 -5
  30. package/src/skills-lib/wizz-design-needs/SKILL.md +7 -0
  31. package/src/skills-lib/wizz-design-needs/references/design-state.md +34 -2
  32. package/src/skills-lib/wizz-design-needs/references/gap-analysis.md +1 -1
  33. package/src/skills-lib/wizz-design-needs/scripts/design_state.py +305 -0
  34. package/src/skills-lib/wizz-design-needs/scripts/tests/test_design_state.py +143 -0
  35. package/src/skills-lib/wizz-impeccable/references/design-rules.md +18 -0
  36. package/src/skills-lib/wizz-ios-build/SKILL.md +61 -0
  37. package/src/skills-lib/wizz-motion-3d-director/references/motion-paths.md +2 -0
  38. package/src/skills-lib/wizz-offer-forge/SKILL.md +2 -2
  39. package/src/skills-lib/wizz-paid-ads/SKILL.md +2 -1
  40. package/src/skills-lib/wizz-paywall-upgrade-cro/SKILL.md +1 -0
  41. package/src/skills-lib/{wizz-pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico}/README.md +13 -13
  42. package/src/skills-lib/{wizz-pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico}/SKILL.md +11 -7
  43. package/src/skills-lib/{wizz-pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico}/assets/template.html +8 -21
  44. package/src/skills-lib/wizz-pesquisa-de-publico/references/interview-mode.md +69 -0
  45. package/src/skills-lib/wizz-premium-landing-ui-researcher/SKILL.md +6 -2
  46. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/audit-protocol.md +7 -34
  47. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/component-sources.md +25 -0
  48. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/design-sources.yaml +325 -0
  49. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/mandatory-process.md +1 -1
  50. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/mining-rubric.md +123 -0
  51. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-first-protocol.md +10 -4
  52. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/web-access-and-evidence.md +1 -1
  53. package/src/skills-lib/wizz-premium-landing-ui-researcher/scripts/design_sources.py +221 -0
  54. package/src/skills-lib/wizz-premium-landing-ui-researcher/scripts/tests/test_design_sources.py +130 -0
  55. package/src/skills-lib/wizz-router/references/routing-table-flat.md +7 -1
  56. package/src/skills-lib/wizz-seo-audit/SKILL.md +4 -0
  57. package/src/skills-lib/wizz-seo-audit/references/gsc-regex-perguntas.md +64 -0
  58. package/src/skills-lib/wizz-signup-flow-cro/SKILL.md +1 -0
  59. package/src/skills-lib/wizz-signup-flow-cro/references/trial-abuse.md +40 -0
  60. package/src/skills-lib/wizz-taste-skill/references/anti-slop-tells.md +8 -0
  61. package/src/skills-lib/wizz-ui-component-curator/SKILL.md +8 -7
  62. package/src/skills-lib/wizz-ui-ux-pro-max/SKILL.md +1 -1
  63. package/src/skills-lib/wizz-ui-ux-pro-max/references/common-rules-checklist.md +11 -0
  64. package/src/skills-lib/wizz-video-effects/LICENSE-motionmaxxing.txt +202 -0
  65. package/src/skills-lib/wizz-video-effects/SKILL.md +7 -2
  66. package/src/skills-lib/wizz-video-effects/assets/chrome/collect_layout.js +113 -0
  67. package/src/skills-lib/wizz-video-effects/assets/effects.config.template.json +45 -0
  68. package/src/skills-lib/wizz-video-effects/references/data-contract.md +32 -1
  69. package/src/skills-lib/wizz-video-effects/references/gates.md +4 -1
  70. package/src/skills-lib/wizz-video-effects/references/one-take.md +41 -0
  71. package/src/skills-lib/wizz-video-effects/scripts/check_chrome.py +376 -0
  72. package/src/skills-lib/wizz-video-effects/scripts/check_dead_frames.py +193 -0
  73. package/src/skills-lib/wizz-video-effects/scripts/check_one_take.py +159 -0
  74. package/src/skills-lib/wizz-video-effects/scripts/config.py +13 -0
  75. package/src/skills-lib/wizz-video-effects/scripts/test_chrome.py +181 -0
  76. package/src/skills-lib/wizz-video-effects/scripts/test_dead_frames.py +171 -0
  77. package/src/skills-lib/wizz-video-effects/scripts/test_one_take.py +126 -0
  78. package/src/skills-lib/wizz-video-styles/LICENSE-lemo-opuscar.txt +21 -0
  79. package/src/skills-lib/wizz-video-styles/SKILL.md +16 -7
  80. package/src/skills-lib/wizz-video-styles/assets/film-styles/dark-keynote.html +78 -0
  81. package/src/skills-lib/wizz-video-styles/assets/film-styles/remotion/Riso.tsx +63 -0
  82. package/src/skills-lib/wizz-video-styles/assets/film-styles/risograph.html +69 -0
  83. package/src/skills-lib/wizz-video-styles/assets/film-styles/swiss-motion.html +84 -0
  84. package/src/skills-lib/wizz-video-styles/references/check-ins.md +25 -0
  85. package/src/skills-lib/wizz-video-styles/references/film-styles/DIRECTOR.md +139 -0
  86. package/src/skills-lib/wizz-video-styles/references/film-styles/archive-lighttable.md +109 -0
  87. package/src/skills-lib/wizz-video-styles/references/film-styles/art-deco.md +112 -0
  88. package/src/skills-lib/wizz-video-styles/references/film-styles/ascii-crt.md +115 -0
  89. package/src/skills-lib/wizz-video-styles/references/film-styles/backrooms.md +125 -0
  90. package/src/skills-lib/wizz-video-styles/references/film-styles/blueprint.md +120 -0
  91. package/src/skills-lib/wizz-video-styles/references/film-styles/brick-toy.md +114 -0
  92. package/src/skills-lib/wizz-video-styles/references/film-styles/briefing-board.md +122 -0
  93. package/src/skills-lib/wizz-video-styles/references/film-styles/caption-news.md +137 -0
  94. package/src/skills-lib/wizz-video-styles/references/film-styles/cel-anime-80s.md +119 -0
  95. package/src/skills-lib/wizz-video-styles/references/film-styles/city-symphony.md +142 -0
  96. package/src/skills-lib/wizz-video-styles/references/film-styles/countdown.md +132 -0
  97. package/src/skills-lib/wizz-video-styles/references/film-styles/crayon-book.md +109 -0
  98. package/src/skills-lib/wizz-video-styles/references/film-styles/dark-keynote.md +117 -0
  99. package/src/skills-lib/wizz-video-styles/references/film-styles/data-physical.md +119 -0
  100. package/src/skills-lib/wizz-video-styles/references/film-styles/dataviz.md +123 -0
  101. package/src/skills-lib/wizz-video-styles/references/film-styles/engraving.md +123 -0
  102. package/src/skills-lib/wizz-video-styles/references/film-styles/game-show.md +118 -0
  103. package/src/skills-lib/wizz-video-styles/references/film-styles/glass-product.md +110 -0
  104. package/src/skills-lib/wizz-video-styles/references/film-styles/halftone-dossier.md +122 -0
  105. package/src/skills-lib/wizz-video-styles/references/film-styles/hd-2d.md +111 -0
  106. package/src/skills-lib/wizz-video-styles/references/film-styles/hologram-hud.md +129 -0
  107. package/src/skills-lib/wizz-video-styles/references/film-styles/impasto.md +124 -0
  108. package/src/skills-lib/wizz-video-styles/references/film-styles/ink-wash.md +120 -0
  109. package/src/skills-lib/wizz-video-styles/references/film-styles/iso-infographic.md +117 -0
  110. package/src/skills-lib/wizz-video-styles/references/film-styles/kino-montage.md +151 -0
  111. package/src/skills-lib/wizz-video-styles/references/film-styles/lidar-survey.md +118 -0
  112. package/src/skills-lib/wizz-video-styles/references/film-styles/living-screencast.md +124 -0
  113. package/src/skills-lib/wizz-video-styles/references/film-styles/lowpoly-island.md +112 -0
  114. package/src/skills-lib/wizz-video-styles/references/film-styles/microgame.md +122 -0
  115. package/src/skills-lib/wizz-video-styles/references/film-styles/microscope.md +108 -0
  116. package/src/skills-lib/wizz-video-styles/references/film-styles/midcentury-toon.md +124 -0
  117. package/src/skills-lib/wizz-video-styles/references/film-styles/news-package.md +158 -0
  118. package/src/skills-lib/wizz-video-styles/references/film-styles/newsreel.md +136 -0
  119. package/src/skills-lib/wizz-video-styles/references/film-styles/one-line.md +114 -0
  120. package/src/skills-lib/wizz-video-styles/references/film-styles/paper-lantern.md +116 -0
  121. package/src/skills-lib/wizz-video-styles/references/film-styles/paper-popup.md +119 -0
  122. package/src/skills-lib/wizz-video-styles/references/film-styles/papercut-red.md +110 -0
  123. package/src/skills-lib/wizz-video-styles/references/film-styles/pictogram-motion.md +119 -0
  124. package/src/skills-lib/wizz-video-styles/references/film-styles/pixel-rpg.md +117 -0
  125. package/src/skills-lib/wizz-video-styles/references/film-styles/risograph.md +125 -0
  126. package/src/skills-lib/wizz-video-styles/references/film-styles/rubber-hose.md +112 -0
  127. package/src/skills-lib/wizz-video-styles/references/film-styles/satellite-briefing.md +116 -0
  128. package/src/skills-lib/wizz-video-styles/references/film-styles/scifi-toon.md +121 -0
  129. package/src/skills-lib/wizz-video-styles/references/film-styles/shadow-puppet.md +124 -0
  130. package/src/skills-lib/wizz-video-styles/references/film-styles/silent-film.md +109 -0
  131. package/src/skills-lib/wizz-video-styles/references/film-styles/silkscreen-poster.md +140 -0
  132. package/src/skills-lib/wizz-video-styles/references/film-styles/spy-titles.md +116 -0
  133. package/src/skills-lib/wizz-video-styles/references/film-styles/stained-glass.md +123 -0
  134. package/src/skills-lib/wizz-video-styles/references/film-styles/swiss-motion.md +115 -0
  135. package/src/skills-lib/wizz-video-styles/references/film-styles/teardown.md +127 -0
  136. package/src/skills-lib/wizz-video-styles/references/film-styles/telestrator.md +137 -0
  137. package/src/skills-lib/wizz-video-styles/references/film-styles/tilt-shift.md +107 -0
  138. package/src/skills-lib/wizz-video-styles/references/film-styles/ukiyoe.md +112 -0
  139. package/src/skills-lib/wizz-video-styles/references/film-styles/urban-sketch.md +126 -0
  140. package/src/skills-lib/wizz-video-styles/references/film-styles/visual-investigation.md +140 -0
  141. package/src/skills-lib/wizz-video-styles/references/film-styles/watercolor.md +113 -0
  142. package/src/skills-lib/wizz-video-styles/references/film-styles/white-model.md +115 -0
  143. package/src/skills-lib/wizz-video-styles/references/film-styles/whiteboard.md +118 -0
  144. package/src/skills-lib/wizz-video-styles/references/film-styles/woodcut.md +122 -0
  145. package/src/skills-lib/wizz-video-styles/references/film-styles.md +140 -0
  146. package/src/skills-lib/wizz-video-styles/references/insert-recipes.md +33 -0
  147. package/src/skills-lib/wizz-video-styles/references/motion-laws.md +62 -0
  148. package/src/skills-lib/wizz-video-styles/references/styles/desafio-numero.md +1 -0
  149. package/src/skills-lib/wizz-video-styles/scripts/beat_grid.py +38 -6
  150. package/src/skills-lib/wizz-video-styles/scripts/blind_review.py +159 -0
  151. package/src/skills-lib/wizz-video-styles/scripts/taste.mjs +110 -0
  152. package/src/skills-lib/wizz-video-styles/scripts/test_blind_review.py +82 -0
  153. package/src/skills-lib/wizz-vsl/LICENSE +49 -0
  154. package/src/skills-lib/wizz-vsl/SKILL.md +122 -0
  155. package/src/skills-lib/wizz-vsl/references/assembly-qa.md +123 -0
  156. package/src/skills-lib/wizz-vsl/references/blocks-and-leads.md +149 -0
  157. package/src/skills-lib/wizz-vsl/references/diagnosis.md +95 -0
  158. package/src/skills-lib/wizz-vsl/references/intake.md +200 -0
  159. package/src/skills-lib/wizz-vsl/references/mechanisms.md +138 -0
  160. package/src/skills-lib/wizz-vsl/references/niches.md +40 -0
  161. package/src/skills-lib/wizz-vsl/references/offer.md +91 -0
  162. package/src/skills-lib/wizz-vsl/references/production.md +75 -0
  163. package/src/skills-lib/wizz-web-security/SKILL.md +2 -0
  164. package/tools/hooks/README.md +10 -9
  165. package/tools/hooks/wizz-arrumar.js +1 -0
  166. package/tools/hooks/wizz-artifact-guard.js +31 -5
  167. package/tools/hooks/wizz-cerebro-lib.js +36 -13
  168. package/tools/hooks/wizz-feature-lib.js +4 -1
  169. package/tools/hooks/wizz-feature.js +66 -76
  170. package/tools/hooks/wizz-sprint-archive-lib.js +311 -0
  171. package/tools/installer/core/hooks-settings.js +1 -0
  172. package/tools/installer/core/installer.js +6 -0
  173. package/tools/installer/guided/labels.js +16 -0
  174. package/tools/installer/install-text.js +2 -0
  175. package/tools/installer/modules/cli-config.js +3 -0
  176. package/tools/installer/modules/core-seed.js +72 -0
  177. package/tools/installer/modules/mcp-config.js +3 -0
  178. package/tools/installer/ui.js +22 -26
  179. /package/src/skills-lib/{wizz-pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico}/prompts.md +0 -0
@@ -26,7 +26,7 @@ The SessionStart and UserPromptSubmit hooks already tell you the state (`FEATURE
26
26
  6. **Feature branch** (`feat/<slug>`) → the branch already is the sprint; when the config disagrees, `F definir <branch-slug> --novo`.
27
27
  7. **`feature_branches: auto`** in the config and you are on a trunk branch → propose `git switch -c feat/<slug>` in one line. With `off` (the default, for whoever works on `main`) say nothing about branches.
28
28
 
29
- **Closing archives.** `F encerrar` moves the sprint's two folders to `_arquivo/<slug>/` inside each artifact folder, together with the root `{implementation_artifacts}/sprint-status.yaml`, so the next sprint starts clean. `F listar` shows the open sprints and one line with the number of archived ones. The archive is history, not trash: when a request needs a past decision, spec or retro ("how did we solve the Meta sync?"), search `_arquivo/` of both artifact folders (and the Cérebro, which points to it) before saying it does not exist.
29
+ **Closing archives.** `F encerrar` moves the sprint's two folders to `_arquivo/<slug>/` inside each artifact folder, together with the root `{implementation_artifacts}/sprint-status.yaml` when it belongs to that sprint (otherwise it stays and the output says why), so the next sprint starts clean. It plans before touching the disk and undoes everything on a failure; closing a reopened sprint keeps the previous close's same-named files beside the new ones as `<name>.anterior-<date>`. References from outside the sprint (CLAUDE.md, roteiros, design html) are rewritten; code and config that still cite the old folder are listed for you to fix. `F listar` shows the open sprints and one line with the number of archived ones. The archive is history, not trash: when a request needs a past decision, spec or retro ("how did we solve the Meta sync?"), search `_arquivo/` of both artifact folders (and the Cérebro, which points to it) before saying it does not exist.
30
30
 
31
31
  **Handoff:** every brief carries `feature: <slug>` (or `feature: nenhuma` for work that is not named). A receiver without that field resolves it itself with the steps above before writing anything; it never writes an artifact without a resolved feature.
32
32
 
@@ -61,7 +61,7 @@ Run `F verificar`. It checks the mechanical items and returns the judgment items
61
61
  - [ ] Spec exists, valid frontmatter, acceptance criteria filled.
62
62
  - [ ] `## Implementação` filled when the status is `implementado` or "no-ar".
63
63
  - [ ] Roteiro with Rollback in `{project_knowledge}/` when the status is "no-ar".
64
- - [ ] Cérebro: project state + one line in the Sessions table (`F encerrar --cerebro` writes both; vault from `WIZZ_CEREBRO_VAULT`, `cerebro_vault:` in the config or `~/Documents/projects/Obsidian Vault`; project file from `--projeto`, `project_name` or the folder name).
64
+ - [ ] Cérebro: project state + one line in the Sessions table (`F encerrar --cerebro` writes both; vault found as in the `wizz-cerebro` skill, section `## Vault`; project file from `--projeto`, `project_name` or the folder name).
65
65
  - [ ] Auto-memory: only a reproducible trap or an owner preference (state and decisions belong to the Cérebro).
66
66
 
67
67
  A delivered feature (status `implementado` or "no-ar") with a pending item also triggers a Stop-hook warning (`wizz-close-reminder.js`) at the end of the turn.
@@ -29,6 +29,8 @@
29
29
  | **Source B-roll / inserts: find real footage of what the script names** | `get-brolls` (darwin) |
30
30
  | Edit/cut recorded footage (cuts, selects, roughcut) | `buttercut` (darwin-arm64 only) |
31
31
  | Analyze an existing reference video (frames + transcription) | `claude-video` |
32
+ | One-take product film with the onetake skill itself, personal or noncommercial work only (client work needs onetake Pro) | `onetake` (opt-in; otherwise the one-take technique in `wizz-video-effects` runs in this pipeline) |
33
+ | Hand-painted cartoon where Claude itself appears (Clawd, p5.brush), for content about Claude/Claude Code/Wizz | `claude-animation-base` (clone-and-run; renders an MP4 that enters Remotion as a clip) |
32
34
  | AI-generated video/image (Veo/Kling/Wan), UGC / ad-ready style | `wizz-ad-creative` (ads area) writes the prompt; the user generates it in Flow or another AI tool, outside the method |
33
35
 
34
36
  ## Full chain (script → publishing)
@@ -25,6 +25,7 @@ You are Wizz's Copy. It writes texts that sell, humanizes what sounds robotic, a
25
25
  - Email, sequence, nurture → `wizz-email-sequence`
26
26
  - Make text natural, remove the AI feel → `wizz-humanizer`
27
27
  - Review/edit existing text → `wizz-copy-editing`
28
+ - VSL (video sales letter) script, from the expert interview to a script ready to record plus the production beat sheet → `wizz-vsl`; under low ticket it reads the methodology's VSL ruler first
28
29
 
29
30
  Always deliver 2-3 variations and say which one you recommend.
30
31
 
@@ -40,6 +40,11 @@ code = "PG"
40
40
  description = "Page copy, headline, value proposition, CTA"
41
41
  prompt = "Invoke the GLOBAL skill 'wizz-copywriting' via the Skill tool. Then run the 'wizz-humanizer' logic on the final text. Deliver 2-3 variations in easy {communication_language}."
42
42
 
43
+ [[agent.menu]]
44
+ code = "VSL"
45
+ description = "VSL script: interview, mechanisms, offer, leads, beat sheet"
46
+ prompt = "Invoke the GLOBAL skill 'wizz-vsl' via the Skill tool. It resumes from its manifest when one exists, reuses an approved wizz-offer-forge offer and the audience research, and hands production (narration, wizz-ctc-align, hyperframes, Remotion) to the designer area: offer wizz-maestro when the user wants the video in the same delivery."
47
+
43
48
  [[agent.menu]]
44
49
  code = "EM"
45
50
  description = "Email, nurture sequence, follow-up"
@@ -4,19 +4,20 @@
4
4
 
5
5
  ## Ordem base (declarada no registry)
6
6
 
7
- `wizz-pesquisa-de-publico-do-piva` e `wizz-offer-forge` vêm antes de `wizz-copywriting`; `wizz-copy-editing` vem depois. Na página de vendas completa, se falta oferta aprovada, use `wizz-offer-forge`; se falta estrutura de conversão aprovada, `wizz-page-cro`. Headline ou CTA isolado, ou texto sobre oferta e estrutura já aprovadas, não exige refazer nada disso.
7
+ `wizz-pesquisa-de-publico` e `wizz-offer-forge` vêm antes de `wizz-copywriting`; `wizz-copy-editing` vem depois. Na página de vendas completa, se falta oferta aprovada, use `wizz-offer-forge`; se falta estrutura de conversão aprovada, `wizz-page-cro`. Headline ou CTA isolado, ou texto sobre oferta e estrutura já aprovadas, não exige refazer nada disso.
8
8
 
9
9
  ## Situações
10
10
 
11
11
  | Situação | Skill (nesta ordem) | Onde olhar |
12
12
  | ----------------------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
13
- | Não conheço o público, as dores, a linguagem | `wizz-pesquisa-de-publico-do-piva` | `wizz-pesquisa-de-publico-do-piva/SKILL.md` |
13
+ | Não conheço o público, as dores, a linguagem | `wizz-pesquisa-de-publico` | `wizz-pesquisa-de-publico/SKILL.md` |
14
14
  | Não há oferta definida | `wizz-offer-forge` | `wizz-offer-forge/references/mecanismo-unico.md`, `empilhamento-valor.md`, `garantia-reversa.md` |
15
15
  | Copy de venda, página ou headline do zero | `wizz-copywriting` | `wizz-copywriting/references/copy-frameworks.md`, `natural-transitions.md` |
16
16
  | Texto já existe e precisa melhorar | `wizz-copy-editing` | `wizz-copy-editing/references/plain-english-alternatives.md` |
17
17
  | Texto soa feito por IA | `wizz-humanizer` | `wizz-humanizer/references/ai-writing-patterns.md` |
18
18
  | Sequência de e-mail (onboarding, nurture, lançamento) | `wizz-email-sequence` | `references/email-types.md`, `sequence-templates.md`, `copy-guidelines.md` |
19
19
  | Cold email, outbound B2B | `wizz-cold-email` | `references/frameworks.md`, `subject-lines.md`, `personalization.md`, `follow-up-sequences.md` |
20
+ | Roteiro de VSL (vídeo de vendas) | `wizz-vsl` | `wizz-vsl/SKILL.md` (no low ticket lê antes a régua de VSL da `wizz-lowticket-metodologia`) |
20
21
  | Deck, one-pager, objeções, demo | `wizz-sales-enablement` | `references/deck-frameworks.md`, `objection-library.md`, `demo-scripts.md`, `one-pager-templates.md` |
21
22
  | Copy de low ticket BR/PT | `wizz-lowticket-metodologia` (área growth) | `wizz-lowticket-metodologia/knowledge/18-copy-desejo-oferta.md`, `07a-copy-humanizacao.md` |
22
23
  | Texto para anúncio | `wizz-ad-creative` (área ads) | `wizz-paid-ads/references/ad-copy-templates.md` |
@@ -63,8 +63,8 @@ prompt = "Invoke the GLOBAL skill 'wizz-churn-prevention' via the Skill tool."
63
63
 
64
64
  [[agent.menu]]
65
65
  code = "PV"
66
- description = "Audience research, VoC, and angle matrix (Piva)"
67
- prompt = "Invoke the GLOBAL skill 'wizz-pesquisa-de-publico-do-piva' via the Skill tool. Ask for the offer's document/description and generate the arsenal with the final HTML dashboard."
66
+ description = "Audience research, VoC, and angle matrix"
67
+ prompt = "Invoke the GLOBAL skill 'wizz-pesquisa-de-publico' via the Skill tool. Ask for the offer's document/description and generate the arsenal with the final HTML dashboard."
68
68
 
69
69
  [[agent.menu]]
70
70
  code = "OF"
@@ -101,6 +101,11 @@ code = "LP"
101
101
  description = "Low ticket: sales page performance (connect rate, pixel order, LCP)"
102
102
  prompt = "Invoke the lowticket module skill 'wizz-lowticket-pagina' via the Skill tool. It engineers performance; it doesn't rewrite copy, price or offer."
103
103
 
104
+ [[agent.menu]]
105
+ code = "LC"
106
+ description = "Low ticket: clone the expert's voice into a GPT/Project for students"
107
+ prompt = "Invoke the lowticket module skill 'wizz-clone-do-expert' via the Skill tool. It needs the expert's real material (lives, audios, posts) or runs a short interview. Persona research is 'wizz-pesquisa-de-publico', not this."
108
+
104
109
  [[agent.menu]]
105
110
  code = "AB"
106
111
  description = "Set up an A/B test or conversion experiment"
@@ -8,8 +8,8 @@
8
8
  | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
9
9
  | Travou, não sabe o canal | `wizz-marketing-ideas` | `wizz-marketing-ideas/references/ideas-by-category.md` |
10
10
  | Posicionamento e contexto do produto | `wizz-product-marketing-context`, `wizz-marketing-psychology` | `SKILL.md` de cada uma |
11
- | Entender o público | `wizz-pesquisa-de-publico-do-piva` | `SKILL.md` |
12
- | Criar ou refazer oferta | `wizz-pesquisa-de-publico-do-piva`, depois `wizz-offer-forge` | `wizz-offer-forge/references/` (mecanismo, empilhamento, garantia, ponte de tráfego, plano de medição) |
11
+ | Entender o público | `wizz-pesquisa-de-publico` | `SKILL.md` |
12
+ | Criar ou refazer oferta | `wizz-pesquisa-de-publico`, depois `wizz-offer-forge` | `wizz-offer-forge/references/` (mecanismo, empilhamento, garantia, ponte de tráfego, plano de medição) |
13
13
  | Lançamento de produto ou feature | `wizz-launch-strategy`, com `wizz-email-sequence` | `references/five-phase-approach.md`, `launch-checklist.md`, `orb-framework.md`, `product-hunt-strategy.md`, `post-launch-and-ongoing.md` |
14
14
  | Preço, planos, packaging | `wizz-pricing-strategy` | `references/tier-structure.md`, `research-methods.md` |
15
15
  | Churn, cancelamento, cobrança que falha | `wizz-churn-prevention` | `references/cancel-flow-patterns.md`, `dunning-playbook.md` |
@@ -32,6 +32,7 @@ Quem não sabe por onde começar entra por `wizz-lowticket-guia`, que vem antes
32
32
  | Performance da página de venda | `wizz-lowticket-pagina` | vem antes de `wizz-lowticket-funil` |
33
33
  | Escada pós-checkout | `wizz-lowticket-funil` | só depois de existir front-end que converte |
34
34
  | Campanha Meta | `wizz-lowticket-trafego` (área ads) | via `wizz-ads` |
35
+ | Clone da voz do expert (GPT/Project pros alunos) | `wizz-clone-do-expert` | persona de quem compra é `wizz-pesquisa-de-publico` |
35
36
 
36
37
  ## Limites
37
38
 
@@ -50,6 +50,20 @@ Requires speed: must go out the same day or within 24h. Once the window passes,
50
50
 
51
51
  **Fundamental pattern:** every hook exists between two poles, **provocation** (you're wrong / your profession is ending) and **promise** (I discovered something you need to know). The strongest ones trigger both at once.
52
52
 
53
+ ### Hook pre-check (before shooting)
54
+
55
+ Write **three** hooks of different types, not three rewordings of one, and for each write the spoken line and the on-screen line separately (on screen is the shorter, blunter one, six words or fewer). Then score each spoken line from 0 to 100 on five checks and keep the best:
56
+
57
+ | Check | Strong when | Weak when |
58
+ |---|---|---|
59
+ | Length | 5 to 12 words, sayable in under 3 seconds | a preamble, or a sentence that needs a breath |
60
+ | Specificity | a concrete marker: a number, a name, a tool, a place | "muita coisa", "um monte de tempo" |
61
+ | Stakes | something is won, lost or at risk in the line | nothing is at stake |
62
+ | Front-loading | the interesting word in the first two or three | "oi gente", "hoje eu vou", "então" opening |
63
+ | Address | aimed at a viewer ("você", a role, a situation) | aimed at nobody |
64
+
65
+ A hook whose weakest check is low is rewritten on that check before anything else. The score catches greetings, preambles and empty hooks; it does not predict which of two decent hooks will travel (the face, the edit, the audio and the distribution decide that): trust the retention graph over the score. Never invent the number a hook needs: it comes from the user or stays a slot. *(Adapted from the hook checks of Jakeschincariol/instagram-agent-skill, MIT, credited in `THIRD-PARTY-NOTICES.md`.)*
66
+
53
67
  ---
54
68
 
55
69
  ## 2. Narrative Structure
@@ -39,6 +39,18 @@ Supporting knowledge for **wizz-social** (Rafa). Creates the optimized descripti
39
39
  - **Fast Reels / simple feed post:** 80 to 150 characters (short, direct, impactful).
40
40
  - **Educational carousel / storytelling:** 300 to 800 characters (delivers value, increases reading time).
41
41
 
42
+ ## The fold check (before delivering)
43
+
44
+ Instagram shows about **125 characters** before "mais". Check the caption the way the feed prints it:
45
+
46
+ - **Decide the job first.** Reel: the video already hooked the viewer, so line one carries the ask and the context, not a second hook competing with the video. Photo, single image or carousel cover: the caption is the content, so line one is the hook and it ends at a cliff, not mid-clause.
47
+ - Line one fits inside the 125 characters: never a greeting, a hashtag or an emoji as the first character.
48
+ - **One ask** only (comment a keyword, save, or DM). A keyword ask uses one typable word with no spaces or emoji, also said in the video.
49
+ - No link in the caption: captions are not clickable; the link goes in the bio, a DM, or the first comment.
50
+ - The phrases the user wants to be found for go in the body as a human would type them; hashtags (up to 5) are labels, not reach.
51
+
52
+ Add one line under the caption in the delivery: `👁️ Antes do "mais": N/125 caracteres`. *(Adapted from the caption linter of Jakeschincariol/instagram-agent-skill, MIT, credited in `THIRD-PARTY-NOTICES.md`.)*
53
+
42
54
  ## General rules
43
55
  - Never robotic or generic. Write like a human.
44
56
  - Emojis with moderation and purpose: they guide the eye, they don't clutter.
@@ -53,12 +53,25 @@ Read offset=(line-5) limit=15 # → reads only the needed excerpt
53
53
 
54
54
  ## Vault
55
55
 
56
- Known default vault: `~/Documents/projects/Obsidian Vault/`
56
+ This section is the **single source** of vault discovery. Every other file (command references, `_shared/cerebro.md`) points here instead of repeating it. The vault is plain markdown: it works with or without the Obsidian app.
57
57
 
58
- **How to discover it:**
59
- 1. Try `~/Documents/projects/Obsidian Vault/CEREBRO.md` — if it exists, use it.
60
- 2. If not: `find ~ -name "CEREBRO.md" -maxdepth 6 2>/dev/null | head -1`
61
- 3. If still nothing: ask the user.
58
+ **How to discover it** (first folder that holds a `CEREBRO.md` wins; `wizz-feature.js encerrar --cerebro` reads steps 1 to 3 in the same order):
59
+ 1. `$WIZZ_CEREBRO_VAULT`, then `cerebro_vault:` in `_wizz/bmm/config.yaml`.
60
+ 2. Per-project Cérebro of the current repo: `_wizz-output/cerebro/` (or `{output_folder}/cerebro/` when the config sets another output folder).
61
+ 3. Global default: `~/Documents/projects/Obsidian Vault/`.
62
+ 4. `find ~ -maxdepth 6 -name "CEREBRO.md" -not -path "*/_wizz-output/*" 2>/dev/null | head -1` (skips other repos' per-project Cérebros).
63
+
64
+ **Nothing found.** A read-only command (`/ver`, `/dia`, `/memoria`) says there is no Cérebro yet and stops. A command that saves (`/iniciar`, `/salvar`, `/dump`, `/decisao`, `/conteudo`, `/prospect`) never writes to an invented place in silence: ask once (AskUserQuestion where available, a numbered question otherwise):
65
+ - **Global Cérebro** (one for every project): then ask which folder, suggesting `~/Documents/projects/Obsidian Vault/`. Outside the default path, tell the user that `export WIZZ_CEREBRO_VAULT="<folder>"` in the shell profile lets the CLI find it too (do not edit the profile yourself).
66
+ - **Per-project Cérebro** (inside this repo, at `_wizz-output/cerebro/`): then ask once whether it stays out of git (private: append `_wizz-output/cerebro/` to `.gitignore`) or is committed (shared with the team).
67
+
68
+ **After the choice**, create the minimal structure and never ask again (the next discovery finds it):
69
+ ```
70
+ <vault>/CEREBRO.md first line: vault: <absolute path> (per-project adds a second line: scope: project)
71
+ <vault>/projetos/
72
+ <vault>/_decisions/
73
+ ```
74
+ A per-project Cérebro needs no config entry: the feature-closing CLI finds `_wizz-output/cerebro/` by itself, so no machine path lands in a committed config. Then continue the command that triggered the question.
62
75
 
63
76
  **Never Read the entire CEREBRO.md just to find the vault path** — use grep:
64
77
  ```bash
@@ -49,6 +49,13 @@ Both read `design-state.yaml`, take the need's `budget` and `floor` as given, an
49
49
 
50
50
  Level 4 or 5 also hands off to `wizz-motion-3d-director` before any behaviour need is mined.
51
51
 
52
+ ## Executable checks on the map
53
+
54
+ `scripts/design_state.py` (run with `uv run --no-cache`) reads the rubric and the level ceiling from the skill files and checks the map itself, so the gates are not only prose:
55
+
56
+ - `validate {output_folder}/design-state.yaml`: statuses and required reasons, verdict against the score band, entry conditions, the cut and each need's floor, one signature per surface, dedup links, the adaptation on every selected discovery, and the behaviour ceiling of the level. The miners run it after writing discoveries; findings are fixed in the map before the handoff.
57
+ - `gate {output_folder}/design-state.yaml --section <surface>/<section>` (or `--need <id>`): the implementer runs it before building a non-trivial section, component or behaviour. Exit 0 passes (selected with a discovery, `unmet` with the research as the justification, `deferred` meaning "not now", `dropped`); exit 1 blocks an open need (mine it first) or a section the map does not know (`unplanned`, back to this skill). No map, no gate: the fail-open contract of the state file holds.
58
+
52
59
  ## References map (read on demand)
53
60
 
54
61
  | For... | Read |
@@ -23,6 +23,8 @@ Read contract, deliberately identical to the one the router and the maestro alre
23
23
  | `implementacao` | `wizz-agent-dev` / `wizz-quick-dev` | QA |
24
24
  | `problemas` | QA, the audit protocol, the user | `wizz-design-needs` when reopening a need |
25
25
 
26
+ `scripts/design_state.py validate` checks this contract and the rubric on a real file; `gate` is the implementer's check before building (both in the skill's SKILL.md).
27
+
26
28
  No block has two owners. A skill that needs a field it does not own asks the owner or records a `problema`; it never overwrites.
27
29
 
28
30
  ## Schema
@@ -70,13 +72,41 @@ needs:
70
72
  adaptive: { mobile: null, reduced_motion: null } # obrigatório antes de fechar
71
73
  status: open # open | mining | evaluated | selected | unmet | dropped | deferred
72
74
  reason: null # obrigatório em dropped, deferred, unmet
73
- satisfied_by: []
75
+ spent: { fontes: 0, secas: 0 } # escrito pelo minerador: fontes abertas, fontes seguidas sem nada elegível
76
+ satisfied_by: [] # ids das discoveries selecionadas
74
77
 
75
78
  proibidos:
76
79
  - { o_que: "partícula, glow, gradiente saturado, 3D", por: sinal:minimalista }
77
80
  - { o_que: "carrossel de logos, card de depoimento genérico", por: tipo:autoridade }
78
81
 
79
- discoveries: [] # escrito pelo minerador
82
+ discoveries: # escrito pelo minerador; regras em mining-rubric.md
83
+ - id: disc-001
84
+ need: b1-hero-entrada # toda descoberta responde a uma necessidade
85
+ surface: home
86
+ layer: comportamento # copiado da need: o teto de comportamento é contado por superfície
87
+ source:
88
+ url: "https://..."
89
+ kind: code # code | registry | visual
90
+ revision: "a3f1c9d" # SHA, tag ou data de leitura
91
+ license: MIT # null quando não há
92
+ access: publico # publico | cache | fornecido_pelo_usuario | pago_autorizado
93
+ evidence: verified # verified | unverified (gate de web-access-and-evidence.md)
94
+ pattern: "revelação por máscara vertical sincronizada com o scroll"
95
+ pattern_key: scroll-mask-reveal # chave de dedup dentro do projeto
96
+ observed: ["máscara 420ms, cubic-bezier(.2,.8,.2,1)", "sem useReducedMotion no código"]
97
+ inferred: ["provavelmente usa GSAP ScrollTrigger"]
98
+ disqualifiers: [] # códigos de condição de entrada não cumprida; qualquer um = DESCARTAR
99
+ scores: { aderencia: 3, execucao: 2, custo_adaptacao: 2, custo_performance: 1, originalidade: 1, total: 9 }
100
+ verdict: ASSINATURA # ASSINATURA | FORTE | UTILITARIO | DESCARTAR
101
+ adaptation: # obrigatório quando selected
102
+ padrao: "o título é revelado por uma máscara que sobe com o scroll"
103
+ por_que: "segura o olho no título antes de liberar o corpo"
104
+ invariante: "a máscara tem que andar mais rápido que o scroll, senão lê como lag"
105
+ variavel: [easing, duração, direção, cor]
106
+ reimplementacao: "reimplementar com os tokens do projeto, sem copiar seletor nem classe"
107
+ status: selected # proposed | selected | rejected | implemented | reverted
108
+ reason: null # obrigatório em rejected e reverted
109
+ dup_of: null # id da discovery selecionada que já resolve este padrão
80
110
  implementacao: {} # escrito pelo dev
81
111
  problemas: [] # escrito pelo QA
82
112
  ```
@@ -97,6 +127,8 @@ open ──▶ mining ──▶ evaluated ──▶ selected ──▶ (implemen
97
127
  └──▶ deferred (perdeu para o teto do nível)
98
128
  ```
99
129
 
130
+ Discoveries have their own lifecycle: `proposed → selected → implemented`, or `rejected` / `reverted` with a `reason`. How a discovery is scored, when it is selected and when the miner stops are in [mining-rubric](../../wizz-premium-landing-ui-researcher/references/mining-rubric.md).
131
+
100
132
  `unmet`, `dropped` and `deferred` require a `reason`. That is what makes reopening cheap: the next round knows what was already tried and why it failed, and does not re-search the same ground.
101
133
 
102
134
  ## Reopening
@@ -24,7 +24,7 @@ When the layers disagree or the coverage cannot be proven by reading (a CSS rule
24
24
  - a site the user named as a reference: at the **system** layer (palette, type scale, spacing, radius), extract it instead of describing it, `npx -y dembrandt@0.39.2 <url> --design-md`, and close the need against the measured tokens
25
25
  - a Figma file from the client, through the Framelink MCP when the user set it up (see [component-sources](../../wizz-premium-landing-ui-researcher/references/component-sources.md))
26
26
 
27
- Cache freshness rule: check the revision and date before treating a cached item as current. A stale cache is still a valid source for a pattern; it is not a valid source for an API. When the cache answers a need, record the revision in the discovery, not just the path. For registry sources, the live index (`https://ui.shadcn.com/r/registries.json`, `health` per registry) is the freshness check: it costs one request and needs no clone.
27
+ Cache freshness rule: check the revision and date before treating a cached item as current (`uv run --no-cache ../wizz-premium-landing-ui-researcher/scripts/design_sources.py freshness` from this skill's folder answers it for every cached clone). A stale cache is still a valid source for a pattern; it is not a valid source for an API. When the cache answers a need, record the revision in the discovery, not just the path. For registry sources, the live index (`https://ui.shadcn.com/r/registries.json`, `health` per registry) is the freshness check: it costs one request and needs no clone.
28
28
 
29
29
  User-supplied material outranks every catalogue. A reference the user chose is worth more than a better reference they did not.
30
30
 
@@ -0,0 +1,305 @@
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = ["ruamel.yaml>=0.18"]
4
+ # ///
5
+ """Check design-state.yaml against the mining rubric, and gate implementation.
6
+
7
+ Prints ONLY JSON to stdout.
8
+
9
+ Subcommands:
10
+ validate <state> Structural and rubric checks on needs and discoveries:
11
+ statuses, required reasons, verdict vs score band, entry
12
+ conditions, the cut and the need's floor, one signature per
13
+ surface, dedup links, adaptation on selected discoveries,
14
+ and the behaviour ceiling of the level. Exit 0 when ok,
15
+ 1 when there are findings, 2 on a file it cannot read.
16
+ gate <state> --section <surface>/<section> [--section ...] | --need <id>
17
+ The implementer's gate. Before building a non-trivial
18
+ section, component or behaviour, every need of that
19
+ section must be in a terminal status, and each `selected`
20
+ need must point at a selected discovery. Open, mining or
21
+ evaluated needs block (mine first); a section the map does
22
+ not know blocks as `unplanned` (back to wizz-design-needs).
23
+ `unmet` passes (build from scratch, the research is the
24
+ justification) and `deferred` passes with "do not build
25
+ this behaviour now". Exit 0 pass, 1 blocked, 2 error.
26
+
27
+ The numbers (cut, bands, entry-condition codes) are read from the
28
+ `# mining-rubric:v1` block of mining-rubric.md and the behaviour ceiling from
29
+ the Rule 3 table of derivacao.md. Nothing is restated here.
30
+
31
+ Run with `uv run --no-cache scripts/design_state.py ...`.
32
+ """
33
+
34
+ import argparse
35
+ import json
36
+ import re
37
+ import sys
38
+ from pathlib import Path
39
+
40
+ HERE = Path(__file__).resolve().parent
41
+ SKILLS = HERE.parents[1]
42
+ DERIVATION = HERE.parent / "references" / "derivacao.md"
43
+ RUBRIC_CANDIDATES = [
44
+ SKILLS / "wizz-premium-landing-ui-researcher" / "references" / "mining-rubric.md",
45
+ ]
46
+
47
+ NEED_STATUSES = {"open", "mining", "evaluated", "selected", "unmet", "dropped", "deferred"}
48
+ NEED_TERMINAL = {"selected", "unmet", "dropped", "deferred"}
49
+ NEED_NEEDS_REASON = {"unmet", "dropped", "deferred"}
50
+ DISC_STATUSES = {"proposed", "selected", "rejected", "implemented", "reverted"}
51
+ DISC_NEEDS_REASON = {"rejected", "reverted"}
52
+ DISC_CHOSEN = {"selected", "implemented"}
53
+ ADAPTATION_FIELDS = ("padrao", "por_que", "invariante", "variavel", "reimplementacao")
54
+
55
+
56
+ def emit(payload, code):
57
+ print(json.dumps(payload, ensure_ascii=False, indent=2, default=str))
58
+ sys.exit(code)
59
+
60
+
61
+ def yaml_loader():
62
+ from ruamel.yaml import YAML
63
+
64
+ return YAML(typ="safe")
65
+
66
+
67
+ def load_rubric(candidates=None):
68
+ for path in candidates or RUBRIC_CANDIDATES:
69
+ if path.exists():
70
+ text = path.read_text(encoding="utf-8")
71
+ match = re.search(r"```yaml\n(# mining-rubric:v1\n.*?)```", text, re.S)
72
+ if not match:
73
+ raise ValueError(f"{path}: no '# mining-rubric:v1' block")
74
+ return yaml_loader().load(match.group(1))
75
+ raise ValueError("mining-rubric.md not found next to this skill")
76
+
77
+
78
+ def load_ceiling(path=DERIVATION):
79
+ text = path.read_text(encoding="utf-8")
80
+ start = text.index("## Rule 3")
81
+ section = text[start : text.find("\n## ", start + 1)]
82
+ ceiling = {}
83
+ for line in section.splitlines():
84
+ cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
85
+ if len(cells) >= 3:
86
+ level, needs = re.match(r"\d+", cells[0]), re.match(r"\d+", cells[2])
87
+ if level and needs:
88
+ ceiling[int(level.group())] = int(needs.group())
89
+ if len(ceiling) < 5:
90
+ raise ValueError(f"{path}: Rule 3 table does not have the 5 levels")
91
+ return ceiling
92
+
93
+
94
+ def load_state(path):
95
+ with open(path, encoding="utf-8") as handle:
96
+ data = yaml_loader().load(handle)
97
+ if not isinstance(data, dict):
98
+ raise ValueError(f"{path}: not a mapping")
99
+ return data
100
+
101
+
102
+ def band(rubric, total):
103
+ for verdict in rubric["verdicts"]:
104
+ if verdict["min"] <= total <= verdict["max"]:
105
+ return verdict["id"]
106
+ return None
107
+
108
+
109
+ class Findings:
110
+ def __init__(self):
111
+ self.items = []
112
+
113
+ def add(self, code, where, message):
114
+ self.items.append({"code": code, "where": where, "message": message})
115
+
116
+
117
+ def check_needs(needs, out):
118
+ seen = set()
119
+ for need in needs:
120
+ nid = need.get("id")
121
+ if not nid or nid in seen:
122
+ out.add("need_id", str(nid), "need without id or with a duplicate id")
123
+ seen.add(nid)
124
+ status = need.get("status")
125
+ if status not in NEED_STATUSES:
126
+ out.add("need_status", str(nid), f"unknown status {status!r}")
127
+ if status in NEED_NEEDS_REASON and not need.get("reason"):
128
+ out.add("need_reason", str(nid), f"status {status} needs a reason")
129
+ if not need.get("why"):
130
+ out.add("need_why", str(nid), "a need without `why` does not trace to the brief")
131
+
132
+
133
+ def check_scores(disc, rubric, out, where):
134
+ scores = disc.get("scores") or {}
135
+ total = 0
136
+ for criterion, (low, high) in rubric["criteria"].items():
137
+ value = scores.get(criterion)
138
+ if not isinstance(value, int) or not low <= value <= high:
139
+ out.add("score_range", where, f"{criterion}={value!r} outside {low}-{high}")
140
+ return None
141
+ total += value
142
+ if scores.get("total") not in (None, total):
143
+ out.add("score_total", where, f"total {scores.get('total')} != sum {total}")
144
+ return total
145
+
146
+
147
+ def check_verdict(disc, rubric, out, where):
148
+ verdict = disc.get("verdict")
149
+ if disc.get("disqualifiers"):
150
+ unknown = set(disc["disqualifiers"]) - set(rubric["disqualifiers"])
151
+ if unknown:
152
+ out.add("disqualifier_code", where, f"unknown entry-condition codes {sorted(unknown)}")
153
+ if verdict != "DESCARTAR":
154
+ out.add("verdict_band", where, "an unmet entry condition means DESCARTAR")
155
+ return None
156
+ if disc.get("evidence") != "verified" or disc.get("dup_of"):
157
+ return None
158
+ total = check_scores(disc, rubric, out, where)
159
+ if total is None:
160
+ return None
161
+ expected = band(rubric, total)
162
+ capped = verdict == "FORTE" and expected == "ASSINATURA" and disc.get("reason") == rubric.get("signature_cap_reason")
163
+ if verdict != expected and not capped:
164
+ out.add("verdict_band", where, f"score {total} is {expected}, recorded {verdict}")
165
+ return total
166
+
167
+
168
+ def check_chosen(disc, need, total, rubric, out, where):
169
+ if disc.get("evidence") != "verified":
170
+ out.add("selected_unverified", where, "an unverified discovery cannot be selected")
171
+ if disc.get("verdict") == "DESCARTAR" or disc.get("disqualifiers"):
172
+ out.add("selected_discarded", where, "a DESCARTAR discovery cannot be selected")
173
+ floor = max(rubric["cut"], int((need or {}).get("floor") or 0))
174
+ if total is not None and total < floor:
175
+ out.add("selected_below_floor", where, f"score {total} below the floor {floor}")
176
+ adaptation = disc.get("adaptation") or {}
177
+ missing = [field for field in ADAPTATION_FIELDS if not adaptation.get(field)]
178
+ if missing:
179
+ out.add("adaptation_missing", where, f"selected without adaptation fields {missing}")
180
+
181
+
182
+ def check_discoveries(state, rubric, ceiling, out):
183
+ needs = {need.get("id"): need for need in state.get("needs") or []}
184
+ discoveries = state.get("discoveries") or []
185
+ by_id = {disc.get("id"): disc for disc in discoveries}
186
+ signatures, behaviours = {}, {}
187
+ for disc in discoveries:
188
+ where = str(disc.get("id"))
189
+ need = needs.get(disc.get("need"))
190
+ if need is None:
191
+ out.add("discovery_need", where, f"need {disc.get('need')!r} is not in the map")
192
+ status = disc.get("status")
193
+ if status not in DISC_STATUSES:
194
+ out.add("discovery_status", where, f"unknown status {status!r}")
195
+ if status in DISC_NEEDS_REASON and not disc.get("reason"):
196
+ out.add("discovery_reason", where, f"status {status} needs a reason")
197
+ if not disc.get("observed"):
198
+ out.add("observed_missing", where, "no `observed` list: a score must rest on what was seen")
199
+ if disc.get("dup_of"):
200
+ original = by_id.get(disc["dup_of"])
201
+ if not original or original.get("status") not in DISC_CHOSEN or original.get("pattern_key") != disc.get("pattern_key"):
202
+ out.add("dup_of", where, "dup_of must point at a selected discovery with the same pattern_key")
203
+ total = check_verdict(disc, rubric, out, where)
204
+ if status in DISC_CHOSEN:
205
+ check_chosen(disc, need, total, rubric, out, where)
206
+ surface = disc.get("surface") or (need or {}).get("surface")
207
+ if disc.get("verdict") == "ASSINATURA":
208
+ signatures[surface] = signatures.get(surface, 0) + 1
209
+ if (disc.get("layer") or (need or {}).get("layer")) == "comportamento":
210
+ behaviours[surface] = behaviours.get(surface, 0) + 1
211
+ limit = rubric.get("signature_per_surface", 1)
212
+ for surface, count in signatures.items():
213
+ if count > limit:
214
+ out.add("signature_cap", str(surface), f"{count} ASSINATURA selected, limit {limit}")
215
+ level = state.get("nivel")
216
+ if isinstance(level, int) and level in ceiling:
217
+ for surface, count in behaviours.items():
218
+ if count > ceiling[level]:
219
+ out.add("behaviour_ceiling", str(surface), f"{count} behaviours selected, level {level} allows {ceiling[level]}")
220
+ for need in needs.values():
221
+ if need.get("status") == "selected":
222
+ linked = [by_id.get(ref) for ref in need.get("satisfied_by") or []]
223
+ if not any(disc and disc.get("status") in DISC_CHOSEN for disc in linked):
224
+ out.add("need_unlinked", str(need.get("id")), "selected need without a selected discovery in satisfied_by")
225
+
226
+
227
+ def validate(state, rubric, ceiling):
228
+ out = Findings()
229
+ check_needs(state.get("needs") or [], out)
230
+ check_discoveries(state, rubric, ceiling, out)
231
+ return out.items
232
+
233
+
234
+ def gate(state, sections, need_ids):
235
+ needs = state.get("needs") or []
236
+ discoveries = {disc.get("id"): disc for disc in state.get("discoveries") or []}
237
+ targets = []
238
+ for ref in sections:
239
+ surface, _, section = ref.partition("/")
240
+ matched = [n for n in needs if n.get("surface") == surface and (not section or n.get("section") == section)]
241
+ targets.append((ref, matched))
242
+ for nid in need_ids:
243
+ targets.append((nid, [n for n in needs if n.get("id") == nid]))
244
+ results, blocked = [], False
245
+ for ref, matched in targets:
246
+ if not matched:
247
+ results.append({"target": ref, "result": "unplanned", "action": "not in the map: back to wizz-design-needs"})
248
+ blocked = True
249
+ continue
250
+ for need in matched:
251
+ row = {"target": ref, "need": need.get("id"), "status": need.get("status")}
252
+ status = need.get("status")
253
+ if status not in NEED_TERMINAL:
254
+ row.update(result="blocked", action="mine this need first (wizz-ui-component-curator / researcher)")
255
+ blocked = True
256
+ elif status == "selected":
257
+ chosen = [discoveries.get(ref_id) for ref_id in need.get("satisfied_by") or []]
258
+ chosen = [disc for disc in chosen if disc and disc.get("status") in {"selected", "implemented"}]
259
+ if chosen:
260
+ row.update(result="pass", discoveries=[disc.get("id") for disc in chosen])
261
+ else:
262
+ row.update(result="blocked", action="selected need without a selected discovery")
263
+ blocked = True
264
+ elif status == "deferred":
265
+ row.update(result="pass", action="deferred by the level: do not build this behaviour now")
266
+ elif status == "unmet":
267
+ row.update(result="pass", action="unmet: build from scratch, cite the research as the justification")
268
+ else:
269
+ row.update(result="pass", action=need.get("reason") or "dropped")
270
+ results.append(row)
271
+ return blocked, results
272
+
273
+
274
+ def main(argv=None):
275
+ parser = argparse.ArgumentParser(prog="design_state.py")
276
+ sub = parser.add_subparsers(dest="command", required=True)
277
+ check = sub.add_parser("validate")
278
+ check.add_argument("state")
279
+ gating = sub.add_parser("gate")
280
+ gating.add_argument("state")
281
+ gating.add_argument("--section", action="append", default=[])
282
+ gating.add_argument("--need", action="append", default=[])
283
+ try:
284
+ args = parser.parse_args(argv)
285
+ except SystemExit as error:
286
+ emit({"error": "invalid arguments"}, int(error.code or 2))
287
+ try:
288
+ state = load_state(args.state)
289
+ if args.command == "validate":
290
+ rubric, ceiling = load_rubric(), load_ceiling()
291
+ except (OSError, ValueError, ImportError) as error:
292
+ emit({"error": str(error)}, 2)
293
+ except Exception as error: # ruamel parse errors have their own hierarchy
294
+ emit({"error": f"cannot parse: {error}"}, 2)
295
+ if args.command == "validate":
296
+ findings = validate(state, rubric, ceiling)
297
+ emit({"ok": not findings, "findings": findings}, 1 if findings else 0)
298
+ if not args.section and not args.need:
299
+ emit({"error": "gate needs --section <surface>/<section> or --need <id>"}, 2)
300
+ blocked, results = gate(state, args.section, args.need)
301
+ emit({"gate": "blocked" if blocked else "pass", "results": results}, 1 if blocked else 0)
302
+
303
+
304
+ if __name__ == "__main__":
305
+ main()