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
@@ -4,29 +4,33 @@ Os hooks do Claude Code usados pelo Wizz Method vivem AQUI e são instalados em
4
4
  `~/.claude/hooks/` via `npm run sync:global`. Nunca edite a cópia global direto:
5
5
  ela é substituída no próximo sync.
6
6
 
7
- | Hook | Evento | O que faz |
8
- | ------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
9
- | `wizz-router-enforce.js` | UserPromptSubmit | Gate `isTrivial()` + injeção curta: em projeto Wizz (`_wizz/`) manda delegar direto ao maestro/agente da área (sem router); fora, manda pro `wizz-router`. |
10
- | `session-rules.js` | SessionStart | Duas coisas num processo só: injeta as regras de comunicação 1x por sessão (antes era por prompt) e resolve a FEATURE ATIVA sozinho (branch git > `_wizz/bmm/config.yaml` > nada), para ninguém precisar invocar `wizz-set-feature`. Branch de tronco e de release não viram feature. Fora de projeto Wizz e sem branch de feature a linha não é emitida (custo zero); dentro de projeto Wizz sem feature sai o lembrete curto "o primeiro pedido nomeado define uma", e sem `layout:` o lembrete de propor um. A feature também é o `tema` da memória. |
11
- | `security-defensive-context.js` | PreToolUse (Skill) | Metodologia defensiva/escopada ao invocar skills de segurança. Genérico por projeto (v2). |
12
- | `wizz-artifact-guard.js` | PostToolUse (Write) | Avisa (nunca bloqueia) artefato escrito fora da subpasta da feature ativa, artefato de feature sem feature ativa, frontmatter faltando em `<tipo>-<slug>.md`, e arquivo solto na raiz fora do `layout:`, com o comando que move. Fail-open sem `_wizz/` ou config. |
13
- | `rtk-rewrite.sh` | PreToolUse (Bash) | Reescreve comandos de shell via `rtk rewrite` (token economy). Fork local do hook gerado pelo instalador upstream do rtk, com 3 patches (auditoria 2026-07-07, C4/RT1; e 2026-09-11, RT-H3): reescrita linha a linha em comando multi-linha (o binário só reescrevia a 1ª linha), timeout interno por linha (`RTK_HOOK_TIMEOUT`, default 3s) que não depende do `timeout`/`gtimeout` do GNU coreutils, e cache do version-guard (`RTK_CACHE_TTL_SECONDS`, default 1h, em `${XDG_CACHE_HOME:-$HOME/.cache}/wizz/rtk-version`) pra não rodar `rtk --version` a cada `Bash` tool call da sessão. Requer `rtk` + `jq` no PATH; fail-open em ambos (e no cache: ilegível/corrompido/expirado recomputa em silêncio). |
7
+ | Hook | Evento | O que faz |
8
+ | ------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
9
+ | `wizz-router-enforce.js` | UserPromptSubmit | Gate `isTrivial()` + injeção curta: em projeto Wizz (`_wizz/`) manda delegar direto ao maestro/agente da área (sem router); fora, manda pro `wizz-router`. |
10
+ | `session-rules.js` | SessionStart | Duas coisas num processo só: injeta as regras de comunicação 1x por sessão (antes era por prompt) e resolve a FEATURE ATIVA sozinho (branch git > `_wizz/bmm/config.yaml` > nada), para ninguém precisar invocar `wizz-set-feature`. Branch de tronco e de release não viram feature. Fora de projeto Wizz e sem branch de feature a linha não é emitida (custo zero); dentro de projeto Wizz sem feature sai o lembrete curto "o primeiro pedido nomeado define uma", e sem `layout:` o lembrete de propor um. A feature também é o `tema` da memória. |
11
+ | `security-defensive-context.js` | PreToolUse (Skill) | Metodologia defensiva/escopada ao invocar skills de segurança. Genérico por projeto (v2). |
12
+ | `wizz-artifact-guard.js` | PostToolUse (Write/Edit) | Avisa (nunca bloqueia) artefato escrito fora da subpasta da feature ativa, artefato de feature sem feature ativa, frontmatter faltando em `<tipo>-<slug>.md`, e arquivo solto na raiz fora do `layout:`, com o comando que move. Fail-open sem `_wizz/` ou config. |
13
+ | `wizz-close-reminder.js` | Stop | Aviso ao usuário (`systemMessage`, nunca `decision: block`) quando a feature ativa está `implementado`/`no-ar` e o checklist de fechamento do `wizz-feature.js` tem pendência (ou está no ar e segue ativa). Sem subprocesso, sai cedo fora de projeto Wizz e com feature em andamento, não repete o mesmo aviso na sessão, fail-open. |
14
+ | `rtk-rewrite.sh` | PreToolUse (Bash) | Reescreve comandos de shell via `rtk rewrite` (token economy). Fork local do hook gerado pelo instalador upstream do rtk, com 3 patches (auditoria 2026-07-07, C4/RT1; e 2026-09-11, RT-H3): reescrita linha a linha em comando multi-linha (o binário só reescrevia a 1ª linha), timeout interno por linha (`RTK_HOOK_TIMEOUT`, default 3s) que não depende do `timeout`/`gtimeout` do GNU coreutils, e cache do version-guard (`RTK_CACHE_TTL_SECONDS`, default 1h, em `${XDG_CACHE_HOME:-$HOME/.cache}/wizz/rtk-version`) pra não rodar `rtk --version` a cada `Bash` tool call da sessão. Requer `rtk` + `jq` no PATH; fail-open em ambos (e no cache: ilegível/corrompido/expirado recomputa em silêncio). |
15
+ | `rtk-rewrite.js` | PreToolUse (Bash) | Versão Node do `rtk-rewrite.sh`, registrada no lugar dele só no Windows (sem bash e jq garantidos lá), desde 2026-10-15. Mesmo contrato: guard de multi-linha, linha a linha, timeout por linha, piso 0.23.0, fail-open. Sem o cache de versão. `node test/test-rtk-hook-node.js` exige a mesma saída dos dois contra o mesmo `rtk` fake. |
14
16
 
