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
package/tools/hooks/README.md
CHANGED
|
@@ -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
|
|
8
|
-
| ------------------------------- |
|
|
9
|
-
| `wizz-router-enforce.js` | UserPromptSubmit
|
|
10
|
-
| `session-rules.js` | SessionStart
|
|
11
|
-
| `security-defensive-context.js` | PreToolUse (Skill)
|
|
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
|
-
| `
|
|
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
|
|
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`
|
|
21
|
-
| `wizz-
|
|
22
|
-
| `wizz-
|
|
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>"
|
|
18
|
-
//
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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 = {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|