wizz-method 2.17.2 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/THIRD-PARTY-NOTICES.md +12 -0
  2. package/package.json +5 -3
  3. package/removals.txt +10 -0
  4. package/skills-registry.yaml +72 -42
  5. package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +9 -9
  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 +1 -1
  8. package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/SKILL.md +1 -1
  9. package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/checklist.md +1 -1
  10. package/src/bmm-skills/4-implementation/wizz-quick-dev/SKILL.md +1 -1
  11. package/src/bmm-skills/4-implementation/wizz-sprint-status/SKILL.md +5 -5
  12. package/src/bmm-skills/module-help.csv +1 -1
  13. package/src/bmm-skills/module.yaml +1 -1
  14. package/src/core-skills/_shared/handoff-protocol.md +5 -3
  15. package/src/modules/lowticket/skills/wizz-lowticket-funil/SKILL.md +1 -1
  16. package/src/modules/lowticket/skills/wizz-lowticket-guia/SKILL.md +1 -1
  17. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/SKILL.md +1 -1
  18. package/src/modules/lowticket/skills/wizz-lowticket-minerador/SKILL.md +1 -1
  19. package/src/modules/lowticket/skills/wizz-lowticket-pagina/SKILL.md +1 -1
  20. package/src/modules/lowticket/skills/wizz-lowticket-trafego/SKILL.md +1 -1
  21. package/src/modules/wizz/_shared/cerebro.md +7 -7
  22. package/src/modules/wizz/_shared/communication-rules.md +1 -1
  23. package/src/modules/wizz/_shared/despacho-barato.md +1 -1
  24. package/src/modules/wizz/_shared/encerramento.md +4 -3
  25. package/src/modules/wizz/_shared/model-ladder.md +10 -0
  26. package/src/modules/wizz/_shared/organizacao.md +8 -4
  27. package/src/modules/wizz/_shared/token-economy.md +1 -1
  28. package/src/modules/wizz/_shared/video-pipeline.md +13 -3
  29. package/src/modules/wizz/agents/wizz-ads/SKILL.md +4 -1
  30. package/src/modules/wizz/agents/wizz-ads/customize.toml +12 -3
  31. package/src/modules/wizz/agents/wizz-ads/knowledge/playbook.md +36 -0
  32. package/src/modules/wizz/agents/wizz-copy/SKILL.md +4 -1
  33. package/src/modules/wizz/agents/wizz-copy/customize.toml +2 -3
  34. package/src/modules/wizz/agents/wizz-copy/knowledge/playbook.md +34 -0
  35. package/src/modules/wizz/agents/wizz-designer/SKILL.md +2 -2
  36. package/src/modules/wizz/agents/wizz-designer/customize.toml +2 -3
  37. package/src/modules/wizz/agents/wizz-growth/SKILL.md +4 -1
  38. package/src/modules/wizz/agents/wizz-growth/customize.toml +33 -4
  39. package/src/modules/wizz/agents/wizz-growth/knowledge/playbook.md +39 -0
  40. package/src/modules/wizz/agents/wizz-maestro/SKILL.md +7 -9
  41. package/src/modules/wizz/agents/wizz-maestro/customize.toml +2 -3
  42. package/src/modules/wizz/agents/wizz-memoria/SKILL.md +7 -7
  43. package/src/modules/wizz/agents/wizz-memoria/customize.toml +5 -6
  44. package/src/modules/wizz/agents/wizz-qa/SKILL.md +5 -6
  45. package/src/modules/wizz/agents/wizz-qa/customize.toml +3 -4
  46. package/src/modules/wizz/agents/wizz-seo/SKILL.md +4 -1
  47. package/src/modules/wizz/agents/wizz-seo/customize.toml +2 -3
  48. package/src/modules/wizz/agents/wizz-seo/knowledge/playbook.md +31 -0
  49. package/src/modules/wizz/agents/wizz-social/SKILL.md +5 -3
  50. package/src/modules/wizz/agents/wizz-social/customize.toml +12 -3
  51. package/src/modules/wizz/overrides/wizz-agent-analyst.toml +2 -2
  52. package/src/modules/wizz/overrides/wizz-agent-architect.toml +3 -3
  53. package/src/modules/wizz/overrides/wizz-agent-dev.toml +3 -3
  54. package/src/modules/wizz/overrides/wizz-agent-pm.toml +2 -2
  55. package/src/modules/wizz/overrides/wizz-agent-tech-writer.toml +2 -2
  56. package/src/modules/wizz/overrides/wizz-agent-ux-designer.toml +2 -2
  57. package/src/modules/wizz/subagents/codex/wizz-exec-standard.toml +1 -1
  58. package/src/modules/wizz/subagents/codex/wizz-exec-strong.toml +1 -1
  59. package/src/modules/wizz/subagents/gemini/wizz-exec-standard.md +1 -1
  60. package/src/modules/wizz/subagents/gemini/wizz-exec-strong.md +1 -1
  61. package/src/modules/wizz/subagents/opencode/wizz-exec-standard.md +1 -1
  62. package/src/modules/wizz/subagents/opencode/wizz-exec-strong.md +1 -1
  63. package/src/modules/wizz/subagents/spec.yaml +6 -6
  64. package/src/modules/wizz/subagents/wizz-exec-standard.md +1 -1
  65. package/src/modules/wizz/subagents/wizz-exec-strong.md +1 -1
  66. package/src/skills-lib/_shared/complexity-signal.md +8 -9
  67. package/src/skills-lib/_shared/dispatch-rule.yaml +21 -0
  68. package/src/skills-lib/{cerebro → wizz-cerebro}/SKILL.md +1 -1
  69. package/src/skills-lib/{ctc-align → wizz-ctc-align}/SKILL.md +1 -1
  70. package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/SKILL.md +1 -1
  71. package/src/skills-lib/{find-skills → wizz-find-skills}/SKILL.md +1 -1
  72. package/src/skills-lib/wizz-hook-extractor/LICENSE +21 -0
  73. package/src/skills-lib/wizz-hook-extractor/SKILL.md +56 -0
  74. package/src/skills-lib/wizz-hook-extractor/references/examples.md +59 -0
  75. package/src/skills-lib/wizz-hook-extractor/references/formulas.md +136 -0
  76. package/src/skills-lib/wizz-hook-extractor/scripts/read-post.sh +83 -0
  77. package/src/skills-lib/wizz-huashu-design/references/sfx-library.md +1 -1
  78. package/src/skills-lib/wizz-huashu-design/scripts/add-music.sh +1 -1
  79. package/src/skills-lib/wizz-huashu-design/scripts/html2pptx.js +45 -37
  80. package/src/skills-lib/wizz-huashu-design/scripts/mix-voiceover.sh +1 -1
  81. package/src/skills-lib/wizz-huashu-design/scripts/narrate-pipeline.mjs +11 -5
  82. package/src/skills-lib/wizz-huashu-design/scripts/test_scripts.py +209 -0
  83. package/src/skills-lib/wizz-huashu-design/scripts/tts-doubao.mjs +42 -28
  84. package/src/skills-lib/wizz-ig-profile-optimizer/LICENSE +21 -0
  85. package/src/skills-lib/wizz-ig-profile-optimizer/SKILL.md +74 -0
  86. package/src/skills-lib/wizz-ig-profile-optimizer/scripts/capture-profile.sh +41 -0
  87. package/src/skills-lib/wizz-ig-profile-optimizer/scripts/limits.mjs +82 -0
  88. package/src/skills-lib/wizz-inicio-de-projeto/SKILL.md +1 -1
  89. package/src/skills-lib/wizz-launch-readiness/SKILL.md +7 -7
  90. package/src/skills-lib/wizz-launch-readiness/references/02-seguranca.md +3 -3
  91. package/src/skills-lib/wizz-launch-readiness/references/03-seo-descoberta.md +5 -5
  92. package/src/skills-lib/wizz-launch-readiness/references/04-analytics-medicao.md +1 -1
  93. package/src/skills-lib/wizz-launch-readiness/references/05-conteudo-prova-social.md +6 -6
  94. package/src/skills-lib/wizz-launch-readiness/references/06-legal-lgpd.md +1 -1
  95. package/src/skills-lib/wizz-launch-readiness/references/08-fusao-priorizacao.md +1 -1
  96. package/src/skills-lib/wizz-offer-forge/SKILL.md +3 -3
  97. package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/README.md +1 -1
  98. package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/SKILL.md +1 -1
  99. package/src/skills-lib/wizz-router/SKILL.md +3 -3
  100. package/src/skills-lib/wizz-router/references/routing-table-flat.md +11 -9
  101. package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/SKILL.md +1 -1
  102. package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/SKILL.md +1 -1
  103. package/src/skills-lib/wizz-video-effects/SKILL.md +4 -3
  104. package/src/skills-lib/wizz-video-effects/references/data-contract.md +3 -3
  105. package/src/skills-lib/wizz-video-effects/scripts/fill_breaths.py +15 -1
  106. package/src/skills-lib/wizz-video-effects/scripts/test_fill_breaths.py +20 -0
  107. package/src/skills-lib/wizz-video-styles/SKILL.md +11 -5
  108. package/src/skills-lib/wizz-video-styles/data/recipes.json +65 -2
  109. package/src/skills-lib/wizz-video-styles/data/references.json +386 -1
  110. package/src/skills-lib/wizz-video-styles/data/styles.json +109 -0
  111. package/src/skills-lib/wizz-video-styles/references/generated-prompts.md +70 -0
  112. package/src/skills-lib/wizz-video-styles/references/measured-patterns.md +2 -0
  113. package/src/skills-lib/wizz-video-styles/references/style-picker.md +8 -4
  114. package/src/skills-lib/wizz-video-styles/references/styles/tipografia-cinetica.md +68 -0
  115. package/src/skills-lib/wizz-video-styles/references/styles/ui-morph.md +68 -0
  116. package/src/skills-lib/wizz-video-styles/scripts/analyze.sh +23 -10
  117. package/src/skills-lib/wizz-video-styles/scripts/beat_grid.py +68 -0
  118. package/src/skills-lib/wizz-video-styles/scripts/references.mjs +265 -0
  119. package/src/skills-lib/wizz-web-security/SKILL.md +1 -1
  120. package/tools/fetch-assets.mjs +1 -1
  121. package/tools/hooks/README.md +18 -14
  122. package/tools/hooks/rtk-rewrite.js +95 -0
  123. package/tools/hooks/security-defensive-context.js +17 -1
  124. package/tools/hooks/session-rules.js +25 -5
  125. package/tools/hooks/wizz-arrumar.js +126 -12
  126. package/tools/hooks/wizz-artifact-guard.js +41 -7
  127. package/tools/hooks/wizz-cerebro-lib.js +239 -0
  128. package/tools/hooks/wizz-close-reminder.js +124 -0
  129. package/tools/hooks/wizz-debug-lib.js +55 -0
  130. package/tools/hooks/wizz-decision-trace.js +19 -2
  131. package/tools/hooks/wizz-feature.js +20 -4
  132. package/tools/hooks/wizz-router-enforce.js +30 -13
  133. package/tools/installer/command-text.js +243 -0
  134. package/tools/installer/commands/catalog.js +2 -0
  135. package/tools/installer/commands/doctor.js +10 -12
  136. package/tools/installer/commands/fetch-assets.js +5 -5
  137. package/tools/installer/commands/install.js +6 -7
  138. package/tools/installer/commands/resolve.js +17 -17
  139. package/tools/installer/commands/trace-report.js +40 -37
  140. package/tools/installer/core/doctor.js +60 -55
  141. package/tools/installer/core/hooks-settings.js +88 -14
  142. package/tools/installer/core/hooks-smoke.js +134 -0
  143. package/tools/installer/core/installer.js +61 -52
  144. package/tools/installer/core/quick-update.js +2 -3
  145. package/tools/installer/install-text.js +201 -0
  146. package/tools/installer/modules/channel-plan.js +2 -2
  147. package/tools/installer/modules/env-vars.js +5 -6
  148. package/tools/installer/modules/skills-lib.js +1 -1
  149. package/tools/installer/ui.js +17 -9
  150. /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-pesquisa-e-memoria.md +0 -0
  151. /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-rotina-e-captura.md +0 -0
  152. /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-sessao.md +0 -0
  153. /package/src/skills-lib/{ctc-align → wizz-ctc-align}/scripts/align.py +0 -0
  154. /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/README.md +0 -0
  155. /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/references/KEYWORDS.md +0 -0
  156. /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/assets/template.html +0 -0
  157. /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/prompts.md +0 -0
  158. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-1-validacao-entrada.md +0 -0
  159. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-2-autorizacao.md +0 -0
  160. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-3-abuso-volume.md +0 -0
  161. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-4-vazamento-resposta.md +0 -0
  162. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-5-fusao-plano.md +0 -0
  163. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/01-cta-principal.md +0 -0
  164. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/02-barra-fixa-mobile.md +0 -0
  165. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/03-tempo-de-resposta.md +0 -0
  166. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/04-prova-social.md +0 -0
  167. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/05-faq-decisao.md +0 -0
  168. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/06-imagens-reais.md +0 -0
  169. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/07-endereco-como-chegar.md +0 -0
  170. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/08-titles.md +0 -0
  171. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/09-open-graph.md +0 -0
  172. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/10-breadcrumbs.md +0 -0
  173. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/11-alt-text.md +0 -0
  174. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/12-schema-local.md +0 -0
  175. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/13-robots-sitemap.md +0 -0
  176. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/14-privacidade-termos.md +0 -0
  177. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/15-medicao.md +0 -0
