wizz-method 2.18.0 → 2.19.1

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 (166) hide show
  1. package/package.json +5 -3
  2. package/removals.txt +10 -0
  3. package/skills-registry.yaml +60 -39
  4. package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +9 -9
  5. package/src/bmm-skills/2-plan-workflows/wizz-ux/references/creative-tools.md +2 -0
  6. package/src/bmm-skills/2-plan-workflows/wizz-ux/references/design-md-spec.md +2 -0
  7. package/src/bmm-skills/4-implementation/wizz-create-story/SKILL.md +1 -1
  8. package/src/bmm-skills/4-implementation/wizz-dev-story/SKILL.md +1 -1
  9. package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/SKILL.md +1 -1
  10. package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/checklist.md +1 -1
  11. package/src/bmm-skills/4-implementation/wizz-quick-dev/SKILL.md +1 -1
  12. package/src/bmm-skills/4-implementation/wizz-sprint-status/SKILL.md +5 -5
  13. package/src/bmm-skills/module-help.csv +1 -1
  14. package/src/bmm-skills/module.yaml +1 -1
  15. package/src/core-skills/_shared/handoff-protocol.md +2 -2
  16. package/src/modules/lowticket/skills/wizz-lowticket-funil/SKILL.md +1 -1
  17. package/src/modules/lowticket/skills/wizz-lowticket-guia/SKILL.md +1 -1
  18. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/SKILL.md +1 -1
  19. package/src/modules/lowticket/skills/wizz-lowticket-minerador/SKILL.md +1 -1
  20. package/src/modules/lowticket/skills/wizz-lowticket-pagina/SKILL.md +1 -1
  21. package/src/modules/lowticket/skills/wizz-lowticket-trafego/SKILL.md +1 -1
  22. package/src/modules/wizz/_shared/cerebro.md +7 -7
  23. package/src/modules/wizz/_shared/communication-rules.md +1 -1
  24. package/src/modules/wizz/_shared/encerramento.md +4 -3
  25. package/src/modules/wizz/_shared/organizacao.md +8 -4
  26. package/src/modules/wizz/_shared/token-economy.md +1 -1
  27. package/src/modules/wizz/_shared/video-pipeline.md +4 -4
  28. package/src/modules/wizz/agents/wizz-ads/SKILL.md +4 -1
  29. package/src/modules/wizz/agents/wizz-ads/customize.toml +12 -3
  30. package/src/modules/wizz/agents/wizz-ads/knowledge/playbook.md +36 -0
  31. package/src/modules/wizz/agents/wizz-copy/SKILL.md +4 -1
  32. package/src/modules/wizz/agents/wizz-copy/customize.toml +2 -3
  33. package/src/modules/wizz/agents/wizz-copy/knowledge/playbook.md +34 -0
  34. package/src/modules/wizz/agents/wizz-designer/SKILL.md +2 -2
  35. package/src/modules/wizz/agents/wizz-designer/customize.toml +2 -3
  36. package/src/modules/wizz/agents/wizz-growth/SKILL.md +4 -1
  37. package/src/modules/wizz/agents/wizz-growth/customize.toml +33 -4
  38. package/src/modules/wizz/agents/wizz-growth/knowledge/playbook.md +39 -0
  39. package/src/modules/wizz/agents/wizz-maestro/SKILL.md +6 -8
  40. package/src/modules/wizz/agents/wizz-maestro/customize.toml +2 -3
  41. package/src/modules/wizz/agents/wizz-memoria/SKILL.md +7 -7
  42. package/src/modules/wizz/agents/wizz-memoria/customize.toml +5 -6
  43. package/src/modules/wizz/agents/wizz-qa/SKILL.md +5 -6
  44. package/src/modules/wizz/agents/wizz-qa/customize.toml +3 -4
  45. package/src/modules/wizz/agents/wizz-seo/SKILL.md +4 -1
  46. package/src/modules/wizz/agents/wizz-seo/customize.toml +2 -3
  47. package/src/modules/wizz/agents/wizz-seo/knowledge/playbook.md +31 -0
  48. package/src/modules/wizz/agents/wizz-social/SKILL.md +3 -3
  49. package/src/modules/wizz/agents/wizz-social/customize.toml +2 -3
  50. package/src/modules/wizz/overrides/wizz-agent-analyst.toml +2 -2
  51. package/src/modules/wizz/overrides/wizz-agent-architect.toml +3 -3
  52. package/src/modules/wizz/overrides/wizz-agent-dev.toml +3 -3
  53. package/src/modules/wizz/overrides/wizz-agent-pm.toml +2 -2
  54. package/src/modules/wizz/overrides/wizz-agent-tech-writer.toml +2 -2
  55. package/src/modules/wizz/overrides/wizz-agent-ux-designer.toml +2 -2
  56. package/src/skills-lib/_shared/complexity-signal.md +8 -9
  57. package/src/skills-lib/_shared/dispatch-rule.yaml +21 -0
  58. package/src/skills-lib/{cerebro → wizz-cerebro}/SKILL.md +1 -1
  59. package/src/skills-lib/{ctc-align → wizz-ctc-align}/SKILL.md +1 -1
  60. package/src/skills-lib/wizz-decision-maker/references/output-format.md +21 -0
  61. package/src/skills-lib/wizz-decision-maker/references/references-and-extraction.md +32 -1
  62. package/src/skills-lib/wizz-design-needs/references/design-state.md +1 -1
  63. package/src/skills-lib/wizz-design-needs/references/gap-analysis.md +3 -1
  64. package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/SKILL.md +1 -1
  65. package/src/skills-lib/{find-skills → wizz-find-skills}/SKILL.md +1 -1
  66. package/src/skills-lib/wizz-huashu-design/references/sfx-library.md +1 -1
  67. package/src/skills-lib/wizz-huashu-design/scripts/add-music.sh +1 -1
  68. package/src/skills-lib/wizz-huashu-design/scripts/html2pptx.js +45 -37
  69. package/src/skills-lib/wizz-huashu-design/scripts/mix-voiceover.sh +1 -1
  70. package/src/skills-lib/wizz-huashu-design/scripts/narrate-pipeline.mjs +11 -5
  71. package/src/skills-lib/wizz-huashu-design/scripts/test_scripts.py +209 -0
  72. package/src/skills-lib/wizz-huashu-design/scripts/tts-doubao.mjs +42 -28
  73. package/src/skills-lib/wizz-ig-profile-optimizer/SKILL.md +1 -1
  74. package/src/skills-lib/wizz-inicio-de-projeto/SKILL.md +1 -1
  75. package/src/skills-lib/wizz-launch-readiness/SKILL.md +7 -7
  76. package/src/skills-lib/wizz-launch-readiness/references/02-seguranca.md +3 -3
  77. package/src/skills-lib/wizz-launch-readiness/references/03-seo-descoberta.md +5 -5
  78. package/src/skills-lib/wizz-launch-readiness/references/04-analytics-medicao.md +1 -1
  79. package/src/skills-lib/wizz-launch-readiness/references/05-conteudo-prova-social.md +6 -6
  80. package/src/skills-lib/wizz-launch-readiness/references/06-legal-lgpd.md +1 -1
  81. package/src/skills-lib/wizz-launch-readiness/references/08-fusao-priorizacao.md +1 -1
  82. package/src/skills-lib/wizz-offer-forge/SKILL.md +3 -3
  83. package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/README.md +1 -1
  84. package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/SKILL.md +1 -1
  85. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/component-sources.md +23 -23
  86. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-first-protocol.md +15 -3
  87. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-links.md +14 -8
  88. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/web-access-and-evidence.md +4 -3
  89. package/src/skills-lib/wizz-router/SKILL.md +3 -3
  90. package/src/skills-lib/wizz-router/references/routing-table-flat.md +10 -9
  91. package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/SKILL.md +1 -1
  92. package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/SKILL.md +1 -1
  93. package/src/skills-lib/wizz-ui-component-curator/SKILL.md +1 -1
  94. package/src/skills-lib/wizz-video-effects/SKILL.md +3 -3
  95. package/src/skills-lib/wizz-video-effects/references/data-contract.md +3 -3
  96. package/src/skills-lib/wizz-video-effects/scripts/fill_breaths.py +15 -1
  97. package/src/skills-lib/wizz-video-effects/scripts/test_fill_breaths.py +20 -0
  98. package/src/skills-lib/wizz-video-styles/SKILL.md +2 -2
  99. package/src/skills-lib/wizz-video-styles/data/recipes.json +32 -1
  100. package/src/skills-lib/wizz-video-styles/data/references.json +211 -1
  101. package/src/skills-lib/wizz-video-styles/data/styles.json +54 -0
  102. package/src/skills-lib/wizz-video-styles/references/generated-prompts.md +13 -3
  103. package/src/skills-lib/wizz-video-styles/references/style-picker.md +4 -2
  104. package/src/skills-lib/wizz-video-styles/references/styles/ui-morph.md +68 -0
  105. package/src/skills-lib/wizz-video-styles/scripts/analyze.sh +21 -9
  106. package/src/skills-lib/wizz-video-styles/scripts/references.mjs +1 -0
  107. package/src/skills-lib/wizz-web-security/SKILL.md +1 -1
  108. package/tools/fetch-assets.mjs +1 -1
  109. package/tools/hooks/README.md +18 -14
  110. package/tools/hooks/rtk-rewrite.js +95 -0
  111. package/tools/hooks/security-defensive-context.js +17 -1
  112. package/tools/hooks/session-rules.js +25 -5
  113. package/tools/hooks/wizz-arrumar.js +126 -12
  114. package/tools/hooks/wizz-artifact-guard.js +41 -7
  115. package/tools/hooks/wizz-cerebro-lib.js +239 -0
  116. package/tools/hooks/wizz-close-reminder.js +124 -0
  117. package/tools/hooks/wizz-debug-lib.js +55 -0
  118. package/tools/hooks/wizz-decision-trace.js +19 -2
  119. package/tools/hooks/wizz-feature.js +20 -4
  120. package/tools/hooks/wizz-router-enforce.js +30 -13
  121. package/tools/installer/command-text.js +243 -0
  122. package/tools/installer/commands/catalog.js +2 -0
  123. package/tools/installer/commands/doctor.js +10 -12
  124. package/tools/installer/commands/fetch-assets.js +5 -5
  125. package/tools/installer/commands/install.js +6 -7
  126. package/tools/installer/commands/resolve.js +17 -17
  127. package/tools/installer/commands/trace-report.js +40 -37
  128. package/tools/installer/core/doctor.js +60 -55
  129. package/tools/installer/core/hooks-settings.js +88 -14
  130. package/tools/installer/core/hooks-smoke.js +134 -0
  131. package/tools/installer/core/installer.js +61 -52
  132. package/tools/installer/core/quick-update.js +2 -3
  133. package/tools/installer/guided/labels.js +5 -0
  134. package/tools/installer/install-text.js +201 -0
  135. package/tools/installer/modules/channel-plan.js +2 -2
  136. package/tools/installer/modules/env-vars.js +5 -6
  137. package/tools/installer/modules/skills-lib.js +1 -1
  138. package/tools/installer/ui.js +17 -9
  139. /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-pesquisa-e-memoria.md +0 -0
  140. /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-rotina-e-captura.md +0 -0
  141. /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-sessao.md +0 -0
  142. /package/src/skills-lib/{ctc-align → wizz-ctc-align}/scripts/align.py +0 -0
  143. /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/README.md +0 -0
  144. /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/references/KEYWORDS.md +0 -0
  145. /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/assets/template.html +0 -0
  146. /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/prompts.md +0 -0
  147. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-1-validacao-entrada.md +0 -0
  148. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-2-autorizacao.md +0 -0
  149. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-3-abuso-volume.md +0 -0
  150. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-4-vazamento-resposta.md +0 -0
  151. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-5-fusao-plano.md +0 -0
  152. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/01-cta-principal.md +0 -0
  153. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/02-barra-fixa-mobile.md +0 -0
  154. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/03-tempo-de-resposta.md +0 -0
  155. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/04-prova-social.md +0 -0
  156. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/05-faq-decisao.md +0 -0
  157. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/06-imagens-reais.md +0 -0
  158. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/07-endereco-como-chegar.md +0 -0
  159. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/08-titles.md +0 -0
  160. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/09-open-graph.md +0 -0
  161. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/10-breadcrumbs.md +0 -0
  162. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/11-alt-text.md +0 -0
  163. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/12-schema-local.md +0 -0
  164. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/13-robots-sitemap.md +0 -0
  165. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/14-privacidade-termos.md +0 -0
  166. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/15-medicao.md +0 -0
