wizz-method 2.17.2 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/THIRD-PARTY-NOTICES.md +12 -0
  2. package/package.json +5 -3
  3. package/removals.txt +10 -0
  4. package/skills-registry.yaml +72 -42
  5. package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +9 -9
  6. package/src/bmm-skills/4-implementation/wizz-create-story/SKILL.md +1 -1
  7. package/src/bmm-skills/4-implementation/wizz-dev-story/SKILL.md +1 -1
  8. package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/SKILL.md +1 -1
  9. package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/checklist.md +1 -1
  10. package/src/bmm-skills/4-implementation/wizz-quick-dev/SKILL.md +1 -1
  11. package/src/bmm-skills/4-implementation/wizz-sprint-status/SKILL.md +5 -5
  12. package/src/bmm-skills/module-help.csv +1 -1
  13. package/src/bmm-skills/module.yaml +1 -1
  14. package/src/core-skills/_shared/handoff-protocol.md +5 -3
  15. package/src/modules/lowticket/skills/wizz-lowticket-funil/SKILL.md +1 -1
  16. package/src/modules/lowticket/skills/wizz-lowticket-guia/SKILL.md +1 -1
  17. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/SKILL.md +1 -1
  18. package/src/modules/lowticket/skills/wizz-lowticket-minerador/SKILL.md +1 -1
  19. package/src/modules/lowticket/skills/wizz-lowticket-pagina/SKILL.md +1 -1
  20. package/src/modules/lowticket/skills/wizz-lowticket-trafego/SKILL.md +1 -1
  21. package/src/modules/wizz/_shared/cerebro.md +7 -7
  22. package/src/modules/wizz/_shared/communication-rules.md +1 -1
  23. package/src/modules/wizz/_shared/despacho-barato.md +1 -1
  24. package/src/modules/wizz/_shared/encerramento.md +4 -3
  25. package/src/modules/wizz/_shared/model-ladder.md +10 -0
  26. package/src/modules/wizz/_shared/organizacao.md +8 -4
  27. package/src/modules/wizz/_shared/token-economy.md +1 -1
  28. package/src/modules/wizz/_shared/video-pipeline.md +13 -3
  29. package/src/modules/wizz/agents/wizz-ads/SKILL.md +4 -1
  30. package/src/modules/wizz/agents/wizz-ads/customize.toml +12 -3
  31. package/src/modules/wizz/agents/wizz-ads/knowledge/playbook.md +36 -0
  32. package/src/modules/wizz/agents/wizz-copy/SKILL.md +4 -1
  33. package/src/modules/wizz/agents/wizz-copy/customize.toml +2 -3
  34. package/src/modules/wizz/agents/wizz-copy/knowledge/playbook.md +34 -0
  35. package/src/modules/wizz/agents/wizz-designer/SKILL.md +2 -2
  36. package/src/modules/wizz/agents/wizz-designer/customize.toml +2 -3
  37. package/src/modules/wizz/agents/wizz-growth/SKILL.md +4 -1
  38. package/src/modules/wizz/agents/wizz-growth/customize.toml +33 -4
  39. package/src/modules/wizz/agents/wizz-growth/knowledge/playbook.md +39 -0
  40. package/src/modules/wizz/agents/wizz-maestro/SKILL.md +7 -9
  41. package/src/modules/wizz/agents/wizz-maestro/customize.toml +2 -3
  42. package/src/modules/wizz/agents/wizz-memoria/SKILL.md +7 -7
  43. package/src/modules/wizz/agents/wizz-memoria/customize.toml +5 -6
  44. package/src/modules/wizz/agents/wizz-qa/SKILL.md +5 -6
  45. package/src/modules/wizz/agents/wizz-qa/customize.toml +3 -4
  46. package/src/modules/wizz/agents/wizz-seo/SKILL.md +4 -1
  47. package/src/modules/wizz/agents/wizz-seo/customize.toml +2 -3
  48. package/src/modules/wizz/agents/wizz-seo/knowledge/playbook.md +31 -0
  49. package/src/modules/wizz/agents/wizz-social/SKILL.md +5 -3
  50. package/src/modules/wizz/agents/wizz-social/customize.toml +12 -3
  51. package/src/modules/wizz/overrides/wizz-agent-analyst.toml +2 -2
  52. package/src/modules/wizz/overrides/wizz-agent-architect.toml +3 -3
  53. package/src/modules/wizz/overrides/wizz-agent-dev.toml +3 -3
  54. package/src/modules/wizz/overrides/wizz-agent-pm.toml +2 -2
  55. package/src/modules/wizz/overrides/wizz-agent-tech-writer.toml +2 -2
  56. package/src/modules/wizz/overrides/wizz-agent-ux-designer.toml +2 -2
  57. package/src/modules/wizz/subagents/codex/wizz-exec-standard.toml +1 -1
  58. package/src/modules/wizz/subagents/codex/wizz-exec-strong.toml +1 -1
  59. package/src/modules/wizz/subagents/gemini/wizz-exec-standard.md +1 -1
  60. package/src/modules/wizz/subagents/gemini/wizz-exec-strong.md +1 -1
  61. package/src/modules/wizz/subagents/opencode/wizz-exec-standard.md +1 -1
  62. package/src/modules/wizz/subagents/opencode/wizz-exec-strong.md +1 -1
  63. package/src/modules/wizz/subagents/spec.yaml +6 -6
  64. package/src/modules/wizz/subagents/wizz-exec-standard.md +1 -1
  65. package/src/modules/wizz/subagents/wizz-exec-strong.md +1 -1
  66. package/src/skills-lib/_shared/complexity-signal.md +8 -9
  67. package/src/skills-lib/_shared/dispatch-rule.yaml +21 -0
  68. package/src/skills-lib/{cerebro → wizz-cerebro}/SKILL.md +1 -1
  69. package/src/skills-lib/{ctc-align → wizz-ctc-align}/SKILL.md +1 -1
  70. package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/SKILL.md +1 -1
  71. package/src/skills-lib/{find-skills → wizz-find-skills}/SKILL.md +1 -1
  72. package/src/skills-lib/wizz-hook-extractor/LICENSE +21 -0
  73. package/src/skills-lib/wizz-hook-extractor/SKILL.md +56 -0
  74. package/src/skills-lib/wizz-hook-extractor/references/examples.md +59 -0
  75. package/src/skills-lib/wizz-hook-extractor/references/formulas.md +136 -0
  76. package/src/skills-lib/wizz-hook-extractor/scripts/read-post.sh +83 -0
  77. package/src/skills-lib/wizz-huashu-design/references/sfx-library.md +1 -1
  78. package/src/skills-lib/wizz-huashu-design/scripts/add-music.sh +1 -1
  79. package/src/skills-lib/wizz-huashu-design/scripts/html2pptx.js +45 -37
  80. package/src/skills-lib/wizz-huashu-design/scripts/mix-voiceover.sh +1 -1
  81. package/src/skills-lib/wizz-huashu-design/scripts/narrate-pipeline.mjs +11 -5
  82. package/src/skills-lib/wizz-huashu-design/scripts/test_scripts.py +209 -0
  83. package/src/skills-lib/wizz-huashu-design/scripts/tts-doubao.mjs +42 -28
  84. package/src/skills-lib/wizz-ig-profile-optimizer/LICENSE +21 -0
  85. package/src/skills-lib/wizz-ig-profile-optimizer/SKILL.md +74 -0
  86. package/src/skills-lib/wizz-ig-profile-optimizer/scripts/capture-profile.sh +41 -0
  87. package/src/skills-lib/wizz-ig-profile-optimizer/scripts/limits.mjs +82 -0
  88. package/src/skills-lib/wizz-inicio-de-projeto/SKILL.md +1 -1
  89. package/src/skills-lib/wizz-launch-readiness/SKILL.md +7 -7
  90. package/src/skills-lib/wizz-launch-readiness/references/02-seguranca.md +3 -3
  91. package/src/skills-lib/wizz-launch-readiness/references/03-seo-descoberta.md +5 -5
  92. package/src/skills-lib/wizz-launch-readiness/references/04-analytics-medicao.md +1 -1
  93. package/src/skills-lib/wizz-launch-readiness/references/05-conteudo-prova-social.md +6 -6
  94. package/src/skills-lib/wizz-launch-readiness/references/06-legal-lgpd.md +1 -1
  95. package/src/skills-lib/wizz-launch-readiness/references/08-fusao-priorizacao.md +1 -1
  96. package/src/skills-lib/wizz-offer-forge/SKILL.md +3 -3
  97. package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/README.md +1 -1
  98. package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/SKILL.md +1 -1
  99. package/src/skills-lib/wizz-router/SKILL.md +3 -3
  100. package/src/skills-lib/wizz-router/references/routing-table-flat.md +11 -9
  101. package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/SKILL.md +1 -1
  102. package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/SKILL.md +1 -1
  103. package/src/skills-lib/wizz-video-effects/SKILL.md +4 -3
  104. package/src/skills-lib/wizz-video-effects/references/data-contract.md +3 -3
  105. package/src/skills-lib/wizz-video-effects/scripts/fill_breaths.py +15 -1
  106. package/src/skills-lib/wizz-video-effects/scripts/test_fill_breaths.py +20 -0
  107. package/src/skills-lib/wizz-video-styles/SKILL.md +11 -5
  108. package/src/skills-lib/wizz-video-styles/data/recipes.json +65 -2
  109. package/src/skills-lib/wizz-video-styles/data/references.json +386 -1
  110. package/src/skills-lib/wizz-video-styles/data/styles.json +109 -0
  111. package/src/skills-lib/wizz-video-styles/references/generated-prompts.md +70 -0
  112. package/src/skills-lib/wizz-video-styles/references/measured-patterns.md +2 -0
  113. package/src/skills-lib/wizz-video-styles/references/style-picker.md +8 -4
  114. package/src/skills-lib/wizz-video-styles/references/styles/tipografia-cinetica.md +68 -0
  115. package/src/skills-lib/wizz-video-styles/references/styles/ui-morph.md +68 -0
  116. package/src/skills-lib/wizz-video-styles/scripts/analyze.sh +23 -10
  117. package/src/skills-lib/wizz-video-styles/scripts/beat_grid.py +68 -0
  118. package/src/skills-lib/wizz-video-styles/scripts/references.mjs +265 -0
  119. package/src/skills-lib/wizz-web-security/SKILL.md +1 -1
  120. package/tools/fetch-assets.mjs +1 -1
  121. package/tools/hooks/README.md +18 -14
  122. package/tools/hooks/rtk-rewrite.js +95 -0
  123. package/tools/hooks/security-defensive-context.js +17 -1
  124. package/tools/hooks/session-rules.js +25 -5
  125. package/tools/hooks/wizz-arrumar.js +126 -12
  126. package/tools/hooks/wizz-artifact-guard.js +41 -7
  127. package/tools/hooks/wizz-cerebro-lib.js +239 -0
  128. package/tools/hooks/wizz-close-reminder.js +124 -0
  129. package/tools/hooks/wizz-debug-lib.js +55 -0
  130. package/tools/hooks/wizz-decision-trace.js +19 -2
  131. package/tools/hooks/wizz-feature.js +20 -4
  132. package/tools/hooks/wizz-router-enforce.js +30 -13
  133. package/tools/installer/command-text.js +243 -0
  134. package/tools/installer/commands/catalog.js +2 -0
  135. package/tools/installer/commands/doctor.js +10 -12
  136. package/tools/installer/commands/fetch-assets.js +5 -5
  137. package/tools/installer/commands/install.js +6 -7
  138. package/tools/installer/commands/resolve.js +17 -17
  139. package/tools/installer/commands/trace-report.js +40 -37
  140. package/tools/installer/core/doctor.js +60 -55
  141. package/tools/installer/core/hooks-settings.js +88 -14
  142. package/tools/installer/core/hooks-smoke.js +134 -0
  143. package/tools/installer/core/installer.js +61 -52
  144. package/tools/installer/core/quick-update.js +2 -3
  145. package/tools/installer/install-text.js +201 -0
  146. package/tools/installer/modules/channel-plan.js +2 -2
  147. package/tools/installer/modules/env-vars.js +5 -6
  148. package/tools/installer/modules/skills-lib.js +1 -1
  149. package/tools/installer/ui.js +17 -9
  150. /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-pesquisa-e-memoria.md +0 -0
  151. /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-rotina-e-captura.md +0 -0
  152. /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-sessao.md +0 -0
  153. /package/src/skills-lib/{ctc-align → wizz-ctc-align}/scripts/align.py +0 -0
  154. /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/README.md +0 -0
  155. /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/references/KEYWORDS.md +0 -0
  156. /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/assets/template.html +0 -0
  157. /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/prompts.md +0 -0
  158. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-1-validacao-entrada.md +0 -0
  159. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-2-autorizacao.md +0 -0
  160. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-3-abuso-volume.md +0 -0
  161. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-4-vazamento-resposta.md +0 -0
  162. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-5-fusao-plano.md +0 -0
  163. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/01-cta-principal.md +0 -0
  164. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/02-barra-fixa-mobile.md +0 -0
  165. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/03-tempo-de-resposta.md +0 -0
  166. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/04-prova-social.md +0 -0
  167. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/05-faq-decisao.md +0 -0
  168. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/06-imagens-reais.md +0 -0
  169. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/07-endereco-como-chegar.md +0 -0
  170. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/08-titles.md +0 -0
  171. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/09-open-graph.md +0 -0
  172. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/10-breadcrumbs.md +0 -0
  173. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/11-alt-text.md +0 -0
  174. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/12-schema-local.md +0 -0
  175. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/13-robots-sitemap.md +0 -0
  176. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/14-privacidade-termos.md +0 -0
  177. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/15-medicao.md +0 -0
