wizz-method 2.18.0 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -3
- package/removals.txt +10 -0
- package/skills-registry.yaml +50 -38
- package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +9 -9
- 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/{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-router/SKILL.md +3 -3
- package/src/skills-lib/wizz-router/references/routing-table-flat.md +9 -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-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/install-text.js +201 -0
- package/tools/installer/modules/channel-plan.js +2 -2
- package/tools/installer/modules/env-vars.js +5 -6
- package/tools/installer/modules/skills-lib.js +1 -1
- package/tools/installer/ui.js +17 -9
- /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-pesquisa-e-memoria.md +0 -0
- /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-rotina-e-captura.md +0 -0
- /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-sessao.md +0 -0
- /package/src/skills-lib/{ctc-align → wizz-ctc-align}/scripts/align.py +0 -0
- /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/README.md +0 -0
- /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/references/KEYWORDS.md +0 -0
- /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/assets/template.html +0 -0
- /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/prompts.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-1-validacao-entrada.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-2-autorizacao.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-3-abuso-volume.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-4-vazamento-resposta.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-5-fusao-plano.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/01-cta-principal.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/02-barra-fixa-mobile.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/03-tempo-de-resposta.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/04-prova-social.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/05-faq-decisao.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/06-imagens-reais.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/07-endereco-como-chegar.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/08-titles.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/09-open-graph.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/10-breadcrumbs.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/11-alt-text.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/12-schema-local.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/13-robots-sitemap.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/14-privacidade-termos.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/15-medicao.md +0 -0
|
@@ -1,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
|
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// wizz-cerebro-lib.js — grava o fechamento de uma feature no Cérebro.
|
|
2
|
+
//
|
|
3
|
+
// Fonte de verdade: wizz-method/tools/hooks/. Não é hook registrado: é a
|
|
4
|
+
// biblioteca do `wizz-feature.js encerrar --cerebro`, copiada junto (installer
|
|
5
|
+
// e sync:global). Tira da memória do modelo o dever de fechamento "bloco
|
|
6
|
+
// Estado + 1 linha na tabela Sessões" (`_shared/cerebro.md`).
|
|
7
|
+
//
|
|
8
|
+
// Regras do protocolo do Cérebro (skill `wizz-cerebro`, comandos-sessao.md):
|
|
9
|
+
// - vault: `~/Documents/projects/Obsidian Vault/CEREBRO.md`. O `find ~`
|
|
10
|
+
// do protocolo fica de fora (caro demais para uma CLI); quem usa outro
|
|
11
|
+
// lugar aponta `WIZZ_CEREBRO_VAULT` ou `cerebro_vault:` no config.
|
|
12
|
+
// - estado mora em `projetos/<nome>.md`, bloco `## Estado` no topo.
|
|
13
|
+
// - `Sessões` com teto de 15 linhas; nunca apagar histórico: as antigas
|
|
14
|
+
// vão para `_index/cerebro-archive.md`, seção `## Sessões antigas —
|
|
15
|
+
// <projeto>`. Decisão do dono (2026-10-15): sessão antiga já foi feita,
|
|
16
|
+
// então sai do arquivo que o `/wizz-cerebro ver` lê sempre e fica no arquivo
|
|
17
|
+
// morto, que só se lê por grep quando precisar. Fora isso, a CLI não toca
|
|
18
|
+
// nenhum outro arquivo do vault.
|
|
19
|
+
//
|
|
20
|
+
// O bloco Estado é curado à mão; sobrescrever inteiro apagaria o que a CLI
|
|
21
|
+
// não sabe. Então só duas linhas são da CLI: `**Feature `<slug>`:**` (uma
|
|
22
|
+
// por feature) e `**Última sessão:**`. Ambas são substituídas, nunca
|
|
23
|
+
// empilhadas: rodar duas vezes deixa o arquivo idêntico.
|
|
24
|
+
|
|
25
|
+
const fs = require('node:fs');
|
|
26
|
+
const os = require('node:os');
|
|
27
|
+
const path = require('node:path');
|
|
28
|
+
const lib = require('./wizz-feature-lib.js');
|
|
29
|
+
|
|
30
|
+
const SESSIONS_CAP = 15;
|
|
31
|
+
const SUMMARY_MAX = 160;
|
|
32
|
+
const DEFAULT_VAULT = path.join('Documents', 'projects', 'Obsidian Vault');
|
|
33
|
+
const ARCHIVE_FILE = path.join('_index', 'cerebro-archive.md');
|
|
34
|
+
const ARCHIVE_HEADER = [
|
|
35
|
+
'# Arquivo do Cérebro',
|
|
36
|
+
'',
|
|
37
|
+
'> Excedente movido do CEREBRO.md e dos arquivos de projeto. Nada aqui foi apagado, só tirado do índice.',
|
|
38
|
+
'> Este arquivo é para **grep**, nunca para Read completo.',
|
|
39
|
+
'',
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const hasCerebro = (dir) => {
|
|
43
|
+
try {
|
|
44
|
+
return Boolean(dir) && fs.statSync(path.join(dir, 'CEREBRO.md')).isFile();
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const expandHome = (p) => (p.startsWith('~') ? path.join(os.homedir(), p.slice(1)) : p);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Acha o vault: `WIZZ_CEREBRO_VAULT` > `cerebro_vault` do config > padrão do
|
|
54
|
+
* protocolo no HOME. Só vale pasta com CEREBRO.md.
|
|
55
|
+
* @param {Record<string,string>} configValues
|
|
56
|
+
* @returns {string} caminho do vault ou ''
|
|
57
|
+
*/
|
|
58
|
+
function findVault(configValues = {}) {
|
|
59
|
+
const candidates = [process.env.WIZZ_CEREBRO_VAULT, configValues.cerebro_vault, path.join(os.homedir(), DEFAULT_VAULT)];
|
|
60
|
+
for (const c of candidates) {
|
|
61
|
+
if (typeof c === 'string' && c.trim() && hasCerebro(expandHome(c.trim()))) return expandHome(c.trim());
|
|
62
|
+
}
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Arquivo `projetos/<nome>.md` do projeto: --projeto > project_name > pasta. */
|
|
67
|
+
function findProjectFile(vault, root, configValues, projeto) {
|
|
68
|
+
const names = [projeto, configValues.project_name, path.basename(root)].filter((n) => typeof n === 'string' && n.trim());
|
|
69
|
+
for (const n of names) {
|
|
70
|
+
for (const variant of new Set([n.trim(), lib.toSlug(n)])) {
|
|
71
|
+
const file = path.join(vault, 'projetos', `${variant}.md`);
|
|
72
|
+
if (variant && fs.existsSync(file)) return file;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const oneLine = (s) => s.replaceAll(/\s+/g, ' ').trim();
|
|
79
|
+
const cell = (s) => oneLine(s).replaceAll('|', String.raw`\|`);
|
|
80
|
+
|
|
81
|
+
/** Primeira linha útil da seção `## Implementação` da spec, ou ''. */
|
|
82
|
+
function implementationSummary(specText) {
|
|
83
|
+
const content = lib.sectionContent(specText || '', 'Implementa[çc][ãa]o') || '';
|
|
84
|
+
const first = content.split('\n').find((l) => l.trim()) || '';
|
|
85
|
+
const clean = oneLine(first.replace(/^[-*]\s+/, ''));
|
|
86
|
+
return clean.length > SUMMARY_MAX ? `${clean.slice(0, SUMMARY_MAX - 1)}…` : clean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Fim de uma seção `## X` que começa em `start` (próximo `#`/`##` ou EOF). */
|
|
90
|
+
function sectionEnd(lines, start) {
|
|
91
|
+
let end = start + 1;
|
|
92
|
+
while (end < lines.length && !/^#{1,2}\s/.test(lines[end])) end++;
|
|
93
|
+
return end;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const headingIndex = (lines, re) => lines.findIndex((l) => re.test(l.trim()));
|
|
97
|
+
|
|
98
|
+
/** Onde nasce um bloco Estado novo: abaixo do título e das linhas `>`. */
|
|
99
|
+
function estadoInsertAt(lines) {
|
|
100
|
+
const title = lines.findIndex((l) => /^#\s/.test(l));
|
|
101
|
+
if (title === -1) return 0;
|
|
102
|
+
let i = title + 1;
|
|
103
|
+
while (i < lines.length && lines[i].startsWith('>')) i++;
|
|
104
|
+
return i;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Atualiza (ou cria) o bloco `## Estado` com as duas linhas da CLI.
|
|
109
|
+
* @returns {string[]} linhas novas
|
|
110
|
+
*/
|
|
111
|
+
function upsertEstado(lines, { featureLine, sessionLine, slug }) {
|
|
112
|
+
const head = headingIndex(lines, /^##\s+Estado\s*$/i);
|
|
113
|
+
if (head === -1) {
|
|
114
|
+
const at = estadoInsertAt(lines);
|
|
115
|
+
const block = ['', '## Estado', featureLine, sessionLine];
|
|
116
|
+
const rest = lines.slice(at);
|
|
117
|
+
return [...lines.slice(0, at), ...block, ...(rest[0] === '' ? rest : ['', ...rest])];
|
|
118
|
+
}
|
|
119
|
+
const end = sectionEnd(lines, head);
|
|
120
|
+
const featureKey = `**Feature \`${slug}\`:**`;
|
|
121
|
+
let body = lines.slice(head + 1, end);
|
|
122
|
+
const featIdx = body.findIndex((l) => l.includes(featureKey));
|
|
123
|
+
if (featIdx === -1) body = [...(body[0] === '' ? [''] : []), featureLine, ...body.slice(body[0] === '' ? 1 : 0)];
|
|
124
|
+
else body = body.map((l, i) => (i === featIdx ? featureLine : l));
|
|
125
|
+
const sessIdx = body.findIndex((l) => /^\s*-\s+\*\*Última sessão:\*\*/.test(l));
|
|
126
|
+
if (sessIdx === -1) {
|
|
127
|
+
let last = body.length;
|
|
128
|
+
while (last > 0 && body[last - 1].trim() === '') last--;
|
|
129
|
+
body = [...body.slice(0, last), sessionLine, ...body.slice(last)];
|
|
130
|
+
} else body = body.map((l, i) => (i === sessIdx ? sessionLine : l));
|
|
131
|
+
return [...lines.slice(0, head + 1), ...body, ...lines.slice(end)];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Separa a tabela de uma seção: {header, rows, tableStart, tableEnd}. */
|
|
135
|
+
function readTable(lines, head) {
|
|
136
|
+
const end = sectionEnd(lines, head);
|
|
137
|
+
let start = head + 1;
|
|
138
|
+
while (start < end && !lines[start].startsWith('|')) start++;
|
|
139
|
+
if (start >= end || !lines[start + 1] || !/^\|[\s:|-]+\|?\s*$/.test(lines[start + 1])) return null;
|
|
140
|
+
let stop = start + 2;
|
|
141
|
+
while (stop < end && lines[stop].startsWith('|')) stop++;
|
|
142
|
+
return { header: lines.slice(start, start + 2), rows: lines.slice(start + 2, stop), tableStart: start, tableEnd: stop };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const isBlankRow = (row) => row.replaceAll(/[|\s]/g, '') === '';
|
|
146
|
+
|
|
147
|
+
const escapeRe = (text) => text.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Põe linhas antigas na seção `## Sessões antigas — <projeto>` (formato da skill cerebro) do arquivo
|
|
151
|
+
* morto (cria a seção no fim se faltar).
|
|
152
|
+
*/
|
|
153
|
+
function archiveRows(lines, overflow, header, projeto) {
|
|
154
|
+
if (overflow.length === 0) return lines;
|
|
155
|
+
const title = `## Sessões antigas — ${projeto}`;
|
|
156
|
+
const head = headingIndex(lines, new RegExp(`^${escapeRe(title)}\\s*$`));
|
|
157
|
+
if (head === -1) {
|
|
158
|
+
const base = lines.at(-1) === '' ? lines.slice(0, -1) : lines;
|
|
159
|
+
return [...base, '', title, ...header, ...overflow, ''];
|
|
160
|
+
}
|
|
161
|
+
const table = readTable(lines, head);
|
|
162
|
+
if (!table) return [...lines.slice(0, head + 1), ...header, ...overflow, ...lines.slice(head + 1)];
|
|
163
|
+
return [...lines.slice(0, table.tableEnd), ...overflow, ...lines.slice(table.tableEnd)];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Põe (ou substitui) a linha da feature na tabela Sessões, com teto.
|
|
168
|
+
* Chave de idempotência: mesma data + "`<slug>` encerrada".
|
|
169
|
+
* @returns {{lines: string[], overflow: string[], header: string[]}} overflow = linhas que saem para o arquivo morto
|
|
170
|
+
*/
|
|
171
|
+
function upsertSessoes(lines, { row, date, slug }) {
|
|
172
|
+
const head = headingIndex(lines, /^##\s+(Sess(õ|o)es|Sessions)\s*$/i);
|
|
173
|
+
const header = ['| Data | O que rolou |', '|---|---|'];
|
|
174
|
+
if (head === -1) {
|
|
175
|
+
const base = lines.at(-1) === '' ? lines.slice(0, -1) : lines;
|
|
176
|
+
return { lines: [...base, '', '## Sessões', ...header, row, ''], overflow: [], header };
|
|
177
|
+
}
|
|
178
|
+
const table = readTable(lines, head);
|
|
179
|
+
if (!table) return { lines: [...lines.slice(0, head + 1), ...header, row, ...lines.slice(head + 1)], overflow: [], header };
|
|
180
|
+
const key = `\`${slug}\` encerrada`;
|
|
181
|
+
const same = (r) => r.startsWith(`| ${date} |`) && r.includes(key);
|
|
182
|
+
let rows = table.rows.filter((r) => !isBlankRow(r));
|
|
183
|
+
rows = rows.some(same) ? rows.map((r) => (same(r) ? row : r)) : [...rows, row];
|
|
184
|
+
const overflow = rows.length > SESSIONS_CAP ? rows.slice(0, rows.length - SESSIONS_CAP) : [];
|
|
185
|
+
const kept = rows.slice(overflow.length);
|
|
186
|
+
const next = [...lines.slice(0, table.tableStart), ...table.header, ...kept, ...lines.slice(table.tableEnd)];
|
|
187
|
+
return { lines: next, overflow, header: table.header };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Grava as linhas que saíram da tabela no arquivo morto do vault. */
|
|
191
|
+
function writeArchive(vault, projeto, overflow, header) {
|
|
192
|
+
if (overflow.length === 0) return;
|
|
193
|
+
const file = path.join(vault, ARCHIVE_FILE);
|
|
194
|
+
const original = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : ARCHIVE_HEADER.join('\n');
|
|
195
|
+
const next = archiveRows(original.split('\n'), overflow, header, projeto).join('\n');
|
|
196
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
197
|
+
lib.atomicWrite(file, next);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Grava o fechamento no arquivo do projeto no Cérebro. Fail-open: qualquer
|
|
202
|
+
* falha devolve {gravado: false, motivo} sem lançar.
|
|
203
|
+
* @param {{root: string, slug: string, status: string, projeto?: string}} input
|
|
204
|
+
* @returns {{gravado: boolean, arquivo?: string, motivo?: string}}
|
|
205
|
+
*/
|
|
206
|
+
function recordClose({ root, slug, status, projeto }) {
|
|
207
|
+
try {
|
|
208
|
+
const { values } = lib.readConfig(root);
|
|
209
|
+
const vault = findVault(values);
|
|
210
|
+
if (!vault) return { gravado: false, motivo: 'vault do Cérebro não encontrado (WIZZ_CEREBRO_VAULT)' };
|
|
211
|
+
const file = findProjectFile(vault, root, values, projeto);
|
|
212
|
+
if (!file) return { gravado: false, motivo: `sem projetos/<projeto>.md no vault (use --projeto <nome>)` };
|
|
213
|
+
|
|
214
|
+
const date = lib.today();
|
|
215
|
+
const dirs = lib.artifactDirs(root);
|
|
216
|
+
const spec = path.join(dirs.planning, slug, `spec-${slug}.md`);
|
|
217
|
+
const specText = fs.existsSync(spec) ? fs.readFileSync(spec, 'utf8') : '';
|
|
218
|
+
const specRel = path.relative(root, spec);
|
|
219
|
+
const resumo = implementationSummary(specText);
|
|
220
|
+
const featureLine = `- **Feature \`${slug}\`:** ${status} em ${date}. Spec: \`${specRel}\`.${resumo ? ` ${resumo}` : ''}`;
|
|
221
|
+
const sessionLine = `- **Última sessão:** ${date} — feature \`${slug}\` encerrada (${status}).`;
|
|
222
|
+
const row = `| ${date} | Feature \`${slug}\` encerrada (${status}). ${cell(resumo || `Spec: ${specRel}`)} |`;
|
|
223
|
+
|
|
224
|
+
const original = fs.readFileSync(file, 'utf8');
|
|
225
|
+
let lines = original.split('\n');
|
|
226
|
+
lines = upsertEstado(lines, { featureLine, sessionLine, slug });
|
|
227
|
+
const sessoes = upsertSessoes(lines, { row, date, slug });
|
|
228
|
+
const next = sessoes.lines.join('\n');
|
|
229
|
+
// Arquivo morto antes do projeto: se a 2ª escrita falhar, a linha fica
|
|
230
|
+
// duplicada no arquivo morto em vez de sumir.
|
|
231
|
+
writeArchive(vault, path.basename(file, '.md'), sessoes.overflow, sessoes.header);
|
|
232
|
+
if (next !== original) lib.atomicWrite(file, next);
|
|
233
|
+
return { gravado: true, arquivo: file };
|
|
234
|
+
} catch (error) {
|
|
235
|
+
return { gravado: false, motivo: error.message };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = { findVault, findProjectFile, implementationSummary, upsertEstado, upsertSessoes, recordClose, SESSIONS_CAP };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// wizz-close-reminder.js — Stop hook: lembrete de fechamento de feature.
|
|
2
|
+
//
|
|
3
|
+
// Fonte de verdade: wizz-method/tools/hooks/. Instalado em `_wizz/hooks/`
|
|
4
|
+
// (installer) e em `~/.claude/hooks/` (sync:global).
|
|
5
|
+
//
|
|
6
|
+
// No fim de cada turno, se a feature ativa já foi ENTREGUE (spec com status
|
|
7
|
+
// `implementado` ou `no-ar`) e o checklist de fechamento do `wizz-feature.js`
|
|
8
|
+
// ainda tem pendência, mostra 1 aviso ao usuário. Antes isso dependia de o
|
|
9
|
+
// modelo lembrar de rodar `verificar`.
|
|
10
|
+
//
|
|
11
|
+
// Forma da saída: `{"systemMessage": "..."}` no stdout, exit 0. É aviso
|
|
12
|
+
// VISÍVEL ao usuário e não bloqueia: `decision: "block"` forçaria o modelo a
|
|
13
|
+
// continuar o turno, o que vira laço quando a pendência depende do dono.
|
|
14
|
+
//
|
|
15
|
+
// Caminho quente barato: sem subprocesso; sai cedo fora de projeto Wizz, sem
|
|
16
|
+
// feature ativa ou com a feature ainda em andamento (rascunho/aprovado).
|
|
17
|
+
// Silencioso quando não há pendência. Mesmo aviso não repete na mesma sessão
|
|
18
|
+
// (estado por session_id em os.tmpdir(), ou WIZZ_CLOSE_REMINDER_STATE).
|
|
19
|
+
// Fail-open: qualquer erro sai 0 em silêncio.
|
|
20
|
+
|
|
21
|
+
const fs = require('node:fs');
|
|
22
|
+
const os = require('node:os');
|
|
23
|
+
const path = require('node:path');
|
|
24
|
+
const crypto = require('node:crypto');
|
|
25
|
+
|
|
26
|
+
// Diagnóstico opt-in (WIZZ_DEBUG=1). Lib ausente numa cópia parcial: ainda avisa em stderr.
|
|
27
|
+
let debug = (hook, phase, err) => {
|
|
28
|
+
if (process.env.WIZZ_DEBUG !== '1') return;
|
|
29
|
+
try {
|
|
30
|
+
process.stderr.write(`[wizz-debug] ${hook} ${phase}: ${(err && err.message) || err}\n`);
|
|
31
|
+
} catch {
|
|
32
|
+
// stderr fechado
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
try {
|
|
36
|
+
({ debug } = require('./wizz-debug-lib.js'));
|
|
37
|
+
} catch (error) {
|
|
38
|
+
debug('close-reminder', 'require wizz-debug-lib', error);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const DELIVERED = new Set(['implementado', 'no-ar']);
|
|
42
|
+
const CLI = 'node _wizz/hooks/wizz-feature.js';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Texto do aviso para o projeto em `cwd`, ou '' quando não há o que avisar.
|
|
46
|
+
* @param {string} cwd
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function pendingMessage(cwd) {
|
|
50
|
+
const lib = require('./wizz-feature-lib.js');
|
|
51
|
+
const root = lib.findWizzRoot(cwd);
|
|
52
|
+
if (!root) return '';
|
|
53
|
+
const state = lib.resolveFeature(root);
|
|
54
|
+
if (!state.slug) return '';
|
|
55
|
+
const { artifactPath, checklist } = require('./wizz-feature.js');
|
|
56
|
+
const spec = artifactPath(root, 'spec', state.slug);
|
|
57
|
+
if (!fs.existsSync(spec)) return '';
|
|
58
|
+
const status = lib.parseFrontmatter(fs.readFileSync(spec, 'utf8')).data.status;
|
|
59
|
+
if (!DELIVERED.has(status)) return '';
|
|
60
|
+
const { pendentes } = checklist(state, state.slug);
|
|
61
|
+
const stillActive = status === 'no-ar' && state.fromConfig === state.slug;
|
|
62
|
+
if (pendentes.length === 0 && !stillActive) return '';
|
|
63
|
+
const what =
|
|
64
|
+
pendentes.length > 0
|
|
65
|
+
? `pendente: ${pendentes.map((p) => p.item + (p.detalhe ? ` (${p.detalhe})` : '')).join('; ')}`
|
|
66
|
+
: 'entregue mas ainda ativa';
|
|
67
|
+
return `⚠️ Fechamento da feature ${state.slug} (${status}) ${what}. Resolva, rode \`${CLI} verificar\` e feche com \`${CLI} encerrar --cerebro\`.`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function stateFile(sessionId) {
|
|
71
|
+
const dir = process.env.WIZZ_CLOSE_REMINDER_STATE || path.join(os.tmpdir(), 'wizz-close-reminder');
|
|
72
|
+
const safe = String(sessionId)
|
|
73
|
+
.replaceAll(/[^\w-]/g, '')
|
|
74
|
+
.slice(0, 80);
|
|
75
|
+
return safe ? path.join(dir, `${safe}.txt`) : '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** true se este aviso já saiu nesta sessão; senão grava e devolve false. */
|
|
79
|
+
function alreadyShown(sessionId, message) {
|
|
80
|
+
const file = sessionId ? stateFile(sessionId) : '';
|
|
81
|
+
if (!file) return false;
|
|
82
|
+
const hash = crypto.createHash('sha1').update(message).digest('hex');
|
|
83
|
+
try {
|
|
84
|
+
if (fs.readFileSync(file, 'utf8') === hash) return true;
|
|
85
|
+
} catch {
|
|
86
|
+
// primeira vez nesta sessão
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
90
|
+
fs.writeFileSync(file, hash);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
// sem dedup: avisa de novo no próximo turno, nunca falha
|
|
93
|
+
debug('close-reminder', 'alreadyShown write', error);
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function runHook() {
|
|
99
|
+
const chunks = [];
|
|
100
|
+
const timer = setTimeout(() => {
|
|
101
|
+
debug('close-reminder', 'stdin', 'timeout de 3s sem fim de stdin; saindo sem avisar');
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}, 3000);
|
|
104
|
+
process.stdin.on('data', (c) => chunks.push(c));
|
|
105
|
+
process.stdin.on('end', () => {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
try {
|
|
108
|
+
const payload = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}');
|
|
109
|
+
if (payload.stop_hook_active) return;
|
|
110
|
+
const cwd = payload.cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
111
|
+
const message = pendingMessage(cwd);
|
|
112
|
+
if (message && !alreadyShown(payload.session_id, message)) {
|
|
113
|
+
process.stdout.write(`${JSON.stringify({ systemMessage: message })}\n`);
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
// fail-open: payload ruim, projeto ilegível, nada disso derruba a sessão
|
|
117
|
+
debug('close-reminder', 'pendingMessage', error);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (require.main === module) runHook();
|
|
123
|
+
|
|
124
|
+
module.exports = { pendingMessage };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Diagnóstico opt-in dos hooks (fail-open continua valendo).
|
|
5
|
+
*
|
|
6
|
+
* Os hooks engolem erro de propósito: nada pode derrubar a sessão. O custo é
|
|
7
|
+
* que uma cópia parcial em `_wizz/hooks/` faz a feature sumir sem rastro.
|
|
8
|
+
* Com `WIZZ_DEBUG=1`, cada erro engolido vira UMA linha em stderr e é
|
|
9
|
+
* anexado a `~/.claude/wizz-debug.log` (`WIZZ_DEBUG_FILE` troca o caminho,
|
|
10
|
+
* é por onde os testes não encostam no home real).
|
|
11
|
+
* Sem `WIZZ_DEBUG=1`: zero saída, zero disco.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('node:fs');
|
|
15
|
+
const os = require('node:os');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
|
|
18
|
+
function isDebugEnabled() {
|
|
19
|
+
return process.env.WIZZ_DEBUG === '1';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function debugFilePath() {
|
|
23
|
+
return process.env.WIZZ_DEBUG_FILE || path.join(os.homedir(), '.claude', 'wizz-debug.log');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Formata a linha: `[wizz-debug] <hook> <fase>: <mensagem>` (sem quebra interna). */
|
|
27
|
+
function formatLine(hook, phase, err) {
|
|
28
|
+
const message = String((err && err.message) || err || 'erro desconhecido').replaceAll(/\s*\n\s*/g, ' ');
|
|
29
|
+
return `[wizz-debug] ${hook} ${phase}: ${message}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Registra um erro engolido. Nunca lança, nunca altera o exit code.
|
|
34
|
+
* @param {string} hook - nome do hook (ex.: 'session-rules')
|
|
35
|
+
* @param {string} phase - onde falhou (ex.: 'require wizz-feature-lib')
|
|
36
|
+
* @param {unknown} err
|
|
37
|
+
*/
|
|
38
|
+
function debug(hook, phase, err) {
|
|
39
|
+
if (!isDebugEnabled()) return;
|
|
40
|
+
const line = formatLine(hook, phase, err);
|
|
41
|
+
try {
|
|
42
|
+
process.stderr.write(`${line}\n`);
|
|
43
|
+
} catch {
|
|
44
|
+
// stderr fechado: o log em disco ainda tenta
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const file = debugFilePath();
|
|
48
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
49
|
+
fs.appendFileSync(file, `${new Date().toISOString()} ${line}\n`);
|
|
50
|
+
} catch {
|
|
51
|
+
// diagnóstico nunca derruba o hook
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { debug, isDebugEnabled, debugFilePath, formatLine };
|
|
@@ -56,6 +56,21 @@
|
|
|
56
56
|
// erro do hook: qualquer falha aqui (arquivo de offsets corrompido, disco
|
|
57
57
|
// cheio, permissão) nunca derruba o hook — fail-silent, volta a reler tudo.
|
|
58
58
|
|
|
59
|
+
// Diagnóstico opt-in (WIZZ_DEBUG=1). Lib ausente numa cópia parcial: ainda avisa em stderr.
|
|
60
|
+
let debug = (hook, phase, err) => {
|
|
61
|
+
if (process.env.WIZZ_DEBUG !== '1') return;
|
|
62
|
+
try {
|
|
63
|
+
process.stderr.write(`[wizz-debug] ${hook} ${phase}: ${(err && err.message) || err}\n`);
|
|
64
|
+
} catch {
|
|
65
|
+
// stderr fechado
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
({ debug } = require('./wizz-debug-lib.js'));
|
|
70
|
+
} catch (error) {
|
|
71
|
+
debug('decision-trace', 'require wizz-debug-lib', error);
|
|
72
|
+
}
|
|
73
|
+
|
|
59
74
|
const fs = require('node:fs');
|
|
60
75
|
const path = require('node:path');
|
|
61
76
|
const os = require('node:os');
|
|
@@ -294,8 +309,9 @@ function extractDecision(text) {
|
|
|
294
309
|
function appendDecision(entry) {
|
|
295
310
|
try {
|
|
296
311
|
fs.appendFileSync(getTraceFile(), JSON.stringify(entry) + '\n');
|
|
297
|
-
} catch {
|
|
312
|
+
} catch (error) {
|
|
298
313
|
// trace nunca pode derrubar o hook
|
|
314
|
+
debug('decision-trace', 'appendDecision', error);
|
|
299
315
|
}
|
|
300
316
|
}
|
|
301
317
|
|
|
@@ -358,8 +374,9 @@ function runHook() {
|
|
|
358
374
|
});
|
|
359
375
|
|
|
360
376
|
process.exit(0);
|
|
361
|
-
} catch {
|
|
377
|
+
} catch (error) {
|
|
362
378
|
// Falha em silêncio em qualquer etapa — nunca bloqueia a sessão.
|
|
379
|
+
debug('decision-trace', 'runHook', error);
|
|
363
380
|
process.exit(0);
|
|
364
381
|
}
|
|
365
382
|
});
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
// status <status> rascunho|aprovado|implementado|no-ar na spec
|
|
14
14
|
// verificar checklist de fechamento (exit 1 se houver pendência)
|
|
15
15
|
// encerrar status final na spec, limpa/troca active_feature
|
|
16
|
-
// (recusa com pendência do checklist, salvo --forcar)
|
|
16
|
+
// (recusa com pendência do checklist, salvo --forcar);
|
|
17
|
+
// com --cerebro grava Estado + linha de Sessões no
|
|
18
|
+
// projetos/<projeto>.md do Cérebro (--projeto <nome>)
|
|
17
19
|
// listar features com pasta e o status de cada spec
|
|
18
20
|
//
|
|
19
21
|
// Opções: --json (saída para máquina), --cwd <dir>, --feature <slug>.
|
|
@@ -22,6 +24,7 @@
|
|
|
22
24
|
const fs = require('node:fs');
|
|
23
25
|
const path = require('node:path');
|
|
24
26
|
const lib = require('./wizz-feature-lib.js');
|
|
27
|
+
const cerebro = require('./wizz-cerebro-lib.js');
|
|
25
28
|
|
|
26
29
|
const ALIASES = {
|
|
27
30
|
resolve: 'resolver',
|
|
@@ -286,7 +289,7 @@ function checklist(state, slug, { statusOverride } = {}) {
|
|
|
286
289
|
}
|
|
287
290
|
const pendentes = items.filter((i) => !i.ok);
|
|
288
291
|
const confirmar = [
|
|
289
|
-
'Cérebro: bloco Estado do projeto atualizado + 1 linha na tabela Sessões',
|
|
292
|
+
'Cérebro: bloco Estado do projeto atualizado + 1 linha na tabela Sessões (`encerrar --cerebro` grava sozinho)',
|
|
290
293
|
'Auto-memória: só armadilha reproduzível ou preferência do dono (estado e decisão vão no Cérebro)',
|
|
291
294
|
];
|
|
292
295
|
return { items, pendentes, confirmar };
|
|
@@ -305,6 +308,12 @@ function cmdVerificar(state, opts) {
|
|
|
305
308
|
};
|
|
306
309
|
}
|
|
307
310
|
|
|
311
|
+
/** `--cerebro`: grava no vault; fail-open, nunca desfaz o encerramento. */
|
|
312
|
+
function cerebroClose(state, slug, status, opts) {
|
|
313
|
+
const projeto = typeof opts.projeto === 'string' ? opts.projeto : '';
|
|
314
|
+
return cerebro.recordClose({ root: state.root, slug, status, projeto });
|
|
315
|
+
}
|
|
316
|
+
|
|
308
317
|
function cmdEncerrar(state, opts) {
|
|
309
318
|
const slug = requireFeature(state, opts);
|
|
310
319
|
if (!slug) return { ok: false, code: 2, line: 'Nenhuma feature ativa para encerrar.' };
|
|
@@ -330,9 +339,16 @@ function cmdEncerrar(state, opts) {
|
|
|
330
339
|
const parts = [`Feature ${slug} encerrada (${status}) em ${lib.today()}. Spec: ${rel(state.root, file)}.`];
|
|
331
340
|
parts.push(next ? `Feature ativa agora: ${next}.` : 'Nenhuma feature ativa agora.');
|
|
332
341
|
if (state.fromBranch === slug) parts.push(`O branch ${state.git.branch} ainda aponta para ${slug}: volte ao tronco ou troque de branch.`);
|
|
333
|
-
|
|
342
|
+
const memo = opts.cerebro ? cerebroClose(state, slug, status, opts) : null;
|
|
343
|
+
if (memo && memo.gravado) parts.push(`Cérebro atualizado: ${memo.arquivo} (Estado + linha em Sessões).`);
|
|
344
|
+
else {
|
|
345
|
+
if (memo) parts.push(`Cérebro não gravado (${memo.motivo}).`);
|
|
346
|
+
parts.push(`Registre no Cérebro: Estado do projeto + linha na tabela Sessões (${slug}: ${status} em ${lib.today()}).`);
|
|
347
|
+
}
|
|
334
348
|
if (pend.length > 0) parts.push(`Pendências: ${pend.map((p) => p.item).join('; ')}.`);
|
|
335
|
-
|
|
349
|
+
const data = { feature: slug, status, proxima: next, pendencias: pend.map((p) => p.item) };
|
|
350
|
+
if (memo) data.cerebro = memo;
|
|
351
|
+
return { ok: true, data, line: parts.join(' ') };
|
|
336
352
|
}
|
|
337
353
|
|
|
338
354
|
function cmdListar(state) {
|