@@ -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
 
@@ -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 };
@@ -0,0 +1,209 @@
1
+ """Testes (unittest, stdlib) das partes puras de fetch_images.py e verify.py.
2
+
3
+ Rede e browser ficam de fora: a API da Wikimedia e o download são trocados por
4
+ mocks. Rodam via `python3 -m unittest discover` (test/test-python-skills.js).
5
+ """
6
+ import contextlib
7
+ import io
8
+ import os
9
+ import sys
10
+ import tempfile
11
+ import unittest
12
+ from unittest import mock
13
+
14
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
15
+
16
+ import fetch_images # noqa: E402
17
+ import verify # noqa: E402
18
+
19
+
20
+ class FakeResponse:
21
+ """Resposta mínima de urlopen usada como context manager."""
22
+
23
+ def __init__(self, payload=b"IMG"):
24
+ self._payload = payload
25
+
26
+ def __enter__(self):
27
+ return self
28
+
29
+ def __exit__(self, *exc):
30
+ return False
31
+
32
+ def read(self):
33
+ return self._payload
34
+
35
+
36
+ def page(title, thumb=None, url=None, lic="CC BY-SA 4.0", artist="<a href='x'>Ana</a> Silva"):
37
+ info = {
38
+ "descriptionurl": "https://commons.example/" + title,
39
+ "extmetadata": {"LicenseShortName": {"value": lic}, "Artist": {"value": artist}},
40
+ }
41
+ if thumb:
42
+ info["thumburl"] = thumb
43
+ if url:
44
+ info["url"] = url
45
+ return {"title": title, "imageinfo": [info]}
46
+
47
+
48
+ class SafeNameTest(unittest.TestCase):
49
+ def test_replaces_unsafe_chars(self):
50
+ self.assertEqual(fetch_images._safe("Petronas Towers/KL"), "Petronas_Towers_KL")
51
+
52
+ def test_keeps_word_chars_dash_dot(self):
53
+ self.assertEqual(fetch_images._safe("a-b.c_d"), "a-b.c_d")
54
+
55
+ def test_truncates_to_60(self):
56
+ self.assertEqual(len(fetch_images._safe("x" * 200)), 60)
57
+
58
+
59
+ class FetchTest(unittest.TestCase):
60
+ def setUp(self):
61
+ self.tmp = tempfile.TemporaryDirectory()
62
+ self.addCleanup(self.tmp.cleanup)
63
+ self.out = self.tmp.name
64
+
65
+ def run_fetch(self, pages, query="Langkawi beach", count=2, width=1600, urlopen=None):
66
+ data = {"query": {"pages": pages}}
67
+ stack = contextlib.ExitStack()
68
+ stack.enter_context(mock.patch.object(fetch_images, "_api_get", return_value=data))
69
+ opener = urlopen or (lambda req, timeout=0: FakeResponse())
70
+ stack.enter_context(mock.patch.object(fetch_images.urllib.request, "urlopen", side_effect=opener))
71
+ with stack:
72
+ buf = io.StringIO()
73
+ with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(io.StringIO()):
74
+ got = fetch_images.fetch(query, self.out, count, width)
75
+ return got, buf.getvalue()
76
+
77
+ def test_downloads_thumb_and_prints_license_and_clean_artist(self):
78
+ pages = {"1": page("File:Beach one.jpg", thumb="https://up.example/t/beach.jpg?x=1")}
79
+ got, out = self.run_fetch(pages)
80
+ self.assertEqual(len(got), 1)
81
+ self.assertTrue(os.path.exists(got[0]))
82
+ self.assertTrue(got[0].startswith(self.out))
83
+ self.assertTrue(got[0].endswith(".jpg"), "extensão vem da URL sem a query string")
84
+ self.assertIn("CC BY-SA 4.0", out)
85
+ self.assertIn("Ana Silva", out)
86
+ self.assertNotIn("<a", out, "tags HTML do autor são removidas")
87
+ self.assertIn("https://commons.example/File:Beach one.jpg", out)
88
+
89
+ def test_filename_has_query_and_title_without_file_prefix(self):
90
+ got, _ = self.run_fetch({"1": page("File:Nice view.png", thumb="https://up.example/x.png")}, query="Petronas Towers")
91
+ name = os.path.basename(got[0])
92
+ self.assertTrue(name.startswith("Petronas_Towers_"), name)
93
+ self.assertNotIn("File_", name)
94
+ self.assertTrue(name.endswith(".png"))
95
+
96
+ def test_falls_back_to_url_when_no_thumb_and_default_ext_jpg(self):
97
+ got, _ = self.run_fetch({"1": page("File:a", url="https://up.example/noext")})
98
+ self.assertEqual(len(got), 1)
99
+ self.assertTrue(got[0].endswith(".jpg"))
100
+
101
+ def test_skips_pages_without_any_image_url(self):
102
+ got, _ = self.run_fetch({"1": page("File:nada")})
103
+ self.assertEqual(got, [])
104
+
105
+ def test_respects_count(self):
106
+ pages = {str(i): page(f"File:img{i}.jpg", thumb=f"https://up.example/{i}.jpg") for i in range(5)}
107
+ got, _ = self.run_fetch(pages, count=2)
108
+ self.assertEqual(len(got), 2)
109
+
110
+ def test_missing_metadata_uses_question_mark(self):
111
+ bare = {"title": "File:x.jpg", "imageinfo": [{"thumburl": "https://up.example/x.jpg"}]}
112
+ _, out = self.run_fetch({"1": bare})
113
+ self.assertIn("| ? | ?", out)
114
+
115
+ def test_api_failure_returns_empty_list(self):
116
+ with mock.patch.object(fetch_images, "_api_get", side_effect=OSError("boom")):
117
+ with contextlib.redirect_stderr(io.StringIO()) as err:
118
+ got = fetch_images.fetch("q", self.out, 2, 800)
119
+ self.assertEqual(got, [])
120
+ self.assertIn("FAIL search", err.getvalue())
121
+
122
+ def test_no_pages_returns_empty_and_warns(self):
123
+ with mock.patch.object(fetch_images, "_api_get", return_value={"query": {}}):
124
+ with contextlib.redirect_stderr(io.StringIO()) as err:
125
+ got = fetch_images.fetch("q", self.out, 2, 800)
126
+ self.assertEqual(got, [])
127
+ self.assertIn("EMPTY", err.getvalue())
128
+
129
+ def test_download_failure_is_skipped_not_fatal(self):
130
+ def opener(req, timeout=0):
131
+ raise OSError("download caiu")
132
+
133
+ got, _ = self.run_fetch({"1": page("File:a.jpg", thumb="https://up.example/a.jpg")}, urlopen=opener)
134
+ self.assertEqual(got, [])
135
+
136
+ def test_requests_send_compliant_user_agent_and_width(self):
137
+ seen = {}
138
+
139
+ def fake_api(params):
140
+ seen.update(params)
141
+ return {"query": {"pages": {}}}
142
+
143
+ with mock.patch.object(fetch_images, "_api_get", side_effect=fake_api):
144
+ with contextlib.redirect_stderr(io.StringIO()):
145
+ fetch_images.fetch("Georgetown", self.out, 3, 1200)
146
+ self.assertEqual(seen["gsrsearch"], "Georgetown")
147
+ self.assertEqual(seen["gsrlimit"], 3)
148
+ self.assertEqual(seen["iiurlwidth"], 1200)
149
+ self.assertEqual(seen["gsrnamespace"], 6)
150
+ self.assertIn("huashu-design", fetch_images.UA)
151
+
152
+ def test_module_clears_proxy_env(self):
153
+ for key in ("ALL_PROXY", "HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy", "all_proxy"):
154
+ self.assertNotIn(key, os.environ)
155
+
156
+
157
+ class MainTest(unittest.TestCase):
158
+ def run_main(self, argv, fetch_result):
159
+ with mock.patch.object(sys, "argv", ["fetch_images.py", *argv]):
160
+ with mock.patch.object(fetch_images, "fetch", return_value=fetch_result) as fetch:
161
+ with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
162
+ try:
163
+ fetch_images.main()
164
+ code = None
165
+ except SystemExit as exc:
166
+ code = exc.code
167
+ return code, fetch
168
+
169
+ def test_exit_1_when_nothing_downloaded(self):
170
+ with tempfile.TemporaryDirectory() as tmp:
171
+ code, _ = self.run_main(["--query", "a", "b", "--out", os.path.join(tmp, "img")], [])
172
+ self.assertEqual(code, 1)
173
+
174
+ def test_success_creates_out_dir_and_passes_defaults(self):
175
+ with tempfile.TemporaryDirectory() as tmp:
176
+ out = os.path.join(tmp, "novo", "img")
177
+ code, fetch = self.run_main(["--query", "a", "b", "--out", out], ["f.jpg"])
178
+ self.assertIsNone(code)
179
+ self.assertTrue(os.path.isdir(out))
180
+ self.assertEqual(fetch.call_count, 2, "um fetch por query")
181
+ fetch.assert_any_call("a", out, 2, 1600)
182
+
183
+ def test_custom_count_and_width(self):
184
+ with tempfile.TemporaryDirectory() as tmp:
185
+ out = os.path.join(tmp, "img")
186
+ _, fetch = self.run_main(["--query", "x", "--out", out, "--count", "4", "--width", "800"], ["f"])
187
+ fetch.assert_called_once_with("x", out, 4, 800)
188
+
189
+ def test_requires_query_and_out(self):
190
+ with mock.patch.object(sys, "argv", ["fetch_images.py"]):
191
+ with contextlib.redirect_stderr(io.StringIO()):
192
+ with self.assertRaises(SystemExit) as ctx:
193
+ fetch_images.main()
194
+ self.assertEqual(ctx.exception.code, 2)
195
+
196
+
197
+ class ParseViewportTest(unittest.TestCase):
198
+ def test_parses_width_x_height(self):
199
+ self.assertEqual(verify.parse_viewport("1440x900"), {"width": 1440, "height": 900})
200
+ self.assertEqual(verify.parse_viewport("375x667"), {"width": 375, "height": 667})
201
+
202
+ def test_rejects_malformed(self):
203
+ for bad in ("1440", "axb", "1x2x3", ""):
204
+ with self.assertRaises(ValueError, msg=bad):
205
+ verify.parse_viewport(bad)
206
+
207
+
208
+ if __name__ == "__main__":
209
+ unittest.main()
@@ -28,8 +28,7 @@ import { randomUUID } from 'node:crypto';
28
28
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
29
29
  const SKILL_ROOT = path.resolve(__dirname, '..');