@@ -0,0 +1,95 @@
1
+ // rtk-rewrite.js — PreToolUse (Bash): reescreve o comando via `rtk rewrite`.
2
+ //
3
+ // Versão Node do rtk-rewrite.sh para o Windows (2026-10-15), onde não há bash
4
+ // nem jq garantidos. O installer registra este arquivo em win32 e o .sh nos
5
+ // outros sistemas (hooks-settings.js), então o comportamento tem de ser o
6
+ // mesmo; test/test-rtk-hook-node.js roda os dois contra o mesmo rtk fake.
7
+ //
8
+ // Mesmo contrato do .sh:
9
+ // - sem rtk no PATH, ou rtk abaixo de 0.23.0: sai 0 sem tocar o comando;
10
+ // - comando multi-linha com heredoc, continuação `\` ou aspas abertas numa
11
+ // linha passa intacto (reescrever corromperia o corpo do heredoc);
12
+ // - cada linha vai sozinha para `rtk rewrite`, com timeout (RTK_HOOK_TIMEOUT,
13
+ // 3 s); exit 0 ou 3 com saída = linha nova, qualquer outra coisa = original;
14
+ // - nada mudou: stdout vazio. Mudou: hookSpecificOutput com updatedInput.
15
+ // Fail-open em tudo: erro aqui nunca bloqueia o Bash.
16
+ //
17
+ // Sem o cache de versão do .sh: `rtk --version` de um binário nativo custa
18
+ // poucos ms, e no Windows não há `command -v` para chavear o cache pelo path.
19
+
20
+ const { spawnSync } = require('node:child_process');
21
+
22
+ const TIMEOUT_MS = (Number(process.env.RTK_HOOK_TIMEOUT) || 3) * 1000;
23
+ const FLOOR = [0, 23];
24
+
25
+ function rtkVersionOk() {
26
+ const res = spawnSync('rtk', ['--version'], { encoding: 'utf8', timeout: TIMEOUT_MS, windowsHide: true });
27
+ if (res.error || res.status !== 0) {
28
+ process.stderr.write('[rtk] WARNING: rtk is not installed or not in PATH. Hook cannot rewrite commands.\n');
29
+ return false;
30
+ }
31
+ const match = /v?(\d+)\.(\d+)\.(\d+)/.exec(res.stdout || '');
32
+ if (!match) return true;
33
+ const [major, minor] = [Number(match[1]), Number(match[2])];
34
+ if (major === FLOOR[0] && minor < FLOOR[1]) {
35
+ process.stderr.write(
36
+ `[rtk] WARNING: rtk ${match[1]}.${match[2]}.${match[3]} is too old (need >= 0.23.0). Upgrade: cargo install rtk\n`,
37
+ );
38
+ return false;
39
+ }
40
+ return true;
41
+ }
42
+
43
+ // Heredoc, continuação ou aspas desbalanceadas numa linha: não dá para
44
+ // reescrever linha a linha sem quebrar o comando.
45
+ function multilineUnsafe(cmd) {
46
+ if (cmd.includes('<<')) return true;
47
+ return cmd.split('\n').some((line) => {
48
+ if (line.endsWith('\\')) return true;
49
+ const count = (ch) => line.split(ch).length - 1;
50
+ return count('"') % 2 !== 0 || count("'") % 2 !== 0;
51
+ });
52
+ }
53
+
54
+ function rewriteOne(line) {
55
+ if (!line) return line;
56
+ const res = spawnSync('rtk', ['rewrite', line], { encoding: 'utf8', timeout: TIMEOUT_MS, windowsHide: true });
57
+ // Como o $(...) do .sh: a saída perde só as quebras de linha do fim.
58
+ const out = (res.stdout || '').replace(/\n+$/, '');
59
+ return !res.error && (res.status === 0 || res.status === 3) && out ? out : line;
60
+ }
61
+
62
+ function main(raw) {
63
+ const input = JSON.parse(raw);
64
+ const toolInput = input && typeof input.tool_input === 'object' && input.tool_input ? input.tool_input : {};
65
+ const cmd = typeof toolInput.command === 'string' ? toolInput.command : '';
66
+ if (!cmd) return;
67
+ if (cmd.includes('\n') && multilineUnsafe(cmd)) return;
68
+ if (!rtkVersionOk()) return;
69
+
70
+ const rewritten = cmd.includes('\n') ? cmd.split('\n').map(rewriteOne).join('\n') : rewriteOne(cmd);
71
+ if (rewritten === cmd) return;
72
+
73
+ process.stdout.write(
74
+ JSON.stringify({
75
+ hookSpecificOutput: {
76
+ hookEventName: 'PreToolUse',
77
+ permissionDecision: 'allow',
78
+ permissionDecisionReason: 'RTK auto-rewrite',
79
+ updatedInput: { ...toolInput, command: rewritten },
80
+ },
81
+ }) + '\n',
82
+ );
83
+ }
84
+
85
+ let raw = '';
86
+ process.stdin.setEncoding('utf8');
87
+ process.stdin.on('data', (chunk) => (raw += chunk));
88
+ process.stdin.on('end', () => {
89
+ try {
90
+ main(raw);
91
+ } catch {
92
+ // fail-open: JSON quebrado ou erro inesperado deixa o comando como veio
93
+ }
94
+ process.exit(0);
95
+ });
@@ -28,6 +28,21 @@ const SECURITY_SKILLS = new Set([
28
28
  'code-review:code-review',
29
29
  ]);
