wizz-method 2.17.2 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +12 -0
- package/package.json +5 -3
- package/removals.txt +10 -0
- package/skills-registry.yaml +72 -42
- 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 +5 -3
- 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/despacho-barato.md +1 -1
- package/src/modules/wizz/_shared/encerramento.md +4 -3
- package/src/modules/wizz/_shared/model-ladder.md +10 -0
- 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 +13 -3
- 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 +7 -9
- 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 +5 -3
- package/src/modules/wizz/agents/wizz-social/customize.toml +12 -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/modules/wizz/subagents/codex/wizz-exec-standard.toml +1 -1
- package/src/modules/wizz/subagents/codex/wizz-exec-strong.toml +1 -1
- package/src/modules/wizz/subagents/gemini/wizz-exec-standard.md +1 -1
- package/src/modules/wizz/subagents/gemini/wizz-exec-strong.md +1 -1
- package/src/modules/wizz/subagents/opencode/wizz-exec-standard.md +1 -1
- package/src/modules/wizz/subagents/opencode/wizz-exec-strong.md +1 -1
- package/src/modules/wizz/subagents/spec.yaml +6 -6
- package/src/modules/wizz/subagents/wizz-exec-standard.md +1 -1
- package/src/modules/wizz/subagents/wizz-exec-strong.md +1 -1
- 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-hook-extractor/LICENSE +21 -0
- package/src/skills-lib/wizz-hook-extractor/SKILL.md +56 -0
- package/src/skills-lib/wizz-hook-extractor/references/examples.md +59 -0
- package/src/skills-lib/wizz-hook-extractor/references/formulas.md +136 -0
- package/src/skills-lib/wizz-hook-extractor/scripts/read-post.sh +83 -0
- 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/LICENSE +21 -0
- package/src/skills-lib/wizz-ig-profile-optimizer/SKILL.md +74 -0
- package/src/skills-lib/wizz-ig-profile-optimizer/scripts/capture-profile.sh +41 -0
- package/src/skills-lib/wizz-ig-profile-optimizer/scripts/limits.mjs +82 -0
- 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 +11 -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 +4 -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 +11 -5
- package/src/skills-lib/wizz-video-styles/data/recipes.json +65 -2
- package/src/skills-lib/wizz-video-styles/data/references.json +386 -1
- package/src/skills-lib/wizz-video-styles/data/styles.json +109 -0
- package/src/skills-lib/wizz-video-styles/references/generated-prompts.md +70 -0
- package/src/skills-lib/wizz-video-styles/references/measured-patterns.md +2 -0
- package/src/skills-lib/wizz-video-styles/references/style-picker.md +8 -4
- package/src/skills-lib/wizz-video-styles/references/styles/tipografia-cinetica.md +68 -0
- package/src/skills-lib/wizz-video-styles/references/styles/ui-morph.md +68 -0
- package/src/skills-lib/wizz-video-styles/scripts/analyze.sh +23 -10
- package/src/skills-lib/wizz-video-styles/scripts/beat_grid.py +68 -0
- package/src/skills-lib/wizz-video-styles/scripts/references.mjs +265 -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
|
@@ -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) {
|
|
@@ -120,17 +120,14 @@ function findWizzRoot(startDir) {
|
|
|
120
120
|
return null;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// Gerado por tools/generate-dispatch-rule.mjs a partir de
|
|
124
|
+
// src/skills-lib/_shared/dispatch-rule.yaml (`hook_hint`). Não edite a linha
|
|
125
|
+
// abaixo: edite o yaml e rode `npm run generate:dispatch-rule`.
|
|
126
|
+
const WIZZ_PROJECT_HINT =
|
|
127
|
+
'🎛️ Wizz: NÃO invoque wizz-router. Capacidades → rel necessário à entrega → áreas → atalhos. 2+ áreas → `wizz-maestro`. 1 área: 2+ fatores (multi-passo, planejar, artefato memorável) → `wizz-maestro`; senão → Skill da área. Auth/pagamento/permissão/migração → maestro. Personas são skills, não subagent_type; Agent usa tipos disponíveis wizz-exec-*; difícil → wizz-exec-strong (ESCALAR → ultra).';
|
|
128
|
+
|
|
123
129
|
function buildContext(isWizzProject) {
|
|
124
|
-
if (isWizzProject)
|
|
125
|
-
return (
|
|
126
|
-
'🎛️ Wizz: NÃO invoque wizz-router. Capacidades → rel necessário à entrega → áreas → atalhos. 2+ áreas → `wizz-maestro`. ' +
|
|
127
|
-
'1 área: 2+ fatores (multi-passo, planejar, artefato memorável) → `wizz-maestro`; ' +
|
|
128
|
-
'senão → Skill da área. ' +
|
|
129
|
-
'Auth/pagamento/permissão/migração → maestro. ' +
|
|
130
|
-
'Personas são skills, não subagent_type; Agent usa tipos disponíveis wizz-exec-*' +
|
|
131
|
-
'; difícil → wizz-exec-strong (ESCALAR → ultra).'
|
|
132
|
-
);
|
|
133
|
-
}
|
|
130
|
+
if (isWizzProject) return WIZZ_PROJECT_HINT;
|
|
134
131
|
return (
|
|
135
132
|
'🎛️ Pedido não-trivial: invoque a Skill `wizz-router` antes de agir para ' +
|
|
136
133
|
'mapear skills/MCPs (liste candidatas via AskUserQuestion e confirme). ' +
|
|
@@ -138,6 +135,21 @@ function buildContext(isWizzProject) {
|
|
|
138
135
|
);
|
|
139
136
|
}
|
|
140
137
|
|
|
138
|
+
// Diagnóstico opt-in (WIZZ_DEBUG=1). Lib ausente numa cópia parcial: ainda avisa em stderr.
|
|
139
|
+
let debug = (hook, phase, err) => {
|
|
140
|
+
if (process.env.WIZZ_DEBUG !== '1') return;
|
|
141
|
+
try {
|
|
142
|
+
process.stderr.write(`[wizz-debug] ${hook} ${phase}: ${(err && err.message) || err}\n`);
|
|
143
|
+
} catch {
|
|
144
|
+
// stderr fechado
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
try {
|
|
148
|
+
({ debug } = require('./wizz-debug-lib.js'));
|
|
149
|
+
} catch (error) {
|
|
150
|
+
debug('router-enforce', 'require wizz-debug-lib', error);
|
|
151
|
+
}
|
|
152
|
+
|
|
141
153
|
// Linha de feature, por prompt, em projeto Wizz (2026-10-02, caso Spider).
|
|
142
154
|
// O SessionStart avisa 1x, mas é aqui que o pedido nomeado chega: sem este
|
|
143
155
|
// lembrete o despacho saía sem feature e o artefato caía na raiz. Fail-open:
|
|
@@ -152,7 +164,8 @@ function featureLine(wizzRoot) {
|
|
|
152
164
|
return ` Feature: nenhuma ativa. Pedido nomeado → ANTES de despachar \`${cmd} definir <slug do pedido>\` (sem perguntar); handoff leva \`feature:\`.`;
|
|
153
165
|
}
|
|
154
166
|
return ` Feature: ${state.slug}. Outro assunto → \`${cmd} definir <novo>\`; continuação mantém; entrega fechada → \`${cmd} encerrar\`. Handoff leva \`feature: ${state.slug}\`.`;
|
|
155
|
-
} catch {
|
|
167
|
+
} catch (error) {
|
|
168
|
+
debug('router-enforce', 'featureLine', error);
|
|
156
169
|
return '';
|
|
157
170
|
}
|
|
158
171
|
}
|
|
@@ -197,8 +210,9 @@ function checkFallbackWarnings(isWizzProject, wizzRoot) {
|
|
|
197
210
|
warnings.push(`routing-table-flat.md não encontrada em ${flatTablePath} — rode "npm run sync:global" no repo wizz-method`);
|
|
198
211
|
}
|
|
199
212
|
}
|
|
200
|
-
} catch {
|
|
213
|
+
} catch (error) {
|
|
201
214
|
// fail-silent — checagem de fallback nunca bloqueia o prompt
|
|
215
|
+
debug('router-enforce', 'checkFallbackWarnings', error);
|
|
202
216
|
}
|
|
203
217
|
return warnings;
|
|
204
218
|
}
|
|
@@ -218,7 +232,10 @@ function emitFallbackWarnings(warnings) {
|
|
|
218
232
|
|
|
219
233
|
function runHook() {
|
|
220
234
|
let input = '';
|
|
221
|
-
const stdinTimeout = setTimeout(() =>
|
|
235
|
+
const stdinTimeout = setTimeout(() => {
|
|
236
|
+
debug('router-enforce', 'stdin', 'timeout de 3s sem fim de stdin; saindo sem rotear');
|
|
237
|
+
process.exit(0);
|
|
238
|
+
}, 3000);
|
|
222
239
|
process.stdin.setEncoding('utf8');
|
|
223
240
|
process.stdin.on('data', (chunk) => (input += chunk));
|
|
224
241
|
process.stdin.on('end', () => {
|