15
17
  Arquivos de apoio (copiados junto, NUNCA registrados em settings.json; o doctor cobra a presença):
16
18
 
17
- | Arquivo | O que é |
18
- | --- | --- |
19
- | `wizz-feature-lib.js` | Funções puras de feature, git, config, pastas de artefato e frontmatter. Usada por todos os hooks acima. |
20
- | `wizz-layout-lib.js` | O que pode ficar na raiz e para onde vai o resto (tipos `codigo`, `scripts`, `dados`, `docs`, `midia`, `arquivo`). |
21
- | `wizz-feature.js` | CLI do ciclo de vida da feature: `resolver`, `definir`, `artefato`, `decidir`, `status`, `verificar`, `encerrar`, `listar`. É o passo verificável que router/maestro rodam antes de despachar. |
22
- | `wizz-arrumar.js` | CLI de arrumação: `layout [--gravar]`, `planejar`, `aplicar` (manifesto .tsv + referências + testes antes/depois + `desfazer.sh`), `mover`, `desfazer`. |
19
+ | Arquivo | O que é |
20
+ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
+ | `wizz-feature-lib.js` | Funções puras de feature, git, config, pastas de artefato e frontmatter. Usada por todos os hooks acima. |
22
+ | `wizz-layout-lib.js` | O que pode ficar na raiz e para onde vai o resto (tipos `codigo`, `scripts`, `dados`, `docs`, `midia`, `arquivo`). |
23
+ | `wizz-cerebro-lib.js` | Grava o fechamento no Cérebro (`encerrar --cerebro`): acha o vault e `projetos/<projeto>.md`, atualiza a linha da feature e a `Última sessão` do bloco Estado e põe 1 linha em Sessões (teto 15; as antigas vão para `_index/cerebro-archive.md`, lido só por grep). Idempotente, fail-open. |
24
+ | `wizz-debug-lib.js` | Diagnóstico opt-in dos hooks: com `WIZZ_DEBUG=1` cada erro engolido vira 1 linha em stderr e é anexada a `~/.claude/wizz-debug.log` (`WIZZ_DEBUG_FILE` troca o caminho). Sem a variável: zero saída, zero disco. Os hooks seguem fail-open (exit 0). O `wizz doctor` roda cada hook com esse modo ligado (check `hooks-run`). |
25
+ | `wizz-feature.js` | CLI do ciclo de vida da feature: `resolver`, `definir`, `artefato`, `decidir`, `status`, `verificar`, `encerrar [--cerebro]`, `listar`. É o passo verificável que router/maestro rodam antes de despachar. |
26
+ | `wizz-arrumar.js` | CLI de arrumação: `layout [--gravar]`, `planejar`, `aplicar` (manifesto .tsv + referências + testes antes/depois + `desfazer.sh`), `mover`, `desfazer`. |
23
27
 
24
28
  Regra completa em `src/modules/wizz/_shared/organizacao.md` (2026-10-02, caso Spider). `session-rules.js` passou a lembrar "nenhuma feature ativa" e "projeto sem layout" dentro de projeto Wizz, e `wizz-router-enforce.js` acrescenta 1 linha de feature por prompt em projeto Wizz.
25
29
 
26
30
  Removidos em 2026-07 (arquivados em `~/.claude/hooks-archive/`):
27
31
  `no-narration-enforce.js` (absorvido pelo session-rules) e `vault-reminder.js`.
28
32
 
29
- Testes: `npm run test:organizacao` (CLIs de feature e arrumação, guard, lembretes nos cenários sem git / main / branch), `npm run eval:feature` (hooks reais + CLI + estado final; `--llm` mede a obediência do modelo), `npm run test:hooks` (isTrivial + contexto por modo), `npm run test:feature-context` (resolução da feature ativa: tronco não vira feature, branch ganha do config, silêncio quando não há feature) e `node test/test-rtk-hook.js` (rewrite básico + multi-linha do `rtk-rewrite.sh`, smoke test que exercita o binário `rtk` real quando disponível, e o cache do version-guard: criado na 1ª chamada e reusado na 2ª, TTL expirado força recompute, path do binário mudou invalida, cache corrompido não quebra e se auto-cura).
33
+ Testes: `node test/test-feature-close.js` (`encerrar --cerebro` e o lembrete do Stop), `npm run test:organizacao` (CLIs de feature e arrumação, guard, lembretes nos cenários sem git / main / branch), `npm run eval:feature` (hooks reais + CLI + estado final; `--llm` mede a obediência do modelo), `npm run test:hooks` (isTrivial + contexto por modo), `npm run test:feature-context` (resolução da feature ativa: tronco não vira feature, branch ganha do config, silêncio quando não há feature) e `node test/test-rtk-hook.js` (rewrite básico + multi-linha do `rtk-rewrite.sh`, smoke test que exercita o binário `rtk` real quando disponível, e o cache do version-guard: criado na 1ª chamada e reusado na 2ª, TTL expirado força recompute, path do binário mudou invalida, cache corrompido não quebra e se auto-cura).
30
34
  Evals de roteamento: `node evals/routing/run-routing-eval.mjs [--llm]`.
31
35
 
32
36
  Drift repo ↔ global: `npm run sync:check` compara hash de cada hook (e skill) entre o repo e `~/.claude`, sem escrever nada — é lembrete (fail-open), não gate; roda no fim do `npm test`. Achou drift? `npm run sync:global` atualiza `~/.claude`.
@@ -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
  }