30
30
 
31
+ // Diagnóstico opt-in (WIZZ_DEBUG=1). Lib ausente numa cópia parcial: ainda avisa em stderr.
32
+ let debug = (hook, phase, err) => {
33
+ if (process.env.WIZZ_DEBUG !== '1') return;
34
+ try {
35
+ process.stderr.write(`[wizz-debug] ${hook} ${phase}: ${(err && err.message) || err}\n`);
36
+ } catch {
37
+ // stderr fechado
38
+ }
39
+ };
40
+ try {
41
+ ({ debug } = require('./wizz-debug-lib.js'));
42
+ } catch (error) {
43
+ debug('security-defensive-context', 'require wizz-debug-lib', error);
44
+ }
45
+
31
46
  const DEFENSIVE_CONTEXT =
32
47
  'Metodologia de revisao de seguranca (defensiva, escopada):\n\n' +
33
48
  '1. ESCOPO: trabalhe por arquivo ou dimensao concreta de cada vez (ex: validacao ' +
@@ -63,8 +78,9 @@ process.stdin.on('end', () => {
63
78
  );
64
79
  }
65
80
  // Para qualquer outra skill/tool: sai silenciosamente sem output
66
- } catch {
81
+ } catch (error) {
67
82
  // Parse error: sai silenciosamente, nao bloqueia nada
83
+ debug('security-defensive-context', 'parse payload', error);
68
84
  }