@@ -2,7 +2,7 @@
2
2
  # Gerado por tools/generate-subagents.mjs a partir de src/modules/wizz/subagents/spec.yaml. Não edite este arquivo direto: edite o spec.yaml e rode `npm run generate:subagents`.
3
3
  name: wizz-exec-standard
4
4
  description: >-
5
- Standard Wizz Method executor for day-to-day implementation. Well-defined small/medium features, bug fixes with a known cause, multi-file edits, tests. Same tier as the recommended session model: dispatch it for isolation or parallel work (1 per file/module, up to 4 at once), not to escalate.
5
+ Standard Wizz Method executor for day-to-day implementation. Well-defined small/medium features, bug fixes with a known cause, multi-file edits, tests. Same tier as the recommended session model: dispatch it for isolation or parallel work (1 per file/module, up to 4 at once), not to escalate. Size, not just difficulty: work crossing more than one layer (database, backend/worker, app/UI, external automation) or ~8 files is split, one dispatch per layer, each brief with max_calls.
6
6
  model: sonnet
7
7
  effort: medium
8
8
  maxTurns: 80
@@ -2,7 +2,7 @@
2
2
  # Gerado por tools/generate-subagents.mjs a partir de src/modules/wizz/subagents/spec.yaml. Não edite este arquivo direto: edite o spec.yaml e rode `npm run generate:subagents`.