30
30
 
31
- function loadEnv() {
32
- const envPath = path.join(SKILL_ROOT, '.env');
31
+ function loadEnv(envPath = path.join(SKILL_ROOT, '.env')) {
33
32
  if (!fs.existsSync(envPath)) return;
34
33
  const text = fs.readFileSync(envPath, 'utf8');
35
34
  for (const line of text.split('\n')) {
@@ -45,7 +44,6 @@ function loadEnv() {
45
44
  if (!(key in process.env)) process.env[key] = val;
46
45
  }
47
46
  }
48
- loadEnv();
49
47
 
50
48
  function parseArgs(argv) {
51
49
  const args = { speed: '1.0', encoding: 'mp3' };
@@ -90,16 +88,9 @@ function getDuration(filePath) {
90
88
  }
91
89
  }
92
90
 
93
- async function tts({ text, voice, speed, encoding }) {
94
- const apiKey = process.env.DOUBAO_TTS_API_KEY;
95
- const cluster = process.env.DOUBAO_TTS_CLUSTER || 'volcano_icl';
96
- const endpoint = process.env.DOUBAO_TTS_ENDPOINT || 'https://openspeech.bytedance.com/api/v1/tts';
97
- const voiceId = voice || process.env.DOUBAO_TTS_VOICE_ID;
98
-
99
- if (!apiKey) throw new Error('缺 DOUBAO_TTS_API_KEY(检查 .env)');
100
- if (!voiceId) throw new Error('缺 DOUBAO_TTS_VOICE_ID(检查 .env 或用 --voice 传)');
101
-
102
- const body = {
91
+ /** Corpo da requisição à API do Doubao (puro, sem rede). */
92
+ function buildTtsBody({ cluster, voiceId, encoding, speed, text, reqid = randomUUID() }) {
93
+ return {
103
94
  app: { cluster },
104
95
  user: { uid: 'wizz-huashu-design' },
105
96
  audio: {
@@ -108,11 +99,36 @@ async function tts({ text, voice, speed, encoding }) {
108
99
  speed_ratio: parseFloat(speed),
109
100
  },
110
101
  request: {
111
- reqid: randomUUID(),
102
+ reqid,
112
103
  text,
113
104
  operation: 'query',
114
105
  },
115
106
  };
107
+ }
108
+
109
+ /** Valida a resposta JSON do Doubao e devolve o áudio decodificado (Buffer). */
110
+ function parseTtsResponse(json) {
111
+ // 豆包标准返回:{ code, message, data: "<base64 audio>", ... }
112
+ // code === 3000 表示成功
113
+ if (json.code !== undefined && json.code !== 3000) {
114
+ throw new Error(`API 返回错误 code=${json.code} msg=${json.message || JSON.stringify(json)}`);
115
+ }
116
+ if (!json.data) {
117
+ throw new Error(`API 响应无 data 字段:${JSON.stringify(json).slice(0, 500)}`);
118
+ }
119
+ return Buffer.from(json.data, 'base64');
120
+ }
121
+
122
+ async function tts({ text, voice, speed, encoding }) {
123
+ const apiKey = process.env.DOUBAO_TTS_API_KEY;
124
+ const cluster = process.env.DOUBAO_TTS_CLUSTER || 'volcano_icl';
125
+ const endpoint = process.env.DOUBAO_TTS_ENDPOINT || 'https://openspeech.bytedance.com/api/v1/tts';
126
+ const voiceId = voice || process.env.DOUBAO_TTS_VOICE_ID;
127
+
128
+ if (!apiKey) throw new Error('缺 DOUBAO_TTS_API_KEY(检查 .env)');
129
+ if (!voiceId) throw new Error('缺 DOUBAO_TTS_VOICE_ID(检查 .env 或用 --voice 传)');
130
+
131
+ const body = buildTtsBody({ cluster, voiceId, encoding, speed, text });
116
132
 
117
133
  const res = await fetch(endpoint, {
118
134
  method: 'POST',
@@ -128,16 +144,7 @@ async function tts({ text, voice, speed, encoding }) {
128
144
  throw new Error(`HTTP ${res.status}: ${errText.slice(0, 500)}`);
129
145
  }
130
146
 
131
- const json = await res.json();
132
- // 豆包标准返回:{ code, message, data: "<base64 audio>", ... }
133
- // code === 3000 表示成功
134
- if (json.code !== undefined && json.code !== 3000) {
135
- throw new Error(`API 返回错误 code=${json.code} msg=${json.message || JSON.stringify(json)}`);
136
- }
137
- if (!json.data) {
138
- throw new Error(`API 响应无 data 字段:${JSON.stringify(json).slice(0, 500)}`);
139
- }
140
- return Buffer.from(json.data, 'base64');
147
+ return parseTtsResponse(await res.json());
141
148
  }
142
149
 
143
150
  async function main() {
@@ -178,7 +185,14 @@ async function main() {
178
185
  console.log(JSON.stringify(result));
179
186
  }
180
187
 
181
- main().catch((err) => {
182
- console.error(`TTS 失败:${err.message}`);
183
- process.exit(1);
184
- });
188
+ // Só executa quando chamado como script; importar o módulo (testes) não lê .env nem chama a API.
189
+ const isMain = process.argv[1] && fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
190
+ if (isMain) {
191
+ loadEnv();
192
+ main().catch((err) => {
193
+ console.error(`TTS 失败:${err.message}`);
194
+ process.exit(1);
195
+ });
196
+ }
197
+
198
+ export { parseArgs, loadEnv, buildTtsBody, parseTtsResponse };
@@ -18,7 +18,7 @@ All text delivered is Brazilian Portuguese with "você", and no em dash in anyth
18
18
 
19
19
  1. **Read the profile.** `scripts/capture-profile.sh <@> <out_dir>` (out_dir under `_wizz-output/`) saves `profile.png` and `profile.json` (NAME, bio and link as text, follower and post counts). **Open the image.** Logged out, Instagram may hide highlights, the category label and the pin marks: ask for a screenshot of those parts instead of guessing them.
20
20
  2. **Pull what the Method already knows**, before judging:
21
- - Positioning, audience and tone from the project's Cérebro (`/cerebro ver`).
21
+ - Positioning, audience and tone from the project's Cérebro (`/wizz-cerebro ver`).
22
22
  - Highlights already made in the project (for example a `posts/highlights/` folder).
23
23
  - The offer from `wizz-offer-forge`, when one exists: the link and one pin depend on it.
24
24
  - Real performance: with Buffer connected, `get_aggregated_post_metrics` and the post list give the best posts by saves, shares and reach. Without it, ask which posts did best; never pick pins by guess.
@@ -87,7 +87,7 @@ Before the first file of a new project, and on the first use of the method in an
87
87
 
88
88
  1. `node _wizz/hooks/wizz-arrumar.js layout` proposes it from what already exists (types: `codigo`, `scripts`, `dados`, `docs`, `midia`, `arquivo`; a type mapped to `.` lives at the root, e.g. a Next.js app at the root).
89
89
  2. Show the proposal to the owner in **one line** and record it with `node _wizz/hooks/wizz-arrumar.js layout --gravar [tipo=pasta ...]`. It creates each new folder with a short README.
90
- 3. An existing project with loose files at the root: `wizz-arrumar.js planejar` writes a `.tsv` manifest; review it and run `wizz-arrumar.js aplicar <manifesto> --testes "<test command>" --refs <auto-memory dir>`. It moves, rewrites references in code, docs and memories, runs the tests before and after, and writes `desfazer.sh`.
90
+ 3. An existing project with loose files at the root: `wizz-arrumar.js planejar` writes a `.tsv` manifest; review it and run `wizz-arrumar.js aplicar <manifesto> --testes "<test command>" --refs <auto-memory dir>`. It moves, rewrites references in code, docs and memories, runs the tests before and after, and writes an undo: `wizz-arrumar.js desfazer <run folder>` on any OS, or the `desfazer.sh` (`desfazer.cmd` on Windows) shortcut. `--testes` accepts only known test runners (npm/pnpm/yarn/bun test, npx vitest|jest, pytest, go/cargo/make test); any other command needs `--testes-livre`.
91
91
 
92
92
  From then on the `wizz-artifact-guard.js` hook points out any file born loose at the root and the command that moves it. Full duty in the wizz module's `_shared/organizacao.md`.
93
93
 
@@ -8,8 +8,8 @@ description: >
8
8
  ready to launch?", "pre-launch audit", "broad release checklist", "what's left
9
9
  before going to production", considering the whole project (not just the site). Output: a diagnosis
10
10
  prioritized by severity (red blocks launch, orange high, yellow medium, green ok), never
11
- blind fixing. Different from security-audit-pentest (hunts vulnerabilities with proof of exploitation)
12
- and site-launch-kit (executes the 15 rounds for ONE site): this DIAGNOSES the whole project and points to
11
+ blind fixing. Different from wizz-security-audit-pentest (hunts vulnerabilities with proof of exploitation)
12
+ and wizz-site-launch-kit (executes the 15 rounds for ONE site): this DIAGNOSES the whole project and points to
13
13
  the right fix owner per finding. Persists the result in a dated section of project-context.md.
14
14
  ---
15
15
 
@@ -17,7 +17,7 @@ description: >
17
17
 
18
18
  Seven checking areas, each a closed pass: single scope, standardized output, its own cutoff rule. At the end, a merge step consolidates everything into a prioritized diagnosis and writes the summary to the project's `project-context.md`.
19
19
 
20
- This is not a build skill (that's `wizz-premium-landing-ui-researcher` / `wizz-taste-skill` / `wizz-agent-dev`), not the vulnerability-hunting methodology (that's `security-audit-pentest`), and it doesn't execute the 15 corrective rounds for a site (that's `site-launch-kit`): this is a broad, prioritized diagnosis to decide whether it's ready to launch.
20
+ This is not a build skill (that's `wizz-premium-landing-ui-researcher` / `wizz-taste-skill` / `wizz-agent-dev`), not the vulnerability-hunting methodology (that's `wizz-security-audit-pentest`), and it doesn't execute the 15 corrective rounds for a site (that's `wizz-site-launch-kit`): this is a broad, prioritized diagnosis to decide whether it's ready to launch.
21
21
 
22
22
  ## Stage gate (read BEFORE running)
23
23
 
@@ -25,7 +25,7 @@ Read the project's stage: `grep -m1 '^stage:' {project-root}/**/project-context.
25
25
 
26
26
  - **`mvp` or `production`:** run the full audit. This is exactly what the skill exists for.
27
27
  - **`prototype` or no `project-context.md` file:** stop before running. Reply that the project is still too early for a launch audit (little or nothing built yet to audit) and point to the right path for this stage: `wizz-inicio-de-projeto`, `wizz-forge-idea`, or `wizz-decision-maker` to define the product first. Only proceed if the user explicitly insists, and in that case warn that the findings will come back full of PENDING ITEMS due to lack of built surface.
28
- - **`maintenance`:** normally not the use case (product already launched, running). Only makes sense if the request is about a NEW batch starting from scratch (a big feature, a new product within the same repo): confirm this before running; otherwise, point to `security-audit-pentest` (periodic pentest) or the `wizz-router`'s "360° Audit" mode (broad audit of a codebase already in production, not about a launch event).
28
+ - **`maintenance`:** normally not the use case (product already launched, running). Only makes sense if the request is about a NEW batch starting from scratch (a big feature, a new product within the same repo): confirm this before running; otherwise, point to `wizz-security-audit-pentest` (periodic pentest) or the `wizz-router`'s "360° Audit" mode (broad audit of a codebase already in production, not about a launch event).
29
29
 
30
30
  ## Common contract (applies to all 7 areas)
31
31
 
@@ -81,8 +81,8 @@ After the 7:
81
81
 
82
82
  ## Relationship with other skills
83
83
 
84
- - **`security-audit-pentest`**: security deep dive. Area 02 here is a light sweep (exposed surface); findings that need proof of exploitation ("how it's exploited") go to `security-audit-pentest`, they aren't redone here.
85
- - **`site-launch-kit`**: when the surface of the finding is a SITE (CTA, social proof, Open Graph, local schema, robots/sitemap, site LGPD, measurement), the fix owner is `site-launch-kit`. This skill doesn't repeat the 15 rounds: it only points to which round resolves the finding.
84
+ - **`wizz-security-audit-pentest`**: security deep dive. Area 02 here is a light sweep (exposed surface); findings that need proof of exploitation ("how it's exploited") go to `wizz-security-audit-pentest`, they aren't redone here.
85
+ - **`wizz-site-launch-kit`**: when the surface of the finding is a SITE (CTA, social proof, Open Graph, local schema, robots/sitemap, site LGPD, measurement), the fix owner is `wizz-site-launch-kit`. This skill doesn't repeat the 15 rounds: it only points to which round resolves the finding.
86
86
  - **`wizz-seo-audit`**: broad ranking/traffic/Core Web Vitals diagnosis. Area 03 here is only basic indexability (can the site be found), not a full SEO audit.
87
87
  - **`wizz-router`'s "360° Audit" mode**: a broad audit of a project already in production, by technical area (code, database, design, growth...), without the "we're about to launch" framing or the stage gate. Use wizz-launch-readiness for the launch event; use 360° mode for a mature project's general health.
88
88
 
@@ -90,5 +90,5 @@ After the 7:
90
90
 
91
91
  - Doesn't fix anything on its own; it points to the fix owner.
92
92
  - Doesn't decide GO/NO-GO on behalf of the project owner; delivers the prioritized reading.
93
- - Doesn't replace `security-audit-pentest` for deep security or `site-launch-kit` for executing the 15 site rounds.
93
+ - Doesn't replace `wizz-security-audit-pentest` for deep security or `wizz-site-launch-kit` for executing the 15 site rounds.
94
94
  - Doesn't run on a project with no built surface (discovery/ideation stage).
@@ -1,6 +1,6 @@
1
1
  # Area 02 · Security
2
2
 
3
- A sweep of the exposed SURFACE before launch, not a hunt for vulnerabilities with proof of exploitation. A finding that requires a concrete payload to prove (mass assignment, IDOR, injection) is NOT dug into here: log the suspicion as 🟠 and point to `security-audit-pentest` as the owner of the deep dive.
3
+ A sweep of the exposed SURFACE before launch, not a hunt for vulnerabilities with proof of exploitation. A finding that requires a concrete payload to prove (mass assignment, IDOR, injection) is NOT dug into here: log the suspicion as 🟠 and point to `wizz-security-audit-pentest` as the owner of the deep dive.
4
4
 
5
5
  ## Objective checks
6
6
 
@@ -11,11 +11,11 @@ A sweep of the exposed SURFACE before launch, not a hunt for vulnerabilities wit
11
11
  5. **CORS too open.** `Access-Control-Allow-Origin: *` on an API that accepts credentials/tokens is 🔴; without credentials is 🟡.
12
12
  6. **Rate limiting missing on a sensitive route.** Login, signup, password recovery, or an endpoint that costs money (AI, SMS, email) with no limit at all is 🟠; point to `wizz-web-security` as the fixer.
13
13
  7. **Dependency with a known critical CVE on the production path.** Reinforces finding #7 from area 01 if it applies specifically to an auth/crypto lib: escalate to 🔴.
14
- 8. **Unproven indication of mass assignment, IDOR, or injection.** An update field that accepts the client's entire object with no allowlist, or a query built by string concatenation: log as 🟠 "suspicion, needs proof of exploitation" and point to `security-audit-pentest`.
14
+ 8. **Unproven indication of mass assignment, IDOR, or injection.** An update field that accepts the client's entire object with no allowlist, or a query built by string concatenation: log as 🟠 "suspicion, needs proof of exploitation" and point to `wizz-security-audit-pentest`.
15
15
 
16
16
  ## Cutoff rule
17
17
 
18
- This area NEVER delivers "how to exploit it" with a real payload: that is exclusively `security-audit-pentest`'s scope. If during the sweep you'd already have the payload ready, still don't include it here: log the suspicion and the reference, to avoid duplicating the two skills' output format.
18
+ This area NEVER delivers "how to exploit it" with a real payload: that is exclusively `wizz-security-audit-pentest`'s scope. If during the sweep you'd already have the payload ready, still don't include it here: log the suspicion and the reference, to avoid duplicating the two skills' output format.
19
19
 
20
20
  ## Output format
21
21
 
@@ -1,16 +1,16 @@
1
1
  # Area 03 · SEO / Discoverability
2
2
 
3
- Checks whether the product CAN be found on launch day. This is not a ranking, traffic, or Core Web Vitals diagnosis (that's `wizz-seo-audit`), and when the surface is a SITE, it doesn't repeat the execution rounds (that's `site-launch-kit`, rounds 08 to 13): here it only confirms whether basic indexability exists.
3
+ Checks whether the product CAN be found on launch day. This is not a ranking, traffic, or Core Web Vitals diagnosis (that's `wizz-seo-audit`), and when the surface is a SITE, it doesn't repeat the execution rounds (that's `wizz-site-launch-kit`, rounds 08 to 13): here it only confirms whether basic indexability exists.
4
4
 
5
5
  If the project has no indexable public web surface (e.g., mobile-only app, internal tool), respond `ITEM DOES NOT APPLY` with a 1-line justification and stop.
6
6
 
7
7
  ## Objective checks
8
8
 
9
- 1. **`robots.txt` present and not blocking everything.** Missing is 🟠; present but blocking `Disallow: /` in production by mistake is 🔴. Point to `site-launch-kit` (round 13) as the fixer.
9
+ 1. **`robots.txt` present and not blocking everything.** Missing is 🟠; present but blocking `Disallow: /` in production by mistake is 🔴. Point to `wizz-site-launch-kit` (round 13) as the fixer.
10
10
  2. **Sitemap present and referenced in `robots.txt`.** Missing is 🟠.
11
- 3. **Unique title and meta description on the main public pages.** A default/repeated title (e.g., the same title on every page) is 🟠. Point to `site-launch-kit` (round 08).
12
- 4. **Open Graph configured.** Without `og:title`/`og:image`/`og:description`, the share preview (WhatsApp, social) comes out broken. It's 🟡, but escalates to 🟠 if the launch's main acquisition channel is social/WhatsApp. Point to `site-launch-kit` (round 09).
13
- 5. **Basic structured business data.** Absence isn't a standalone blocker, but if the business depends on appearing in Google local/rich snippets, it's 🟡. Point to `site-launch-kit` (round 12) or `wizz-schema-markup`.
11
+ 3. **Unique title and meta description on the main public pages.** A default/repeated title (e.g., the same title on every page) is 🟠. Point to `wizz-site-launch-kit` (round 08).
12
+ 4. **Open Graph configured.** Without `og:title`/`og:image`/`og:description`, the share preview (WhatsApp, social) comes out broken. It's 🟡, but escalates to 🟠 if the launch's main acquisition channel is social/WhatsApp. Point to `wizz-site-launch-kit` (round 09).
13
+ 5. **Basic structured business data.** Absence isn't a standalone blocker, but if the business depends on appearing in Google local/rich snippets, it's 🟡. Point to `wizz-site-launch-kit` (round 12) or `wizz-schema-markup`.
14
14
  6. **Canonical and non-duplicated indexing.** Multiple URLs serving the same content without `rel=canonical` is 🟡.
15
15
  7. **Domain/DNS resolving to the right production environment.** A domain configured but pointing to the staging/preview environment is 🔴 (also an infra finding, see area 07; log only the discoverability part here: whether the search engine has already indexed the wrong domain).
16
16
 
@@ -6,7 +6,7 @@ Checks whether the team will be able to KNOW if the launch worked. A launch with
6
6
 
7
7
  1. **Analytics tool installed and firing.** No GA4/pixel/equivalent tool loading on the public pages at all is 🔴 if the launch depends on measuring acquisition; 🟠 otherwise.
8
8
  2. **Main conversion event configured.** The event that represents the launch's goal (signup, purchase, lead, download) not firing or not existing is 🔴.
9
- 3. **Thank-you/confirmation page event.** A post-conversion page with no "confirmed conversion" event (distinct from just "visited the page") is 🟠. Overlaps with `site-launch-kit` round 15 when the surface is a site.
9
+ 3. **Thank-you/confirmation page event.** A post-conversion page with no "confirmed conversion" event (distinct from just "visited the page") is 🟠. Overlaps with `wizz-site-launch-kit` round 15 when the surface is a site.
10
10
  4. **Trackable traffic source/UTM.** Launch campaigns without standardized UTM is 🟡: it measures what happened, but not where it came from.
11
11
  5. **Production error monitoring (Sentry or equivalent).** A project that already uses an error tool but it's not active in the production environment is 🟠. A project that never adopted one at all is 🟡 (not a recommendation to adopt Sentry now, just logging a gap: the adoption decision belongs to the team).
12
12
  6. **Downtime/uptime alert.** No alert configured for when the product goes down on launch day (when traffic is most sensitive) is 🟠.