69
85
  process.exit(0);
70
86
  });
@@ -45,11 +45,27 @@ const RULES =
45
45
  // Feature, git e config moram em wizz-feature-lib.js (mesma pasta: o installer
46
46
  // e o sync:global copiam tools/hooks/ inteiro). Se a lib faltar numa cópia
47
47
  // manual parcial, a regra de comunicação continua saindo e a feature cala.
48
+ // Diagnóstico opt-in (WIZZ_DEBUG=1). Lib ausente numa cópia parcial: ainda avisa em stderr.
49
+ let debug = (hook, phase, err) => {
50
+ if (process.env.WIZZ_DEBUG !== '1') return;
51
+ try {
52
+ process.stderr.write(`[wizz-debug] ${hook} ${phase}: ${(err && err.message) || err}\n`);
53
+ } catch {
54
+ // stderr fechado
55
+ }
56
+ };
57
+ try {
58
+ ({ debug } = require('./wizz-debug-lib.js'));
59
+ } catch (error) {
60
+ debug('session-rules', 'require wizz-debug-lib', error);
61
+ }
62
+
48
63
  let lib = null;
49
64
  try {
50
65
  lib = require('./wizz-feature-lib.js');
51
- } catch {
66
+ } catch (error) {
52
67
  lib = null;
68
+ debug('session-rules', 'require wizz-feature-lib', error);
53
69
  }