3
3
  name: wizz-exec-strong
4
4
  description: >-
5
- Strong Wizz Method executor for hard work. Bugs with no known cause after one investigation cycle, cross-module refactors, schema/auth/payment changes, local technical decisions. The session dispatches it when the work is too hard for the session's own tier. If the work turns out to be ultra hard, it returns ESCALAR with its findings; it never dispatches anything itself.
5
+ Strong Wizz Method executor for hard work. Bugs with no known cause after one investigation cycle, cross-module refactors, schema/auth/payment changes, local technical decisions. Size, not just difficulty: work crossing more than one layer (database, backend/worker, app/UI, external automation) or ~8 files is split, one dispatch per layer, each brief with max_calls. The session dispatches it when the work is too hard for the session's own tier. If the work turns out to be ultra hard, it returns ESCALAR with its findings; it never dispatches anything itself.
6
6
  model: opus
7
7
  effort: high
8
8
  maxTurns: 120
@@ -1,16 +1,15 @@
1
1
  # Complexity signal (single source)
2
2
 
3
3
  # Read by wizz-router (SKILL.md), wizz-maestro (SKILL.md) and wizz-quick-dev
4
- # (SKILL.md) as the CANONICAL SOURCE of the rule paragraph below. The SKILL.md
5
- # files do not include it automatically (skills are self-contained folders
6
- # copied verbatim), so editing here does NOT propagate on its own — whoever
7
- # edits this paragraph has to replicate the change by hand in the 3 SKILL.md
8
- # files. `test/test-dispatch-rule-consistency.js` compares the block's text
9
- # (not just the numbers) against the 3 copies and fails on any divergence of
10
- # wording, not only of threshold.
4
+ # (SKILL.md) as the CANONICAL SOURCE of the counting rules below. The
5
+ # dispatch-rule and decision-order lines are GENERATED from
6
+ # `dispatch-rule.yaml` (next to this file) into here, the 3 SKILL.md files and
7
+ # the router hook by `tools/generate-dispatch-rule.mjs`: edit the yaml and run
8
+ # `npm run generate:dispatch-rule`, never these lines.
9
+ # `test/test-dispatch-rule-generated.js` fails on any drift.
11
10
  # ---
12
11
 
13
- Dispatch rule: IF 2+ areas → maestro, always. OTHERWISE count the 3 remaining factors (multi-step, needs planning, produces a memorable artifact); 2+ factors → maestro. The area clause decides on its own, without summing with anything else.
12
+ Dispatch rule: IF 2+ areas → maestro, always. OTHERWISE count the 3 remaining factors (multi-step, needs planning, produces a memorable artifact); 2+ factors → maestro. The area clause decides on its own, without combining with anything else.
14
13
 
15
14
  **Decision order — capabilities → required relations → areas → shortcuts.** Match the catalogue entries, inspect their `rel`, and include only capabilities needed for the requested deliverable and not already supplied in the context. Resolve their owning areas before applying ANY one-area shortcut. One artifact can require multiple areas; the word "copy" does not prove that only copy is needed. For full sales-page copy, if either the offer or the conversion structure is missing from the supplied context, include the missing capability: `wizz-offer-forge` for the offer and/or `wizz-page-cro` for conversion structure (growth), alongside `wizz-copywriting` (copy); route to maestro. Do not assume those inputs are ready merely because the user did not mention them. If both are already approved and the request is limited to text, keep copy-only work in copy. An isolated headline or CTA does not require redesigning the whole page. Optional neighbours and separate deliverables do not add areas; `pairs_with` alone never escalates.
16
15
 
@@ -34,7 +33,7 @@ Written as bare labels, the 3 factors above were true of almost any request, and
34
33
 
35
34
  **The counterpart, and this is what is left for the maestro:** a request **of open scope, which the request itself does not delimit** — "how do I improve the signup conversion rate", "the sales page gets plenty of visits and almost nobody buys", "my sales page converts badly, improve it". There the scope has to be discovered before anything is delivered, and discovering scope is the Manager's work. The deciding difference is not the size of the request, it is whether **the request already states what the delivery is**.
36
35
 
