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.
- package/package.json +5 -3
- package/removals.txt +10 -0
- package/skills-registry.yaml +60 -39
- package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +9 -9
- package/src/bmm-skills/2-plan-workflows/wizz-ux/references/creative-tools.md +2 -0
- package/src/bmm-skills/2-plan-workflows/wizz-ux/references/design-md-spec.md +2 -0
- package/src/bmm-skills/4-implementation/wizz-create-story/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-dev-story/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/checklist.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-quick-dev/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-sprint-status/SKILL.md +5 -5
- package/src/bmm-skills/module-help.csv +1 -1
- package/src/bmm-skills/module.yaml +1 -1
- package/src/core-skills/_shared/handoff-protocol.md +2 -2
- package/src/modules/lowticket/skills/wizz-lowticket-funil/SKILL.md +1 -1
- package/src/modules/lowticket/skills/wizz-lowticket-guia/SKILL.md +1 -1
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/SKILL.md +1 -1
- package/src/modules/lowticket/skills/wizz-lowticket-minerador/SKILL.md +1 -1
- package/src/modules/lowticket/skills/wizz-lowticket-pagina/SKILL.md +1 -1
- package/src/modules/lowticket/skills/wizz-lowticket-trafego/SKILL.md +1 -1
- package/src/modules/wizz/_shared/cerebro.md +7 -7
- package/src/modules/wizz/_shared/communication-rules.md +1 -1
- package/src/modules/wizz/_shared/encerramento.md +4 -3
- package/src/modules/wizz/_shared/organizacao.md +8 -4
- package/src/modules/wizz/_shared/token-economy.md +1 -1
- package/src/modules/wizz/_shared/video-pipeline.md +4 -4
- package/src/modules/wizz/agents/wizz-ads/SKILL.md +4 -1
- package/src/modules/wizz/agents/wizz-ads/customize.toml +12 -3
- package/src/modules/wizz/agents/wizz-ads/knowledge/playbook.md +36 -0
- package/src/modules/wizz/agents/wizz-copy/SKILL.md +4 -1
- package/src/modules/wizz/agents/wizz-copy/customize.toml +2 -3
- package/src/modules/wizz/agents/wizz-copy/knowledge/playbook.md +34 -0
- package/src/modules/wizz/agents/wizz-designer/SKILL.md +2 -2
- package/src/modules/wizz/agents/wizz-designer/customize.toml +2 -3
- package/src/modules/wizz/agents/wizz-growth/SKILL.md +4 -1
- package/src/modules/wizz/agents/wizz-growth/customize.toml +33 -4
- package/src/modules/wizz/agents/wizz-growth/knowledge/playbook.md +39 -0
- package/src/modules/wizz/agents/wizz-maestro/SKILL.md +6 -8
- package/src/modules/wizz/agents/wizz-maestro/customize.toml +2 -3
- package/src/modules/wizz/agents/wizz-memoria/SKILL.md +7 -7
- package/src/modules/wizz/agents/wizz-memoria/customize.toml +5 -6
- package/src/modules/wizz/agents/wizz-qa/SKILL.md +5 -6
- package/src/modules/wizz/agents/wizz-qa/customize.toml +3 -4
- package/src/modules/wizz/agents/wizz-seo/SKILL.md +4 -1
- package/src/modules/wizz/agents/wizz-seo/customize.toml +2 -3
- package/src/modules/wizz/agents/wizz-seo/knowledge/playbook.md +31 -0
- package/src/modules/wizz/agents/wizz-social/SKILL.md +3 -3
- package/src/modules/wizz/agents/wizz-social/customize.toml +2 -3
- package/src/modules/wizz/overrides/wizz-agent-analyst.toml +2 -2
- package/src/modules/wizz/overrides/wizz-agent-architect.toml +3 -3
- package/src/modules/wizz/overrides/wizz-agent-dev.toml +3 -3
- package/src/modules/wizz/overrides/wizz-agent-pm.toml +2 -2
- package/src/modules/wizz/overrides/wizz-agent-tech-writer.toml +2 -2
- package/src/modules/wizz/overrides/wizz-agent-ux-designer.toml +2 -2
- package/src/skills-lib/_shared/complexity-signal.md +8 -9
- package/src/skills-lib/_shared/dispatch-rule.yaml +21 -0
- package/src/skills-lib/{cerebro → wizz-cerebro}/SKILL.md +1 -1
- package/src/skills-lib/{ctc-align → wizz-ctc-align}/SKILL.md +1 -1
- package/src/skills-lib/wizz-decision-maker/references/output-format.md +21 -0
- package/src/skills-lib/wizz-decision-maker/references/references-and-extraction.md +32 -1
- package/src/skills-lib/wizz-design-needs/references/design-state.md +1 -1
- package/src/skills-lib/wizz-design-needs/references/gap-analysis.md +3 -1
- package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/SKILL.md +1 -1
- package/src/skills-lib/{find-skills → wizz-find-skills}/SKILL.md +1 -1
- package/src/skills-lib/wizz-huashu-design/references/sfx-library.md +1 -1
- package/src/skills-lib/wizz-huashu-design/scripts/add-music.sh +1 -1
- package/src/skills-lib/wizz-huashu-design/scripts/html2pptx.js +45 -37
- package/src/skills-lib/wizz-huashu-design/scripts/mix-voiceover.sh +1 -1
- package/src/skills-lib/wizz-huashu-design/scripts/narrate-pipeline.mjs +11 -5
- package/src/skills-lib/wizz-huashu-design/scripts/test_scripts.py +209 -0
- package/src/skills-lib/wizz-huashu-design/scripts/tts-doubao.mjs +42 -28
- package/src/skills-lib/wizz-ig-profile-optimizer/SKILL.md +1 -1
- package/src/skills-lib/wizz-inicio-de-projeto/SKILL.md +1 -1
- package/src/skills-lib/wizz-launch-readiness/SKILL.md +7 -7
- package/src/skills-lib/wizz-launch-readiness/references/02-seguranca.md +3 -3
- package/src/skills-lib/wizz-launch-readiness/references/03-seo-descoberta.md +5 -5
- package/src/skills-lib/wizz-launch-readiness/references/04-analytics-medicao.md +1 -1
- package/src/skills-lib/wizz-launch-readiness/references/05-conteudo-prova-social.md +6 -6
- package/src/skills-lib/wizz-launch-readiness/references/06-legal-lgpd.md +1 -1
- package/src/skills-lib/wizz-launch-readiness/references/08-fusao-priorizacao.md +1 -1
- package/src/skills-lib/wizz-offer-forge/SKILL.md +3 -3
- package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/README.md +1 -1
- package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/SKILL.md +1 -1
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/component-sources.md +23 -23
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-first-protocol.md +15 -3
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-links.md +14 -8
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/web-access-and-evidence.md +4 -3
- package/src/skills-lib/wizz-router/SKILL.md +3 -3
- package/src/skills-lib/wizz-router/references/routing-table-flat.md +10 -9
- package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/SKILL.md +1 -1
- package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/SKILL.md +1 -1
- package/src/skills-lib/wizz-ui-component-curator/SKILL.md +1 -1
- package/src/skills-lib/wizz-video-effects/SKILL.md +3 -3
- package/src/skills-lib/wizz-video-effects/references/data-contract.md +3 -3
- package/src/skills-lib/wizz-video-effects/scripts/fill_breaths.py +15 -1
- package/src/skills-lib/wizz-video-effects/scripts/test_fill_breaths.py +20 -0
- package/src/skills-lib/wizz-video-styles/SKILL.md +2 -2
- package/src/skills-lib/wizz-video-styles/data/recipes.json +32 -1
- package/src/skills-lib/wizz-video-styles/data/references.json +211 -1
- package/src/skills-lib/wizz-video-styles/data/styles.json +54 -0
- package/src/skills-lib/wizz-video-styles/references/generated-prompts.md +13 -3
- package/src/skills-lib/wizz-video-styles/references/style-picker.md +4 -2
- package/src/skills-lib/wizz-video-styles/references/styles/ui-morph.md +68 -0
- package/src/skills-lib/wizz-video-styles/scripts/analyze.sh +21 -9
- package/src/skills-lib/wizz-video-styles/scripts/references.mjs +1 -0
- package/src/skills-lib/wizz-web-security/SKILL.md +1 -1
- package/tools/fetch-assets.mjs +1 -1
- package/tools/hooks/README.md +18 -14
- package/tools/hooks/rtk-rewrite.js +95 -0
- package/tools/hooks/security-defensive-context.js +17 -1
- package/tools/hooks/session-rules.js +25 -5
- package/tools/hooks/wizz-arrumar.js +126 -12
- package/tools/hooks/wizz-artifact-guard.js +41 -7
- package/tools/hooks/wizz-cerebro-lib.js +239 -0
- package/tools/hooks/wizz-close-reminder.js +124 -0
- package/tools/hooks/wizz-debug-lib.js +55 -0
- package/tools/hooks/wizz-decision-trace.js +19 -2
- package/tools/hooks/wizz-feature.js +20 -4
- package/tools/hooks/wizz-router-enforce.js +30 -13
- package/tools/installer/command-text.js +243 -0
- package/tools/installer/commands/catalog.js +2 -0
- package/tools/installer/commands/doctor.js +10 -12
- package/tools/installer/commands/fetch-assets.js +5 -5
- package/tools/installer/commands/install.js +6 -7
- package/tools/installer/commands/resolve.js +17 -17
- package/tools/installer/commands/trace-report.js +40 -37
- package/tools/installer/core/doctor.js +60 -55
- package/tools/installer/core/hooks-settings.js +88 -14
- package/tools/installer/core/hooks-smoke.js +134 -0
- package/tools/installer/core/installer.js +61 -52
- package/tools/installer/core/quick-update.js +2 -3
- package/tools/installer/guided/labels.js +5 -0
- package/tools/installer/install-text.js +201 -0
- package/tools/installer/modules/channel-plan.js +2 -2
- package/tools/installer/modules/env-vars.js +5 -6
- package/tools/installer/modules/skills-lib.js +1 -1
- package/tools/installer/ui.js +17 -9
- /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-pesquisa-e-memoria.md +0 -0
- /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-rotina-e-captura.md +0 -0
- /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-sessao.md +0 -0
- /package/src/skills-lib/{ctc-align → wizz-ctc-align}/scripts/align.py +0 -0
- /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/README.md +0 -0
- /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/references/KEYWORDS.md +0 -0
- /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/assets/template.html +0 -0
- /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/prompts.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-1-validacao-entrada.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-2-autorizacao.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-3-abuso-volume.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-4-vazamento-resposta.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-5-fusao-plano.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/01-cta-principal.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/02-barra-fixa-mobile.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/03-tempo-de-resposta.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/04-prova-social.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/05-faq-decisao.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/06-imagens-reais.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/07-endereco-como-chegar.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/08-titles.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/09-open-graph.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/10-breadcrumbs.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/11-alt-text.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/12-schema-local.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/13-robots-sitemap.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/14-privacidade-termos.md +0 -0
- /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://
|
|
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://
|
|
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
|
-
|
|
917
|
-
|
|
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://
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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 🟠.
|