54
70
 
55
71
  const HOOK_CMD = 'node _wizz/hooks/wizz-feature.js';
@@ -246,22 +262,25 @@ function finish(raw) {
246
262
  try {
247
263
  const payload = JSON.parse(raw || '{}');
248
264
  if (payload && typeof payload.cwd === 'string' && payload.cwd) cwd = payload.cwd;
249
- } catch {
265
+ } catch (error) {
250
266
  // payload ausente ou inválido: cai no cwd do processo
267
+ debug('session-rules', 'parse payload', error);
251
268
  }
252
269
 
253
270
  try {
254
271
  const feature = buildFeatureContext(cwd);
255
272
  if (feature) context += `\n\n${feature}`;
256
- } catch {
273
+ } catch (error) {
257
274
  // resolução de feature NUNCA derruba as regras de comunicação
275
+ debug('session-rules', 'buildFeatureContext', error);
258
276
  }
259
277
 
260
278
  try {
261
279
  const ceiling = buildMemoryCeilingContext(cwd);
262
280
  if (ceiling) context += `\n\n${ceiling}`;
263
- } catch {
281
+ } catch (error) {
264
282
  // teto da memória é aviso, não gate: erro aqui não afeta o resto
283
+ debug('session-rules', 'buildMemoryCeilingContext', error);
265
284
  }
266
285
 
267
286
  try {
@@ -273,8 +292,9 @@ function finish(raw) {
273
292
  },
274
293
  }),
275
294
  );
276
- } catch {
295
+ } catch (error) {
277
296
  // nunca bloquear
297
+ debug('session-rules', 'write stdout', error);
278
298
  }
279
299
  process.exit(0);
280
300
  }
@@ -14,8 +14,10 @@
14
14
  // mover <origem> <destino> atalho de um arquivo (manifesto de uma linha + aplicar)
15
15
  // desfazer <pasta-da-arrumacao>
16
16
  //
17
- // Opções de aplicar/mover: --testes "<comando>", --refs <dir,dir> (ex.: pasta
18
- // da auto-memória), --manter (não desfaz sozinho se os testes quebrarem).
17
+ // Opções de aplicar/mover: --testes "<comando>" (só runner conhecido: npm/pnpm/
18
+ // yarn/bun test, npx vitest|jest, pytest, go/cargo/make test; sem valor = script
19
+ // test do package.json; outro comando exige --testes-livre), --refs <dir,dir>
20
+ // (ex.: pasta da auto-memória), --manter (não desfaz sozinho se os testes quebrarem).
19
21
  // Cada execução fica em `<arquivo>/arrumacao/<data>-<hora>/`.
20
22
 
21
23
  const fs = require('node:fs');