37
- **And the counterpart has one exception: a single entry already delimits the scope.** If the whole outcome being asked for fits the `when:` of ONE catalogue entry, and what leaves at the end is that entry's own deliverable — a plan, a recommendation, an audit — then the scope is not open, the catalogue delimited it, and the request goes to that entry's area. "A plan to reduce churn" is `wizz-churn-prevention`; "I want ChatGPT and Perplexity to cite us" is `wizz-ai-seo`; "what to check before going live" is `site-launch-kit`. It stops being the exception the moment delivering the outcome means changing what other areas own: "improve the signup conversion rate" ends up in copy, UI and code, so it comes back to the maestro through the area clause. Ask which single entry would deliver it whole — if it takes two, the scope really is open. (Owner's decision, 2026-09-18.)
36
+ **And the counterpart has one exception: a single entry already delimits the scope.** If the whole outcome being asked for fits the `when:` of ONE catalogue entry, and what leaves at the end is that entry's own deliverable — a plan, a recommendation, an audit — then the scope is not open, the catalogue delimited it, and the request goes to that entry's area. "A plan to reduce churn" is `wizz-churn-prevention`; "I want ChatGPT and Perplexity to cite us" is `wizz-ai-seo`; "what to check before going live" is `wizz-site-launch-kit`. It stops being the exception the moment delivering the outcome means changing what other areas own: "improve the signup conversion rate" ends up in copy, UI and code, so it comes back to the maestro through the area clause. Ask which single entry would deliver it whole — if it takes two, the scope really is open. (Owner's decision, 2026-09-18.)
38
37
 
39
38
  **Changing the product's code is `dev`, even when the subject is visual.** "Adjust the secondary button's colour", "fix the mobile spacing": the delivery is a commit, so it is the dev area — `designer` decides what the look should be, it does not ship the change. Ask which artifact leaves at the end: a file in the repo → dev; a decision, a spec or a piece of art → the area that owns it. **Exception: new or rebuilt screens.** When the request asks to BUILD screens in code (a new app, page, dashboard or panel, or a whole screen rebuilt in the product), the visual decision has not been made yet, so `designer` comes first and the sequence is design → dev (2 areas → maestro): the designer runs its research gate before any screen is coded. A tweak to an existing screen (a price, a label, a spacing) stays dev, and redesigning a section with no build asked for (a hero, a card) stays designer.
40
39
 
@@ -0,0 +1,21 @@
1
+ # Fonte única da regra de dispatch (2026-10-15).
2
+ #
3
+ # tools/generate-dispatch-rule.mjs reescreve, a partir daqui, a linha da regra e
4
+ # a linha da ordem de decisão em complexity-signal.md e nas SKILL.md do
5
+ # wizz-router, wizz-maestro e wizz-quick-dev, e a dica curta do hook
6
+ # tools/hooks/wizz-router-enforce.js. Não edite essas linhas nos arquivos:
7
+ # edite aqui e rode `npm run generate:dispatch-rule`. O `--check` roda no
8
+ # `npm test` (test/test-dispatch-rule-generated.js) e falha em qualquer drift.
9
+ #
10
+ # `{maestro}` vira o nome que cada arquivo usa para o maestro (ver `targets`
11
+ # no gerador). O texto renderizado na SKILL.md do router entra no prompt do
12
+ # eval de rota: mudar `rule` ou `decision_order` é mudança de rota e exige a
13
+ # prova de eval (linha de base em worktree, 9 amostras nas duas árvores).
14
+
15
+ rule: "IF 2+ areas → {maestro}, always. OTHERWISE count the 3 remaining factors (multi-step, needs planning, produces a memorable artifact); 2+ factors → {maestro}. The area clause decides on its own, without combining with anything else."
16
+
17
+ decision_order: "**Decision order — capabilities → required relations → areas → shortcuts.** Match the catalogue entries, inspect their `rel`, and include only capabilities needed for the requested deliverable and not already supplied in the context. Resolve their owning areas before applying ANY one-area shortcut. One artifact can require multiple areas; the word \"copy\" does not prove that only copy is needed. For full sales-page copy, if either the offer or the conversion structure is missing from the supplied context, include the missing capability: `wizz-offer-forge` for the offer and/or `wizz-page-cro` for conversion structure (growth), alongside `wizz-copywriting` (copy); route to maestro. Do not assume those inputs are ready merely because the user did not mention them. If both are already approved and the request is limited to text, keep copy-only work in copy. An isolated headline or CTA does not require redesigning the whole page. Optional neighbours and separate deliverables do not add areas; `pairs_with` alone never escalates."
18
+
19
+ # Paráfrase curta em PT-BR injetada pelo hook em projeto Wizz (a cada prompt,
20
+ # então fica curta de propósito).
21
+ hook_hint: "🎛️ Wizz: NÃO invoque wizz-router. Capacidades → rel necessário à entrega → áreas → atalhos. 2+ áreas → `wizz-maestro`. 1 área: 2+ fatores (multi-passo, planejar, artefato memorável) → `wizz-maestro`; senão → Skill da área. Auth/pagamento/permissão/migração → maestro. Personas são skills, não subagent_type; Agent usa tipos disponíveis wizz-exec-*; difícil → wizz-exec-strong (ESCALAR → ultra)."
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: cerebro
2
+ name: wizz-cerebro
3
3
  description: >
4
4
  Persistent memory system across AI sessions. Use this skill whenever the user types
5
5
  /iniciar, /salvar, /ver, /dia, /dump, /decisao, /conteudo, /prospect or /memoria. Also activate when the
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ctc-align
2
+ name: wizz-ctc-align
3
3
  description: Reliable word-by-word timestamps for TTS narration via forced alignment (ctc-forced-aligner + anchoring on real silences). ALWAYS use before setting caption timing, scene cuts, animation, or any speech-synchronized element in any video (HyperFrames or other editor). Replaces Whisper as the timing source when the narration script text is known. Triggers - sincronizar legenda, timing de cena, alinhar áudio e texto, palavra a palavra, forced alignment, legenda karaokê, corrigir sync de vídeo. Use when the exact narration script is known and you need to decide caption timing, scene cuts, or speech-synchronized animation.
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: enhance-prompt
2
+ name: wizz-enhance-prompt
3
3
  description: Transforms vague UI ideas into polished, Stitch-optimized prompts. Enhances specificity, adds UI/UX keywords, injects design system context, and structures output for better generation results. Use when the user wants to polish a UI prompt before sending it to Stitch, improve a prompt that produced poor results, or turn a vague UI idea into a structured, design-system-aware prompt.
4
4
  allowed-tools:
5
5
  - "Read"
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: find-skills
2
+ name: wizz-find-skills
3
3
  description: Helps users discover and install agent skills OR MCP servers when they ask questions like "how do I do X", "find a skill for X", "adicione essa skill", "adicione esse MCP", "is there a skill/mcp that can...", or express interest in extending capabilities. Use when the user is looking for functionality that might exist as an installable skill or MCP server.
4
4
  ---
5
5
 
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sergey Bulaev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: wizz-hook-extractor
3
+ description: Reverse-engineers the hook of one viral Instagram Reel, carousel or post and hands back a mould ready to fill with the user's own topic. Reads the post itself from the link (caption, metrics, first 3 seconds, local transcription), classifies the hook with the Viral Script Blueprint types (Tipo 01 to 07) plus the post shape, breaks down hook, body and close, names the goal the original chased and why it worked, points the editing style from the wizz-video-styles catalogue, and lists what not to copy. Use when someone shares a viral post and wants to know why it worked or wants to do one like it. Triggers - por que esse reel viralizou, o que tem de bom nesse post, quero fazer um igual a esse, destrincha esse carrossel, extrai o gancho desse vídeo. Does not write the user's own script from scratch (that is wizz-social-content and the Blueprint) and does not study a whole niche (that is the reverse-engineering reference of wizz-social-content).
4
+ ---
5
+
6
+ # wizz-hook-extractor: why that post worked, as a mould
7
+
8
+ One post in, one teardown out: the hook classified the way the Method already numbers hooks, the structure around it, the goal, and a mould with `{slots}` that drops into a script without rework.
9
+
10
+ All text delivered to the user is Brazilian Portuguese with "você" (never "tu/teu"), and no em dash in anything a third party reads.
11
+
12
+ ## Inputs
13
+
14
+ - A link (`instagram.com/reel/...`, `/p/...`, `/tv/...`). Preferred: the skill reads it.
15
+ - Or, when reading is impossible: a screenshot, or the caption plus the text on the slides, or a description of the Reel's first 3 seconds.
16
+ - The user's topic (or project), to aim the mould. If missing, ask once, in the same message as anything else.
17
+
18
+ ## Steps
19
+
20
+ 1. **Read the post yourself.** Run `scripts/read-post.sh <url> <out_dir>` (out_dir under the project's `_wizz-output/`). It writes `post.json` (surface, caption, author, likes, comments, views), and for a Reel `head.png` (first 3 s, 6 frames), `sheet.png` (whole Reel) and `transcript.json` (first 10 s, local Whisper through `npx hyperframes transcribe`, no API key). **Open the images**: the hook of a Reel is usually the text on the first frame. When `post.json` says `speech: suspeita` or `sem fala`, the transcript is Whisper inventing words over music: ignore it and read the hook from the frames.
21
+ - Exit 3 = Instagram asked for a login. Ask the user, once, whether to use their own logged-in browser for this read (`WIZZ_IG_COOKIES_BROWSER=<browser>`); never do it without that OK. If they decline, ask for a screenshot or the pasted text.
22
+ - A carousel comes back with the slide count only: capture the slides with `wizz-agent-browser` (one screenshot per slide) or ask for them.
23
+ 2. **Classify the hook** with [references/formulas.md](references/formulas.md): the Blueprint type (Tipo 01 to 07, or "fora dos 7") with a confidence from 0 to 1, and the post shape. When two fit, give both with their scores; the tiebreaker is the goal and the surface.
24
+ 3. **Break down the structure.** Hook (first ~125 characters of the caption, slide 1, or the first 3 s), body (role of each slide, or the order of the Reel's beats with timestamps from the transcript), close (what earns the save, send, comment or follow).
25
+ 4. **Goal and why it worked.** One goal (saves, shares, comments, follows) and two or three reasons tied to what is on the post, not generic psychology. Metrics from `post.json` back the claim when present (comments far above the usual ratio = a comment hook).
26
+ 5. **Editing style (Reels).** Compare `sheet.png` with the cards of `wizz-video-styles` and name the closest style. That is what keeps the hook and the edit in one delivery.
27
+ 6. **Mould.** The shape's skeleton from formulas.md, slots renamed to the user's topic. For a Reel, write it in the `wizz-social` script format (`[HOOK · Tipo NN]`, `[TUTORIAL]`, `[RESULTADO VISUAL]`, `[CTA triplo]`) so it pastes into a script. For a carousel, slide by slide.
28
+ 7. **What not to copy.** Formulas.md section 5, applied to this post.
29
+
30
+ ## Delivery
31
+
32
+ ```
33
+ 🔎 POST LIDO: superfície · autor · métricas (curtidas, comentários, views) · como foi lido (link / login / print)
34
+ 🎣 GANCHO: Tipo NN · nome (0,x) [+ 2ª opção] · formato: {forma do post}
35
+ 🧱 ESTRUTURA: gancho / corpo / fechamento
36
+ 🎯 OBJETIVO: salvar | enviar | comentar | seguir · por que funcionou (2 a 3 motivos ligados ao post)
37
+ 🎬 ESTILO DE EDIÇÃO: {estilo do wizz-video-styles} (Reels)
38
+ 🧩 MOLDE PRO SEU TEMA: {molde com slots}
39
+ ⚠️ NÃO COPIAR: {lista}
40
+ ```
41
+
42
+ Worked examples: [references/examples.md](references/examples.md).
43
+
44
+ ## Hand-off
45
+
46
+ - Turning the mould into a full Reel script: `wizz-social` (RT command, Blueprint) or `wizz-social-content` for a post.
47
+ - The carousel slides themselves: `wizz-canvas-design`.
48
+ - The edit of the Reel: `wizz-video-styles` with the style named in step 5.
49
+ - Text that needs to sound less like AI before publishing: `wizz-humanizer`.
50
+
51
+ ## Hard rules
52
+
53
+ - Never invent a metric or a number for the mould; a slot stays a slot.
54
+ - Never log in on the user's behalf, and never store cookies or credentials. The browser session is theirs and is used only after their OK, for that read.
55
+ - Delete downloaded media after reading (the script does); keep only the frames, the transcript and `post.json`.
56
+ - One post per teardown. Studying a whole profile or niche is the reverse-engineering reference of `wizz-social-content`.
@@ -0,0 +1,59 @@
1
+ # Worked teardowns
2
+
3
+ Three teardowns in the delivery format of [SKILL.md](../SKILL.md). The posts are illustrative, not real.
4
+
5
+ ## Legenda (single image)
6
+
7
+ **Read:** caption starts "Apaguei 4.000 dos meus 6.000 inscritos da newsletter e a taxa de abertura dobrou."
8
+
9
+ - **Gancho:** Tipo 03 · O Comando Provocador (0,7), as a contrary action; second: Tipo 01 · A Façanha Pessoal (0,6), pulled by the exact numbers. Shape: Verdade contrária.
10
+ - **Superfície:** single image; the whole idea fits in the first 125 characters.
11
+ - **Estrutura:** hook = the counter-move with two real numbers, no setup. Body = one reason. Close = a "manda pra quem..." prompt.
12
+ - **Objetivo:** shares ("viu, eu falei").
13
+ - **Por que funcionou:** a defensible counter-move plus hard numbers is screenshot-ready; the numbers pre-empt the "prova" comment.
14
+ - **Molde:**
15
+ ```
16
+ Eu {ação contrária com número real} e {resultado melhor e inesperado}.
17
+
18
+ {a razão em uma linha}.
19
+
20
+ {pedido de envio ou de salvar}.
21
+ ```
22
+ - **Não copiar:** nothing in the hook; confirm the numbers are real before reusing.
23
+
24
+ ## Carrossel (7 slides)
25
+
26
+ **Read:** slide 1 "5 erros de precificação que estão te fazendo cobrar 40% a menos (o 4º quase ninguém percebe)"; slides 2 to 6 one error each; slide 7 the list on one slide + "salva".
27
+
28
+ - **Gancho:** Tipo 05 · A Lista Urgente (0,9). Shape: Lista.
29
+ - **Superfície:** carousel; one item per slide earns the save.
30
+ - **Estrutura:** slide 1 = promise + named loss + open loop ("o 4º"). Slides 2 to 6 = one error, one example. Slide 7 = recap, single ask.
31
+ - **Objetivo:** saves.
32
+ - **Por que funcionou:** a specific loss (40%) raises the stakes; the open loop pulls the swipe to slide 5.
33
+ - **Molde:**
34
+ ```
35
+ Slide 1: {N} erros de {tema} que estão te fazendo {perda com número} (o {k}º quase ninguém percebe)
36
+ Slides 2..N+1: erro, por que custa, o que fazer no lugar.
37
+ Último: a lista num slide só + "salva pra revisar antes de {momento}".
38
+ ```
39
+ - **Estilo de edição:** static carousel, no video: hand the slides to `wizz-canvas-design`.
40
+ - **Não copiar:** if any slide has 3 lines of text where 1 fits, cut it.
41
+
42
+ ## Reel (32 s, read with scripts/read-post.sh)
43
+
44
+ **Read:** head.png shows on-screen text "Você está postando Reels no pior horário possível"; transcript starts "Aqui está quando o seu público está online de verdade"; sheet.png shows a talking head with captions 2 to 3 words at a time, cutting to phone screenshots.
45
+
46
+ - **Gancho:** Tipo 06 · A Revelação de Erro (0,8). Shape: Quebra de padrão.
47
+ - **Superfície:** Reel; text on the first frame gets silent viewers.
48
+ - **Estrutura:** 0 to 3 s = mistake named on screen + first line goes straight to the fix. 3 to 25 s = three steps, each with a screenshot. 25 to 32 s = result on screen, "salva", follow.
49
+ - **Objetivo:** shares (people send it to whoever manages their account).
50
+ - **Estilo de edição (wizz-video-styles):** talking-head-cards (face + full-screen screenshots), captions as in talking-head-legenda.
51
+ - **Molde (pronto pro roteiro):**
52
+ ```
53
+ [HOOK · Tipo 06] Texto na tela: "Você está {erro comum} {consequência}."
54
+ 1ª fala: "Aqui está {a correção} de verdade."
55
+ [TUTORIAL] passo 1 → print / passo 2 → print / passo 3 → print
56
+ [RESULTADO VISUAL] → mostrar {métrica antes e depois}
57
+ [CTA triplo]
58
+ ```
59
+ - **Não copiar:** 25 hashtags in the caption.
@@ -0,0 +1,136 @@
1
+ # Hook formulas and post shapes, mapped to the Blueprint types
2
+
3
+ The Method already numbers hooks: the **7 types** of the Viral Script Blueprint (`wizz-social` agent, `knowledge/blueprint-roteiro-viral.md`, section 1). This skill classifies the hook with **those** types, so an extracted mould drops straight into a script. What the Blueprint does not cover is the **shape of the post** around the hook (a caption cut at the "mais" fold, a carousel slide by slide, a Reel's first seconds). The 10 shapes below cover that; they are adapted from sergebulaev/instagram-skills (MIT, credited in `THIRD-PARTY-NOTICES.md`).
4
+
5
+ ## 1. Hook → Blueprint type
6
+
7
+ | Type | Name | How to spot it in the first line / slide 1 / first 3 s |
8
+ |---|---|---|
9
+ | Tipo 01 | A Façanha Pessoal | past tense + an absurd or precise result of the author ("eu fiz X em 47 minutos") |
10
+ | Tipo 02 | A Sentença de Morte | a tool, job or company declared dead ("X acabou de matar Y") |
11
+ | Tipo 03 | O Comando Provocador | an order that sounds radical ("pode demitir...", "para de postar...") |
12
+ | Tipo 04 | A Pergunta de Resultado | a question about a specific result the viewer wants |
13
+ | Tipo 05 | A Lista Urgente | a number + things/errors/prompts ("5 erros que...") |
14
+ | Tipo 06 | A Revelação de Erro | points at something the viewer does wrong, with a cost ("se você faz X, está perdendo Y") |
15
+ | Tipo 07 | O Breaking News | a fresh piece of news as a headline, with "a partir de hoje" |
16
+ | fora dos 7 | identificação / confissão | see the equivalence table: two Instagram shapes have no Blueprint type |
17
+
18
+ ## 2. Shape → Blueprint type (equivalence)
19
+
20
+ The 10 shapes do not map 1 to 1. This table is the single place where they meet; never number hooks with IG1..IG10 in a delivery, use the Tipo and the shape name.
21
+
22
+ | Shape (surface) | Closest Blueprint type | Note |
23
+ |---|---|---|
24
+ | Resultado com número (legenda) | Tipo 01 | Tipo 01 with an odd, measured number |
25
+ | Verdade contrária (legenda) | Tipo 03, or Tipo 02 when something is declared dead | |
26
+ | Identificação (legenda) | **fora dos 7** (closest: Tipo 06 without the guilt) | a shared moment, no number, no lesson |
27
+ | Confissão curta (legenda ou Reel) | **fora dos 7** (closest: Tipo 01 told from the failure) | a real cost, then the lesson |
28
+ | Lista (carrossel) | Tipo 05 | |
29
+ | Antes e depois (carrossel) | Tipo 01 | Blueprint phase 04 "resultado visual" moved to slide 1 |
30
+ | Mito e verdade (carrossel) | Tipo 06 | |
31
+ | Framework (carrossel) | Tipo 01 + the Blueprint's "Revelação de Sistema" structure | |
32
+ | Quebra de padrão (Reel) | Tipo 02, 03 or 07: the shape is how it is delivered, the type is what is said | |
33
+ | "Como eu fiz" (Reel) | Tipo 01 + the Blueprint's "Tutorial Bombástico" structure | |
34
+
35
+ Tipo 04 and Tipo 07 have no Instagram shape of their own: they live in any surface.
36
+
37
+ ## 3. The 10 shapes
38
+
39
+ Moulds use `{slots}` and Brazilian Portuguese with "você". Goal = what the shape tends to earn.
40
+
41
+ ### Legenda (single image or any post): the first ~125 characters, before "mais"
42
+
43
+ **Resultado com número** · saves
44
+ ```
45
+ {resultado com número exato} em {prazo}.
46
+
47
+ O que eu mudei está aqui embaixo.
48
+ ```
49
+ Odd numbers (47 minutos, R$ 873) read as measured. Never invent the number.
50
+
51
+ **Verdade contrária** · shares
52
+ ```
53
+ {conselho que todo mundo repete} está errado.
54
+
55
+ {o que funciona, dito sem rodeio}.
56
+ ```
57
+ Only claims the author would defend in the comments.
58
+
59
+ **Identificação** · comments
60
+ ```
61
+ {um momento específico que todo mundo do nicho conhece, sem contexto}
62
+ ```
63
+ Specific beats generic ("abrir o Canva pra fazer uma arte rápida e fechar 90 minutos depois").
64
+
65
+ **Confissão curta** · comments and follows
66
+ ```
67
+ {marcador de tempo}: {o que aconteceu, incluindo a parte feia}.
68
+
69
+ {a linha de sentido que faz valer a leitura}.
70
+ ```
71
+ It has to be true.
72
+
73
+ ### Carrossel: slide 1 promises, the last slide pays
74
+
75
+ **Lista** · saves
76
+ ```
77
+ Slide 1: {N} {coisas} que {resultado grande e específico}. (quase todo mundo erra a {k})
78
+ Slides 2..N-1: um item por slide, com exemplo concreto.
79
+ Último: resumo em uma linha + "salva pra usar depois".
80
+ ```
81
+
82
+ **Antes e depois** · saves and follows
83
+ ```
84
+ Slide 1: {o depois, com número ou imagem forte}.
85
+ Slide 2: de onde começou (o antes).
86
+ Slides 3..N-1: os passos, um por slide.
87
+ Último: "segue pra ver o sistema completo de {tema}" + pedido de salvar.
88
+ ```
89
+
90
+ **Mito e verdade** · shares
91
+ ```
92
+ Slide 1: {N} mitos de {tema} que estão te custando {perda específica}.
93
+ Slides 2..N-1: "Mito: {crença}." / "Verdade: {o que funciona}."
94
+ Último: a virada em uma linha + "manda pra quem ainda acredita no 1".
95
+ ```
96
+ Never invent the myth to knock it down.
97
+
98
+ **Framework** · saves
99
+ ```
100
+ Slide 1: o {nome} que eu uso pra {resultado}.
101
+ Slides 2..N-1: uma parte por slide, com o porquê.
102
+ Último: o framework inteiro num slide só + pedido de salvar.
103
+ ```
104
+
105
+ ### Reel: the first 1 to 3 seconds (image + text on screen + first spoken line)
106
+
107
+ **Quebra de padrão** · shares
108
+ ```
109
+ Texto do 1º quadro: {afirmação ou pergunta que faz passar reto parecer perda}.
110
+ 1ª fala: {direto ao ponto, sem "e aí, pessoal"}.
111
+ ```
112
+
113
+ **"Como eu fiz"** · saves
114
+ ```
115
+ Texto do 1º quadro: como eu {resultado com número} em {prazo}.
116
+ Corpo: passo 1, 2, 3 com rótulo na tela, cortes rápidos, sem enchimento.
117
+ Fim: "salva pra fazer junto" + resumo de uma linha na tela.
118
+ ```
119
+
120
+ ## 4. Goal of the original
121
+
122
+ | Close / structure | Goal |
123
+ |---|---|
124
+ | "salva", list, framework, how-to | saves |
125
+ | "manda pra...", contrary take, myth | shares |
126
+ | identification, question, confession | comments |
127
+ | proof of transformation, "segue pra..." | follows |
128
+
129
+ ## 5. What not to copy from a viral post
130
+
131
+ - Dashes gluing clauses together, and in text a third party reads, no em dash at all (Wizz rule).
132
+ - An AI-vocabulary cluster ("alavancar", "fundamentalmente", "eleve", "potencialize") in one paragraph.
133
+ - 20 to 30 hashtags at the top of the caption.
134
+ - Engagement bait ("comenta SIM", "marca 3 amigos"): Instagram pushes it down.
135
+ - A hook that only makes sense after the "mais" fold.
136
+ - Images and videos mixed in one carousel.
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env bash
2
+ # Reads one public Instagram post so its hook can be studied without the user pasting anything.
3
+ # Usage: read-post.sh <instagram_url> <out_dir>
4
+ # → <out_dir>/post.json (caption, author, metrics, surface), head.png (first 3 s, Reels),
5
+ # sheet.png (whole Reel), transcript.json (first 10 s of speech, local Whisper).
6
+ # Exit 0 = read; 3 = Instagram asked for a login (see the message); 2 = usage or missing tool.
7
+ # Login is never automatic: set WIZZ_IG_COOKIES_BROWSER=<browser> (chrome, opera, safari...) only
8
+ # after the user agreed to use their own logged-in browser session for this read.
9
+ set -euo pipefail
10
+ [ $# -eq 2 ] || { echo "uso: read-post.sh <url do instagram> <pasta de saída>" >&2; exit 2; }
11
+ url="$1"; out="$2"
12
+ [[ "$url" =~ ^https://(www\.)?instagram\.com/([A-Za-z0-9._]+/)?(reel|reels|p|tv)/[A-Za-z0-9_-]+/?(\?.*)?$ ]] \
13
+ || { echo "url inválida (esperado instagram.com/reel/..., /p/... ou /tv/...): $url" >&2; exit 2; }
14
+ for bin in yt-dlp ffmpeg node; do command -v "$bin" >/dev/null || { echo "falta $bin no PATH" >&2; exit 2; }; done
15
+ mkdir -p "$out"; cd "$out"
16
+ trap 'rm -f ./media.* ./head.wav ./raw.json.part' EXIT
17
+
18
+ auth=()
19
+ [ -n "${WIZZ_IG_COOKIES_BROWSER:-}" ] && auth=(--cookies-from-browser "$WIZZ_IG_COOKIES_BROWSER")
20
+ if ! yt-dlp -q --no-warnings --socket-timeout 20 ${auth[@]+"${auth[@]}"} -J "$url" > raw.json 2> err.txt; then
21
+ if grep -qiE "login|cookies|empty media|not available" err.txt; then
22
+ echo "O Instagram pediu login para este post. Opções: (1) com o OK do usuário, rodar de novo com WIZZ_IG_COOKIES_BROWSER=<navegador logado>; (2) print do post; (3) colar a legenda e o texto da tela." >&2
23
+ exit 3
24
+ fi
25
+ cat err.txt >&2; exit 1
26
+ fi
27
+
28
+ # Caption, author and metrics; a carousel comes as a playlist of entries.
29
+ node - <<'JS'
30
+ const fs = require('node:fs');
31
+ const raw = JSON.parse(fs.readFileSync('raw.json', 'utf8'));
32
+ const entries = raw._type === 'playlist' ? raw.entries ?? [] : [raw];
33
+ const videos = entries.filter((e) => e && (e.vcodec && e.vcodec !== 'none' || e.duration));
34
+ const surface = raw._type === 'playlist' ? 'carrossel' : videos.length ? 'reel' : 'imagem';
35
+ const pick = (k) => raw[k] ?? entries[0]?.[k] ?? null;
36
+ fs.writeFileSync('post.json', JSON.stringify({
37
+ url: raw.webpage_url ?? raw.original_url,
38
+ surface,
39
+ slides: raw._type === 'playlist' ? entries.length : 1,
40
+ author: pick('uploader') ?? pick('channel'),
41
+ handle: pick('channel') ?? pick('uploader_id'),
42
+ date: pick('upload_date'),
43
+ caption: pick('description') ?? '',
44
+ likes: pick('like_count'),
45
+ comments: pick('comment_count'),
46
+ views: pick('view_count'),
47
+ duration_s: pick('duration'),
48
+ }, null, 1));
49
+ JS
50
+ rm -f raw.json err.txt
51
+
52
+ surface=$(node -p "require('./post.json').surface")
53
+ if [ "$surface" = "reel" ]; then
54
+ yt-dlp -q --no-warnings --socket-timeout 20 ${auth[@]+"${auth[@]}"} -o "media.%(ext)s" "$url"
55
+ media=$(ls media.* | head -1)
56
+ ffmpeg -loglevel error -y -t 3 -i "$media" -vf "fps=2,scale=270:-1,tile=6x1" -frames:v 1 head.png
57
+ d=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$media")
58
+ step=$(node -p "Math.max(1, $d / 8)")
59
+ ffmpeg -loglevel error -y -i "$media" -vf "fps=1/$step,scale=270:-1,tile=8x1" -frames:v 1 sheet.png
60
+ if ffmpeg -loglevel error -y -t 10 -i "$media" -vn -ac 1 -ar 16000 head.wav 2>/dev/null; then
61
+ # Local Whisper through the HyperFrames CLI: no API key. Multilingual model, Portuguese by default.
62
+ npx -y hyperframes transcribe head.wav -d . -m small -l "${WIZZ_IG_LANG:-pt}" --json >/dev/null 2>transcribe.log \
63
+ || echo "transcrição falhou (ver transcribe.log); siga pelos quadros e pela legenda" >&2
64
+ # Whisper invents words over music with no speech ("Thank you for watching", "Legendas pela
65
+ # comunidade"): stretched words or too few words per second mean there was no real speech.
66
+ [ -f transcript.json ] && node - <<'JS'
67
+ const fs = require('node:fs');
68
+ const raw = JSON.parse(fs.readFileSync('transcript.json', 'utf8'));
69
+ const words = Array.isArray(raw) ? raw : raw.words ?? [];
70
+ const span = words.length ? words.at(-1).end - words[0].start : 0;
71
+ const stretched = words.some((w) => w.end - w.start > 1.2);
72
+ const slow = span > 0 && words.length / span < 0.8;
73
+ const canned = /thank you for watching|obrigad[oa] por assistir|legendas? pela comunidade|inscreva-se/i.test(words.map((w) => w.text).join(' '));
74
+ const post = JSON.parse(fs.readFileSync('post.json', 'utf8'));
75
+ post.speech = words.length === 0 ? 'sem fala' : stretched || slow || canned ? 'suspeita' : 'ok';
76
+ fs.writeFileSync('post.json', JSON.stringify(post, null, 1));
77
+ if (post.speech !== 'ok') console.error(`transcrição ${post.speech}: provavelmente música sem fala; o gancho está nos quadros e no texto da tela`);
78
+ JS
79
+ else
80
+ echo "Reel sem áudio: o gancho está só no texto da tela (head.png)" >&2
81
+ fi
82
+ fi
83
+ echo "ok: $(pwd)/post.json ($surface)"
@@ -12,7 +12,7 @@
12
12
  > 仓库和已下载的缓存中)。用某个 SFX 前先 `ls assets/sfx/<category>/` 确认文件存在;
13
13
  > 缺失就跑 `npx --yes wizz-method fetch-assets --bundle huashu-audio --dest
14
14
  > assets`(路径相对本 skill 根目录)。离线时手动下载
15
- > `npx wizz-method fetch-assets --bundle huashu-audio` (2 partes em `https://wizz-assets.junior-aec.workers.dev/huashu-audio-1.tar.gz` e `.../huashu-audio-2.tar.gz`)
15
+ > `npx wizz-method fetch-assets --bundle huashu-audio` (2 partes em `https://assets.wizzcomms.com/huashu-audio-1.tar.gz` e `.../huashu-audio-2.tar.gz`)
16
16
  > 解压到 `assets/`,能力不受影响,只是首次用要等下载。
17
17
 
18
18
  ---
@@ -83,7 +83,7 @@ if [ ! -f "$MUSIC" ]; then
83
83
  echo "✗ Music not found: $MUSIC" >&2
84
84
  echo " Available moods: $(ls "$ASSETS_DIR" 2>/dev/null | grep -E '^bgm-.*\.mp3$' | sed 's/^bgm-//;s/\.mp3$//' | tr '\n' ' ')" >&2
85
85
  echo " Offline or npx failed? Download manually:" >&2
86
- echo " npx wizz-method fetch-assets --bundle huashu-audio (https://wizz-assets.junior-aec.workers.dev/huashu-audio-{1,2}.tar.gz)" >&2
86
+ echo " npx wizz-method fetch-assets --bundle huashu-audio (https://assets.wizzcomms.com/huashu-audio-{1,2}.tar.gz)" >&2
87
87
  echo " and extract it into: $ASSETS_DIR" >&2
88
88
  exit 1
89
89
  fi
@@ -913,43 +913,8 @@ function writeElement(slide, pres, el) {
913
913
  }
914
914
  }
915
915
 
916
- module.exports = async function html2pptx(htmlFile, pres, options) {
917
- options = options || {};
918
- const absHtmlPath = path.isAbsolute(htmlFile) ? htmlFile : path.resolve(process.cwd(), htmlFile);
919
- const tmpDir = options.tmpDir || process.env.TMPDIR || '/tmp';
920
-
921
- function finalizeError(rawMessage) {
922
- let message = rawMessage;
923
- if (!message.startsWith(htmlFile)) {
924
- message = `${htmlFile}: ${message}`;
925
- }
926
- return new Error(message);
927
- }
928
-
929
- let browser;
930
- let extraction;
931
- try {
932
- const launchOptions = { env: Object.assign({}, process.env, { TMPDIR: tmpDir }) };
933
- if (process.platform === 'darwin') launchOptions.channel = 'chrome';
934
- browser = await chromium.launch(launchOptions);
935
- const page = await browser.newPage();
936
- await page.goto('file://' + absHtmlPath);
937
-
938
- const initialBodySize = await page.evaluate(() => {
939
- const cs = getComputedStyle(document.body);
940
- return { width: parseFloat(cs.width), height: parseFloat(cs.height) };
941
- });
942
- await page.setViewportSize({
943
- width: Math.max(1, Math.round(initialBodySize.width)),
944
- height: Math.max(1, Math.round(initialBodySize.height)),
945
- });
946
-
947
- extraction = await page.evaluate(extractPageDataInBrowser);
948
- } finally {
949
- if (browser) await browser.close();
950
- }
951
-
952
- // ---- 整页级校验(第 5 节),和提取阶段收集到的错误合并 ----
916
+ // 整页级校验(第 5 节):和提取阶段收集到的错误合并,返回错误文案数组。纯函数,不碰浏览器。
917
+ function validatePage(extraction, pres) {
953
918
  const errors = extraction.errors.slice();
954
919
 
955
920
  const widthOverflowPx = Math.max(0, extraction.body.scrollWidth - extraction.body.width - 1);
@@ -989,6 +954,46 @@ module.exports = async function html2pptx(htmlFile, pres, options) {
989
954
  }
990
955
  }
991
956
  }
957
+ return errors;
958
+ }
959
+
960
+ module.exports = async function html2pptx(htmlFile, pres, options) {
961
+ options = options || {};
962
+ const absHtmlPath = path.isAbsolute(htmlFile) ? htmlFile : path.resolve(process.cwd(), htmlFile);
963
+ const tmpDir = options.tmpDir || process.env.TMPDIR || '/tmp';
964
+
965
+ function finalizeError(rawMessage) {
966
+ let message = rawMessage;
967
+ if (!message.startsWith(htmlFile)) {
968
+ message = `${htmlFile}: ${message}`;
969
+ }
970
+ return new Error(message);
971
+ }
972
+
973
+ let browser;
974
+ let extraction;
975
+ try {
976
+ const launchOptions = { env: Object.assign({}, process.env, { TMPDIR: tmpDir }) };
977
+ if (process.platform === 'darwin') launchOptions.channel = 'chrome';
978
+ browser = await chromium.launch(launchOptions);
979
+ const page = await browser.newPage();
980
+ await page.goto('file://' + absHtmlPath);
981
+
982
+ const initialBodySize = await page.evaluate(() => {
983
+ const cs = getComputedStyle(document.body);
984
+ return { width: parseFloat(cs.width), height: parseFloat(cs.height) };
985
+ });
986
+ await page.setViewportSize({
987
+ width: Math.max(1, Math.round(initialBodySize.width)),
988
+ height: Math.max(1, Math.round(initialBodySize.height)),
989
+ });
990
+
991
+ extraction = await page.evaluate(extractPageDataInBrowser);
992
+ } finally {
993
+ if (browser) await browser.close();
994
+ }
995
+
996
+ const errors = validatePage(extraction, pres);
992
997
 
993
998
  if (errors.length > 0) {
994
999
  let message;
@@ -1017,3 +1022,6 @@ module.exports = async function html2pptx(htmlFile, pres, options) {
1017
1022
 
1018
1023
  return { slide, placeholders: extraction.placeholders };
1019
1024
  };
1025
+
1026
+ // Expostos só para teste (funções puras, sem browser).
1027
+ module.exports._internals = { toPt, toIn, stripFileProtocol, previewTextOf, writeElement, validatePage };
@@ -80,7 +80,7 @@ if [ -n "$BGM" ] && [ ! -f "$BGM" ]; then
80
80
  echo "✗ BGM 文件不存在: $BGM" >&2
81
81
  echo " 可用 mood: $(ls "$ASSETS_DIR" 2>/dev/null | grep -E '^bgm-.*\.mp3$' | sed 's/^bgm-//;s/\.mp3$//' | tr '\n' ' ')" >&2
82
82
  echo " 离线,或 npx 失败?手动下载:" >&2
83
- echo " npx wizz-method fetch-assets --bundle huashu-audio (https://wizz-assets.junior-aec.workers.dev/huashu-audio-{1,2}.tar.gz)" >&2
83
+ echo " npx wizz-method fetch-assets --bundle huashu-audio (https://assets.wizzcomms.com/huashu-audio-{1,2}.tar.gz)" >&2
84
84
  echo " 解压到:$ASSETS_DIR" >&2
85
85
  exit 1
86
86
  fi
@@ -308,8 +308,14 @@ async function main() {
308
308
  console.error(` cue 数: ${totalCues}`);
309
309
  }
310
310
 
311
- main().catch((err) => {
312
- console.error(`narrate-pipeline 失败:${err.message}`);
313
- console.error(err.stack);
314
- process.exit(1);
315
- });
311
+ // Só executa quando chamado como script; importar o módulo (testes) não dispara o pipeline.
312
+ const isMain = process.argv[1] && fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
313
+ if (isMain) {
314
+ main().catch((err) => {
315
+ console.error(`narrate-pipeline 失败:${err.message}`);
316
+ console.error(err.stack);
317
+ process.exit(1);
318
+ });
319
+ }
320
+
321
+ export { parseArgs, parseScript, splitByCues };