@@ -76,7 +78,7 @@ function parseArgs(argv) {
76
78
  if (a.startsWith('--')) {
77
79
  const key = a.slice(2);
78
80
  const next = argv[i + 1];
79
- if (next !== undefined && !next.startsWith('--')) {
81
+ if (key !== 'testes-livre' && next !== undefined && !next.startsWith('--')) {
80
82
  opts[key] = next;
81
83
  i++;
82
84
  } else opts[key] = true;
@@ -237,17 +239,116 @@ function rewriteRefs(text, moves, fileDir) {
237
239
 
238
240
  // ── aplicar / desfazer ──────────────────────────────────────────────────
239
241
 
240
- function runTests(root, cmd) {
241
- if (!cmd || cmd === true) return null;
242
- const r = spawnSync(cmd, { cwd: root, shell: true, encoding: 'utf8', timeout: 10 * 60 * 1000 });
243
- return { ok: r.status === 0, code: r.status, tail: `${r.stdout || ''}${r.stderr || ''}`.trim().split('\n').slice(-5).join('\n') };
242
+ // ── comando de testes: allowlist, sem shell ─────────────────────────────
243
+ //
244
+ // `--testes` não executa string arbitrária: o comando é cortado em argv (sem
245
+ // shell) e só roda se for de um runner conhecido. `--testes` sem valor usa o
246
+ // script `test` do package.json. Qualquer outra coisa exige `--testes-livre`,
247
+ // que imprime o que vai rodar e usa o shell (o dono assumiu o comando).
248
+
249
+ const SHELL_META = /[;&|<>$`()\n\r\\]/;
250
+ const PM = new Set(['npm', 'pnpm', 'yarn', 'bun']);
251
+ const TEST_SCRIPT = /^test(?:[:\w.-]*)$/;
252
+
253
+ /** Corta em argv respeitando aspas simples/duplas. null = não dá para cortar com segurança. */
254
+ function splitArgv(text) {
255
+ if (SHELL_META.test(text)) return null;
256
+ const argv = [];
257
+ let cur = '';
258
+ let quote = '';
259
+ let has = false;
260
+ for (const ch of text) {
261
+ if (quote) {
262
+ if (ch === quote) quote = '';
263
+ else cur += ch;
264
+ } else if (ch === '"' || ch === "'") {
265
+ quote = ch;
266
+ has = true;
267
+ } else if (/\s/.test(ch)) {
268
+ if (has || cur) argv.push(cur);
269
+ cur = '';
270
+ has = false;
271
+ } else {
272
+ cur += ch;
273
+ has = true;
274
+ }
275
+ }
276
+ if (quote) return null;
277
+ if (has || cur) argv.push(cur);
278
+ return argv;
279
+ }
280
+
281
+ /** O argv é de um runner de testes conhecido? */
282
+ function isAllowedTestArgv(argv) {
283
+ const [bin, a, b] = argv;
284
+ if (!bin) return false;
285
+ if (PM.has(bin)) return a === 'test' || (a === 'run' && TEST_SCRIPT.test(b || ''));
286
+ if (bin === 'npx') return a === 'vitest' || a === 'jest';
287
+ if (bin === 'pytest') return true;
288
+ if (bin === 'python' || bin === 'python3') return a === '-m' && (b === 'pytest' || b === 'unittest');
289
+ if (bin === 'go' || bin === 'cargo' || bin === 'make') return a === 'test';
290
+ return false;
291
+ }
292
+
293
+ /**
294
+ * Resolve o que `--testes` vai rodar.
295
+ * @returns {{none: true}|{argv: string[], shell: false, display: string}|{shellCmd: string, shell: true, display: string}|{error: string}}
296
+ */
297
+ function resolveTestCommand(root, opts) {
298
+ const raw = opts.testes;
299
+ if (!raw) return { none: true };
300
+ let text = raw;
301
+ if (raw === true) {
302
+ try {
303
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
304
+ if (!pkg.scripts || typeof pkg.scripts.test !== 'string') throw new Error('sem script test');
305
+ } catch {
306
+ return {
307
+ error: '--testes sem comando pede o script "test" do package.json, e ele não existe aqui. Passe o comando ou use --testes-livre.',
308
+ };
309
+ }
310
+ text = 'npm test';
311
+ }
312
+ const argv = splitArgv(text);
313
+ if (argv && isAllowedTestArgv(argv)) return { argv, shell: false, display: argv.join(' ') };
314
+ if (opts['testes-livre']) return { shellCmd: text, shell: true, display: text };
315
+ return {
316
+ error:
317
+ `Comando de testes fora da lista: "${text}". Aceitos: npm/pnpm/yarn/bun test ou run test*, npx vitest|jest, pytest, ` +
318
+ 'python -m pytest|unittest, go test, cargo test, make test (sem ; & | < > $ ` ou parênteses). ' +
319
+ 'Para rodar outro comando, repita com --testes-livre.',
320
+ };
321
+ }
322
+
323
+ function runTests(root, resolved) {
324
+ if (!resolved || resolved.none) return null;
325
+ process.stderr.write(`Rodando testes${resolved.shell ? ' (livre, via shell)' : ''}: ${resolved.display}\n`);
326
+ const timeout = 10 * 60 * 1000;
327
+ const r = resolved.shell
328
+ ? spawnSync(resolved.shellCmd, { cwd: root, shell: true, encoding: 'utf8', timeout })
329
+ : spawnSync(resolved.argv[0], resolved.argv.slice(1), { cwd: root, shell: false, encoding: 'utf8', timeout });
330
+ const out = `${r.stdout || ''}${r.stderr || ''}${r.error ? r.error.message : ''}`;
331
+ return { ok: r.status === 0, code: r.status, tail: out.trim().split('\n').slice(-5).join('\n') };
244
332
  }
245
333
 
246
334
  function shQuote(s) {
247
335
  return `'${String(s).replaceAll("'", String.raw`'\''`)}'`;
248
336
  }
249
337
 
250
- function writeUndoScript(runDir, root, moves, edits) {
338
+ // Desfazer em qualquer sistema: `node wizz-arrumar.js desfazer <pasta>` (Node
339
+ // puro, função undo abaixo). O script ao lado é atalho: desfazer.sh fora do
340
+ // Windows; no Windows, sem bash garantido, um desfazer.cmd que chama o Node.
341
+ function undoCommand(root, runDir) {
342
+ const q = (p) => `"${p}"`;
343
+ return `node ${q(__filename)} desfazer ${q(path.relative(root, runDir))}`;
344
+ }
345
+
346
+ function writeUndoScript(runDir, root, moves, edits, platform = process.platform) {
347
+ if (platform === 'win32') {
348
+ const file = path.join(runDir, 'desfazer.cmd');
349
+ fs.writeFileSync(file, ['@echo off', `cd /d "${root}"`, undoCommand(root, runDir), ''].join('\r\n'));
350
+ return file;
351
+ }
251
352
  const lines = [
252
353
  '#!/usr/bin/env bash',
253
354
  `# Desfaz a arrumação de ${path.basename(runDir)}: restaura os arquivos editados e devolve os moves.`,
@@ -305,12 +406,14 @@ function applyMoves(root, moves, opts) {
305
406
  const config = lib.readConfig(root);
306
407
  const problems = validateMoves(root, moves);
307
408
  if (problems.length > 0) return { ok: false, code: 2, line: `Nada foi movido: ${problems.join('; ')}.` };
409
+ const testCommand = resolveTestCommand(root, opts);
410
+ if (testCommand.error) return { ok: false, code: 2, line: `Nada foi movido: ${testCommand.error}` };
308
411
 
309
412
  const runDir = path.join(archiveDir(root, config.layout), stamp());
310
413
  fs.mkdirSync(runDir, { recursive: true });
311
414
  writeManifest(path.join(runDir, 'manifesto.tsv'), moves);
312
415
 
313
- const before = runTests(root, opts.testes);
416
+ const before = runTests(root, testCommand);
314
417
 
315
418
  // Referências: projeto inteiro + pastas extras (auto-memória, Cérebro).
316
419
  const extra = typeof opts.refs === 'string' ? opts.refs.split(',').map((d) => path.resolve(root, d.trim())) : [];
@@ -348,7 +451,7 @@ function applyMoves(root, moves, opts) {
348
451
  fs.renameSync(path.join(root, m.origem), to);
349
452
  }
350
453
  const undoFile = writeUndoScript(runDir, root, moves, edits);
351
- const after = runTests(root, opts.testes);
454
+ const after = runTests(root, testCommand);
352
455
  const relRun = path.relative(root, runDir);
353
456
 
354
457
  if (before && after && before.ok && !after.ok && !opts.manter) {
@@ -365,7 +468,7 @@ function applyMoves(root, moves, opts) {
365
468
  return {
366
469
  ok: !after || after.ok || !before?.ok,
367
470
  code: 0,
368
- line: `Movi ${moves.length} arquivo(s) e atualizei referências em ${edits.length}.${testes} Desfazer: ${path.relative(root, undoFile)}.`,
471
+ line: `Movi ${moves.length} arquivo(s) e atualizei referências em ${edits.length}.${testes} Desfazer: ${undoCommand(root, runDir)} (ou ${path.relative(root, undoFile)}).`,
369
472
  };
370
473
  }
371
474
 
@@ -415,4 +518,15 @@ if (require.main === module) {
415
518
  process.exit(result.code ?? (result.ok ? 0 : 1));
416
519
  }
417
520
 
418
- module.exports = { run, rewriteRefs, readManifest, planMoves, undo };
521
+ module.exports = {
522
+ run,
523
+ splitArgv,
524
+ isAllowedTestArgv,
525
+ resolveTestCommand,
526
+ rewriteRefs,
527
+ readManifest,
528
+ planMoves,
529
+ undo,
530
+ writeUndoScript,
531
+ undoCommand,
532
+ };
@@ -1,4 +1,4 @@
1
- // wizz-artifact-guard.js — PostToolUse hook (matcher: Write)
1
+ // wizz-artifact-guard.js — PostToolUse hook (matcher: Write|Edit|MultiEdit)
2
2
  //
3
3
  // Fonte de verdade: wizz-method/tools/hooks/. Instalar via `npm run sync:global`
4
4
  // ou pelo installer (registra em `.claude/settings.json` do projeto).
@@ -12,22 +12,43 @@
12
12
  // 2. Arquivo solto na raiz: fora do `layout:` do config. Sugere o destino e
13
13
  // o comando que move atualizando as referências.
14
14
  //
15
+ // Edit/MultiEdit só conferem o frontmatter do artefato (lido do disco): o
16
+ // arquivo já existia, então lugar e raiz foram avisados quando ele nasceu.
17
+ //
15
18
  // Fail-open: sem `_wizz/`, sem config, payload estranho ou qualquer erro =
16
19
  // silêncio e exit 0. Caso comum (arquivo dentro de uma pasta qualquer) custa
17
20
  // zero token.
18
21
 
22
+ const fs = require('node:fs');
19
23
  const path = require('node:path');
20
24
 
25
+ // Diagnóstico opt-in (WIZZ_DEBUG=1). Lib ausente numa cópia parcial: ainda avisa em stderr.
26
+ let debug = (hook, phase, err) => {
27
+ if (process.env.WIZZ_DEBUG !== '1') return;
28
+ try {
29
+ process.stderr.write(`[wizz-debug] ${hook} ${phase}: ${(err && err.message) || err}\n`);
30
+ } catch {
31
+ // stderr fechado
32
+ }
33
+ };
34
+ try {
35
+ ({ debug } = require('./wizz-debug-lib.js'));
36
+ } catch (error) {
37
+ debug('artifact-guard', 'require wizz-debug-lib', error);
38
+ }
39
+
21
40
  let lib;
22
41
  let layoutLib;
23
42
  try {
24
43
  lib = require('./wizz-feature-lib.js');
25
44
  layoutLib = require('./wizz-layout-lib.js');
26
- } catch {
45
+ } catch (error) {
27
46
  lib = null;
47
+ debug('artifact-guard', 'require wizz-feature-lib/wizz-layout-lib', error);
28
48
  }
29
49
 
30
50
  const HOOK_CMD = '_wizz/hooks';
51
+ const EDIT_TOOLS = new Set(['Edit', 'MultiEdit']);
31
52
 
32
53
  function isInside(child, parent) {
33
54
  const r = path.relative(parent, child);
@@ -40,7 +61,7 @@ function isInside(child, parent) {
40
61
  * @param {string} content - conteúdo escrito (pode ser '')
41
62
  * @param {ReturnType<typeof lib.resolveFeature>} state
42
63
  */
43
- function artifactWarning(file, content, state) {
64
+ function artifactWarning(file, content, state, { frontmatterOnly = false } = {}) {
44
65
  const dirs = lib.artifactDirs(state.root, state.config);
45
66
  const base = [dirs.planning, dirs.implementation].find((d) => isInside(file, d));
46
67
  if (!base) return '';
@@ -52,7 +73,8 @@ function artifactWarning(file, content, state) {
52
73
  const slug = state.slug;
53
74
  const msgs = [];
54
75
 
55
- if (parts.length === 1) {
76
+ // Edit: o lugar já foi avisado quando o arquivo nasceu; só o frontmatter conta.
77
+ if (!frontmatterOnly && parts.length === 1) {
56
78
  if (lib.PROJECT_WIDE.test(name)) return '';
57
79
  if (slug) {
58
80
  msgs.push(
@@ -65,7 +87,7 @@ function artifactWarning(file, content, state) {
65
87
  `Grave a feature a partir do pedido (node ${HOOK_CMD}/wizz-feature.js definir <slug>) e mova o arquivo para a subpasta dela.`,
66
88
  );
67
89
  }
68
- } else if (slug && parts[0] !== slug) {
90
+ } else if (!frontmatterOnly && slug && parts[0] !== slug) {
69
91
  msgs.push(
70
92
  `ARTEFATO EM OUTRA FEATURE: ${relFile} está em "${parts[0]}", mas a ativa é "${slug}". ` +
71
93
  `Se o trabalho é de "${slug}", mova; se o assunto mudou, troque a feature (wizz-feature.js definir ${parts[0]}).`,
@@ -107,6 +129,15 @@ function rootWarning(file, state) {
107
129
  );
108
130
  }
109
131
 
132
+ function readOrEmpty(file) {
133
+ try {
134
+ return fs.readFileSync(file, 'utf8');
135
+ } catch (error) {
136
+ debug('artifact-guard', 'readOrEmpty', error);
137
+ return '';
138
+ }
139
+ }
140
+
110
141
  /**
111
142
  * Decide o aviso a partir do payload do PostToolUse. Pura o bastante para teste.
112
143
  * @param {{tool_name?: string, tool_input?: {file_path?: string, content?: string}, cwd?: string}} payload
@@ -114,7 +145,8 @@ function rootWarning(file, state) {
114
145
  */
115
146
  function buildWarning(payload) {
116
147
  if (!lib || !payload || typeof payload !== 'object') return '';
117
- if (payload.tool_name && payload.tool_name !== 'Write') return '';
148
+ const isEdit = EDIT_TOOLS.has(payload.tool_name);
149
+ if (payload.tool_name && payload.tool_name !== 'Write' && !isEdit) return '';
118
150
  const input = payload.tool_input || {};
119
151
  if (typeof input.file_path !== 'string' || !input.file_path) return '';
120
152
 
@@ -129,6 +161,7 @@ function buildWarning(payload) {
129
161
  const first = path.relative(state.root, file).split(path.sep)[0];
130
162
  if (first === '_wizz' || first.startsWith('.')) return '';
131
163
 
164
+ if (isEdit) return artifactWarning(file, readOrEmpty(file), state, { frontmatterOnly: true });
132
165
  return artifactWarning(file, typeof input.content === 'string' ? input.content : '', state) || rootWarning(file, state);
133
166
  }
134
167
 
@@ -150,8 +183,9 @@ if (require.main === module) {
150
183
  JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: `📁 Wizz: ${warning}` } }),
151
184
  );
152
185
  }
153
- } catch {
186
+ } catch (error) {
154
187
  // aviso nunca derruba a ferramenta
188
+ debug('artifact-guard', 'buildWarning', error);
155
189
  }
156
190
  process.exit(0);
157
191
  }