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
|
@@ -15,6 +15,7 @@ const yaml = require('yaml');
|
|
|
15
15
|
const fs = require('../fs-native');
|
|
16
16
|
const prompts = require('../prompts');
|
|
17
17
|
const { getProjectRoot } = require('../project-root');
|
|
18
|
+
const { it, useCommandLang } = require('../install-text');
|
|
18
19
|
|
|
19
20
|
function getTraceFile() {
|
|
20
21
|
return process.env.WIZZ_TRACE_FILE || path.join(os.homedir(), '.claude', 'wizz-trace.jsonl');
|
|
@@ -36,8 +37,8 @@ function getRegistryFile() {
|
|
|
36
37
|
// Lê e parseia skills-registry.yaml, devolvendo o catálogo de ids de
|
|
37
38
|
// skills em ordem alfabética estável (mesma lista independente da ordem em
|
|
38
39
|
// que as áreas/skills aparecem no YAML). Escopo deliberado: só as skills
|
|
39
|
-
// `areas.*.skills[]` e as cross-cutting `utility[]` (find-skills,
|
|
40
|
-
// enhance-prompt, wizz-router; também são skills de verdade em
|
|
40
|
+
// `areas.*.skills[]` e as cross-cutting `utility[]` (wizz-find-skills,
|
|
41
|
+
// wizz-enhance-prompt, wizz-router; também são skills de verdade em
|
|
41
42
|
// src/skills-lib/). CLIs/MCPs/squads ficam de fora: `sel`/`desc` no
|
|
42
43
|
// marcador de decisão podem citar agentes ou skills (ver encerramento.md),
|
|
43
44
|
// mas este comando mede especificamente subutilização de SKILL, não de
|
|
@@ -122,20 +123,20 @@ function aggregateCoverage(entries, catalogIds) {
|
|
|
122
123
|
function formatCoverageSummary(stats, registryFile) {
|
|
123
124
|
const consideredCount = stats.catalogTotal - stats.neverConsidered.length;
|
|
124
125
|
const lines = [
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
it('trCatalog', { n: stats.catalogTotal, file: registryFile }),
|
|
127
|
+
it('trDecisions', { n: stats.decisionCount }),
|
|
128
|
+
it('trSelected', { n: stats.selectedCount, pct: pct(stats.selectedCount, stats.catalogTotal) }),
|
|
129
|
+
it('trDiscarded', { n: stats.discardedOnlyCount, pct: pct(stats.discardedOnlyCount, stats.catalogTotal) }),
|
|
130
|
+
it('trNever', { n: stats.neverConsidered.length, pct: pct(stats.neverConsidered.length, stats.catalogTotal) }),
|
|
131
|
+
it('trCoverage', { pct: pct(consideredCount, stats.catalogTotal) }),
|
|
131
132
|
];
|
|
132
133
|
|
|
133
134
|
if (stats.decisionCount === 0) {
|
|
134
|
-
lines.push('',
|
|
135
|
+
lines.push('', it('trNoDecisions'));
|
|
135
136
|
} else if (stats.neverConsidered.length === 0) {
|
|
136
|
-
lines.push('', '
|
|
137
|
+
lines.push('', it('trNeverNone'));
|
|
137
138
|
} else {
|
|
138
|
-
lines.push('', '
|
|
139
|
+
lines.push('', it('trNeverHeader'));
|
|
139
140
|
for (const id of stats.neverConsidered) lines.push(` ${id}`);
|
|
140
141
|
}
|
|
141
142
|
|
|
@@ -293,18 +294,18 @@ function pct(part, total) {
|
|
|
293
294
|
|
|
294
295
|
function formatSummary(stats, traceFile) {
|
|
295
296
|
const lines = [
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
297
|
+
it('trFile', { file: traceFile }),
|
|
298
|
+
it('trTotal', { n: stats.total }),
|
|
299
|
+
it('trTrivial', { n: stats.trivialCount }),
|
|
300
|
+
it('trRouted', { n: stats.routedCount }),
|
|
301
|
+
it('trModeWizz', { n: stats.byMode.wizz }),
|
|
302
|
+
it('trModeFlat', { n: stats.byMode.flat }),
|
|
303
|
+
it('trModeNull', { n: stats.byMode.null }),
|
|
304
|
+
it('trWarnings', { n: stats.warningsCount }),
|
|
305
|
+
it('trPeriod', { first: stats.firstTs || '—', last: stats.lastTs || '—' }),
|
|
305
306
|
];
|
|
306
307
|
if (stats.corruptedCount > 0) {
|
|
307
|
-
lines.push(
|
|
308
|
+
lines.push(it('trCorrupted', { n: stats.corruptedCount }));
|
|
308
309
|
}
|
|
309
310
|
return lines.join('\n');
|
|
310
311
|
}
|
|
@@ -316,14 +317,14 @@ const ROUTE_DISPLAY_ORDER = ['agent:*', 'maestro', 'flat', 'outro'];
|
|
|
316
317
|
function formatLadderSummary(ladderStats) {
|
|
317
318
|
const byTier = ladderStats.byTier || {};
|
|
318
319
|
const lines = [
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
'
|
|
320
|
+
it('trLadderTotal', { n: ladderStats.total }),
|
|
321
|
+
it('trLadderExec', { n: ladderStats.withExec, pct: pct(ladderStats.withExec, ladderStats.total) }),
|
|
322
|
+
it('trByRoute'),
|
|
322
323
|
];
|
|
323
324
|
|
|
324
325
|
const routes = ROUTE_DISPLAY_ORDER.filter((route) => ladderStats.byRoute[route]);
|
|
325
326
|
if (routes.length === 0) {
|
|
326
|
-
lines.push(
|
|
327
|
+
lines.push(it('trNoData'));
|
|
327
328
|
} else {
|
|
328
329
|
for (const route of routes) {
|
|
329
330
|
const bucket = ladderStats.byRoute[route];
|
|
@@ -331,10 +332,10 @@ function formatLadderSummary(ladderStats) {
|
|
|
331
332
|
}
|
|
332
333
|
}
|
|
333
334
|
|
|
334
|
-
lines.push('
|
|
335
|
+
lines.push(it('trByTier'));
|
|
335
336
|
const tiers = TIER_DISPLAY_ORDER.filter((tier) => byTier[tier]);
|
|
336
337
|
if (tiers.length === 0) {
|
|
337
|
-
lines.push(
|
|
338
|
+
lines.push(it('trNoData'));
|
|
338
339
|
} else {
|
|
339
340
|
for (const tier of tiers) {
|
|
340
341
|
lines.push(` ${tier.padEnd(10)} ${byTier[tier]}`);
|
|
@@ -346,16 +347,18 @@ function formatLadderSummary(ladderStats) {
|
|
|
346
347
|
|
|
347
348
|
module.exports = {
|
|
348
349
|
command: 'trace-report',
|
|
349
|
-
description: '
|
|
350
|
-
options: [['--coverage', '
|
|
350
|
+
description: 'Summarize the local routing trace file (WIZZ_TRACE)',
|
|
351
|
+
options: [['--coverage', 'Cross the registry skill catalog with the traces: measures skills never considered in routing']],
|
|
351
352
|
action: async (options) => {
|
|
352
353
|
try {
|
|
354
|
+
const wizzDir = path.join(process.cwd(), '_wizz');
|
|
355
|
+
useCommandLang(fs.existsSync(wizzDir) ? wizzDir : null);
|
|
353
356
|
const traceFile = getTraceFile();
|
|
354
357
|
|
|
355
358
|
if (!(await fs.pathExists(traceFile))) {
|
|
356
|
-
await prompts.log.warn('
|
|
357
|
-
await prompts.log.message(
|
|
358
|
-
await prompts.log.message('
|
|
359
|
+
await prompts.log.warn(it('trNoTrace'));
|
|
360
|
+
await prompts.log.message(it('trExpected', { file: traceFile }));
|
|
361
|
+
await prompts.log.message(it('trHowTo'));
|
|
359
362
|
process.exit(0);
|
|
360
363
|
return;
|
|
361
364
|
}
|
|
@@ -386,14 +389,14 @@ module.exports = {
|
|
|
386
389
|
try {
|
|
387
390
|
catalogIds = collectSkillCatalog(registryFile);
|
|
388
391
|
} catch (error) {
|
|
389
|
-
await prompts.log.error(
|
|
390
|
-
await prompts.log.message(
|
|
392
|
+
await prompts.log.error(it('trCatalogFail', { error: error.message }));
|
|
393
|
+
await prompts.log.message(it('trCatalogExpected', { file: registryFile }));
|
|
391
394
|
process.exit(1);
|
|
392
395
|
return;
|
|
393
396
|
}
|
|
394
397
|
|
|
395
398
|
const coverageStats = aggregateCoverage(entries, catalogIds);
|
|
396
|
-
await prompts.box(formatCoverageSummary(coverageStats, registryFile), '
|
|
399
|
+
await prompts.box(formatCoverageSummary(coverageStats, registryFile), it('trCoverageTitle'));
|
|
397
400
|
process.exit(0);
|
|
398
401
|
return;
|
|
399
402
|
}
|
|
@@ -403,11 +406,11 @@ module.exports = {
|
|
|
403
406
|
await prompts.box(formatSummary(stats, traceFile), 'Wizz Trace Report');
|
|
404
407
|
|
|
405
408
|
const ladderStats = aggregateLadder(entries);
|
|
406
|
-
await prompts.box(formatLadderSummary(ladderStats), '
|
|
409
|
+
await prompts.box(formatLadderSummary(ladderStats), it('trLadderTitle'));
|
|
407
410
|
|
|
408
411
|
process.exit(0);
|
|
409
412
|
} catch (error) {
|
|
410
|
-
await prompts.log.error(
|
|
413
|
+
await prompts.log.error(it('trFailed', { error: error.message }));
|
|
411
414
|
if (process.env.WIZZ_DEBUG) {
|
|
412
415
|
await prompts.log.message(error.stack);
|
|
413
416
|
}
|
|
@@ -23,13 +23,15 @@
|
|
|
23
23
|
const path = require('node:path');
|
|
24
24
|
const { HOOK_DEFS, SUPPORT_FILES, isOurHookCommand } = require('./hooks-settings');
|
|
25
25
|
const { classifyPython, PYTHON_ONLY_SKILLS } = require('./python-check');
|
|
26
|
+
const { checkHooksRun } = require('./hooks-smoke');
|
|
27
|
+
const { it } = require('../install-text');
|
|
26
28
|
|
|
27
29
|
// Piso de versão do rtk que o hook rtk-rewrite.sh exige para `rtk rewrite`
|
|
28
30
|
// (o registry pin do CLI é mais alto, 0.50.0, e é aplicado pelo detectClis
|
|
29
31
|
// no install; aqui reportamos o piso que quebra o hook hoje).
|
|
30
32
|
const RTK_HOOK_FLOOR = '0.23.0';
|
|
31
33
|
|
|
32
|
-
const CHECKS = ['hooks', 'rtk', 'jq', 'python', 'drift', 'resolved'];
|
|
34
|
+
const CHECKS = ['hooks', 'hooks-run', 'rtk', 'jq', 'python', 'drift', 'resolved'];
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* Versão numérica comparável a partir de texto de `rtk --version` etc.
|
|
@@ -177,7 +179,7 @@ function checkHooks({ wizzDir, settingsFile, globalSettingsFile, fileExists, rea
|
|
|
177
179
|
return {
|
|
178
180
|
check: 'hooks',
|
|
179
181
|
status: 'error',
|
|
180
|
-
detail:
|
|
182
|
+
detail: it('docHooksMissingFiles', { list: missingFiles.join(', ') }),
|
|
181
183
|
};
|
|
182
184
|
}
|
|
183
185
|
|
|
@@ -185,18 +187,19 @@ function checkHooks({ wizzDir, settingsFile, globalSettingsFile, fileExists, rea
|
|
|
185
187
|
return {
|
|
186
188
|
check: 'hooks',
|
|
187
189
|
status: 'warn',
|
|
188
|
-
detail:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
detail: it('docHooksNotRegistered', {
|
|
191
|
+
settings: settingsFile,
|
|
192
|
+
list: missingSettings.join(', '),
|
|
193
|
+
global: globalSettingsFile || '~/.claude/settings.json',
|
|
194
|
+
}),
|
|
192
195
|
};
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
const skippedNote =
|
|
196
199
|
skippedGlobal.length > 0
|
|
197
|
-
?
|
|
200
|
+
? it('docHooksSkippedNote', { n: skippedGlobal.length, global: globalSettingsFile || '~/.claude/settings.json' })
|
|
198
201
|
: '';
|
|
199
|
-
return { check: 'hooks', status: 'ok', detail:
|
|
202
|
+
return { check: 'hooks', status: 'ok', detail: it('docHooksOk', { n: HOOK_DEFS.length, note: skippedNote }) };
|
|
200
203
|
}
|
|
201
204
|
|
|
202
205
|
/**
|
|
@@ -214,7 +217,7 @@ function checkCli({ check, version, floor }) {
|
|
|
214
217
|
return {
|
|
215
218
|
check,
|
|
216
219
|
status: 'warn',
|
|
217
|
-
detail:
|
|
220
|
+
detail: it('docRtkMissing'),
|
|
218
221
|
};
|
|
219
222
|
}
|
|
220
223
|
const installed = extractVersion(version);
|
|
@@ -222,10 +225,10 @@ function checkCli({ check, version, floor }) {
|
|
|
222
225
|
return {
|
|
223
226
|
check,
|
|
224
227
|
status: 'warn',
|
|
225
|
-
detail:
|
|
228
|
+
detail: it('docRtkOld', { installed, floor: RTK_HOOK_FLOOR }),
|
|
226
229
|
};
|
|
227
230
|
}
|
|
228
|
-
return { check, status: 'ok', detail:
|
|
231
|
+
return { check, status: 'ok', detail: it('docRtkOk', { version: installed || it('docVersionUnknown') }) };
|
|
229
232
|
}
|
|
230
233
|
|
|
231
234
|
// jq — sem verificação de versão, o hook só precisa dele presente.
|
|
@@ -233,11 +236,10 @@ function checkCli({ check, version, floor }) {
|
|
|
233
236
|
return {
|
|
234
237
|
check: 'jq',
|
|
235
238
|
status: 'warn',
|
|
236
|
-
detail:
|
|
237
|
-
'jq não encontrado no PATH. O hook rtk-rewrite.sh precisa dele; sem jq o rewrite de Bash avisa e segue sem reescrever. Instale: brew install jq',
|
|
239
|
+
detail: it('docJqMissing'),
|
|
238
240
|
};
|
|
239
241
|
}
|
|
240
|
-
return { check: 'jq', status: 'ok', detail: '
|
|
242
|
+
return { check: 'jq', status: 'ok', detail: it('docJqOk') };
|
|
241
243
|
}
|
|
242
244
|
|
|
243
245
|
/**
|
|
@@ -266,20 +268,13 @@ function checkPython({ detected }) {
|
|
|
266
268
|
* @returns {string}
|
|
267
269
|
*/
|
|
268
270
|
function pythonDetail(status, detected) {
|
|
269
|
-
const optional =
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (status === '
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (status === 'partial') {
|
|
277
|
-
return `Python ${detected.version.raw} no PATH, sem tomllib (as skills com script próprio pedem 3.11+). ${optional}`;
|
|
278
|
-
}
|
|
279
|
-
if (status === 'unsupported') {
|
|
280
|
-
return `Python ${detected.version.raw} é antigo demais (as skills com script próprio pedem 3.11+). ${optional}`;
|
|
281
|
-
}
|
|
282
|
-
return `python3 não encontrado no PATH. ${optional} Para tê-las: brew install python3 (macOS).`;
|
|
271
|
+
const optional = it('docPyOptional', { skills: PYTHON_ONLY_SKILLS });
|
|
272
|
+
const vars = detected ? { version: detected.version.raw, command: detected.command, optional } : { optional };
|
|
273
|
+
if (status === 'full' && detected.isRuntimeCommand) return it('docPyFull', vars);
|
|
274
|
+
if (status === 'full') return it('docPyFullNoPython3', vars);
|
|
275
|
+
if (status === 'partial') return it('docPyPartial', vars);
|
|
276
|
+
if (status === 'unsupported') return it('docPyUnsupported', vars);
|
|
277
|
+
return it('docPyNone', vars);
|
|
283
278
|
}
|
|
284
279
|
|
|
285
280
|
/**
|
|
@@ -301,7 +296,7 @@ async function checkDrift({ wizzDir, manifestContent, hashFile, fileExists }) {
|
|
|
301
296
|
return {
|
|
302
297
|
check: 'drift',
|
|
303
298
|
status: 'warn',
|
|
304
|
-
detail: '
|
|
299
|
+
detail: it('docDriftNoManifest'),
|
|
305
300
|
};
|
|
306
301
|
}
|
|
307
302
|
|
|
@@ -312,7 +307,7 @@ async function checkDrift({ wizzDir, manifestContent, hashFile, fileExists }) {
|
|
|
312
307
|
.map(parseCsvLine)
|
|
313
308
|
.filter((f) => f.length >= 5 && f[3] && f[4]);
|
|
314
309
|
if (rows.length === 0) {
|
|
315
|
-
return { check: 'drift', status: 'ok', detail: '
|
|
310
|
+
return { check: 'drift', status: 'ok', detail: it('docDriftEmpty') };
|
|
316
311
|
}
|
|
317
312
|
|
|
318
313
|
const drifted = [];
|
|
@@ -332,16 +327,16 @@ async function checkDrift({ wizzDir, manifestContent, hashFile, fileExists }) {
|
|
|
332
327
|
}
|
|
333
328
|
|
|
334
329
|
if (drifted.length === 0 && missing.length === 0) {
|
|
335
|
-
return { check: 'drift', status: 'ok', detail:
|
|
330
|
+
return { check: 'drift', status: 'ok', detail: it('docDriftOk', { n: rows.length }) };
|
|
336
331
|
}
|
|
337
332
|
|
|
338
333
|
const parts = [];
|
|
339
|
-
if (drifted.length > 0) parts.push(
|
|
340
|
-
if (missing.length > 0) parts.push(
|
|
334
|
+
if (drifted.length > 0) parts.push(it('docDriftChanged', { n: drifted.length }));
|
|
335
|
+
if (missing.length > 0) parts.push(it('docDriftMissing', { n: missing.length }));
|
|
341
336
|
return {
|
|
342
337
|
check: 'drift',
|
|
343
338
|
status: 'warn',
|
|
344
|
-
detail:
|
|
339
|
+
detail: it('docDriftWarn', { parts: parts.join(it('docDriftAnd')), examples: [...drifted, ...missing].slice(0, 3).join(', ') }),
|
|
345
340
|
};
|
|
346
341
|
}
|
|
347
342
|
|
|
@@ -380,14 +375,14 @@ async function checkResolved({ projectDir, skillSources, index, fileExists, hash
|
|
|
380
375
|
const sources = skillSources instanceof Map ? skillSources : new Map(Object.entries(skillSources || {}));
|
|
381
376
|
const skills = [...sources.keys()].sort();
|
|
382
377
|
if (skills.length === 0) {
|
|
383
|
-
return { check: 'resolved', status: 'ok', detail: '
|
|
378
|
+
return { check: 'resolved', status: 'ok', detail: it('docResolvedNone') };
|
|
384
379
|
}
|
|
385
380
|
|
|
386
381
|
if (!index || typeof index !== 'object' || !index.skills || typeof index.skills !== 'object') {
|
|
387
382
|
return {
|
|
388
383
|
check: 'resolved',
|
|
389
384
|
status: 'warn',
|
|
390
|
-
detail:
|
|
385
|
+
detail: it('docResolvedNoIndex', { file: path.join(resolvedDir, '.index.json'), n: skills.length }),
|
|
391
386
|
};
|
|
392
387
|
}
|
|
393
388
|
|
|
@@ -426,14 +421,14 @@ async function checkResolved({ projectDir, skillSources, index, fileExists, hash
|
|
|
426
421
|
return {
|
|
427
422
|
check: 'resolved',
|
|
428
423
|
status: 'ok',
|
|
429
|
-
detail:
|
|
424
|
+
detail: it('docResolvedOk', { n: skills.length }),
|
|
430
425
|
};
|
|
431
426
|
}
|
|
432
427
|
|
|
433
428
|
const parts = [];
|
|
434
|
-
if (missing.length > 0) parts.push(
|
|
435
|
-
if (stale.length > 0) parts.push(
|
|
436
|
-
const prefix = stale.length > 0 ? '
|
|
429
|
+
if (missing.length > 0) parts.push(it('docResolvedMissing', { n: missing.length, list: missing.slice(0, 3).join(', ') }));
|
|
430
|
+
if (stale.length > 0) parts.push(it('docResolvedStale', { n: stale.length, list: stale.slice(0, 3).join(', ') }));
|
|
431
|
+
const prefix = stale.length > 0 ? it('docResolvedPrefixStale') : it('docResolvedPrefix');
|
|
437
432
|
return { check: 'resolved', status: 'warn', detail: `${prefix} — ${parts.join('; ')}.` };
|
|
438
433
|
}
|
|
439
434
|
|
|
@@ -482,11 +477,11 @@ async function checkProjectInstructions({ projectDir, fileExists, readFile }) {
|
|
|
482
477
|
if (!(await fileExists(file))) continue;
|
|
483
478
|
seen += 1;
|
|
484
479
|
const text = await readFile(file);
|
|
485
|
-
if (!text.includes(BLOCK_BEGIN)) problems.push(
|
|
486
|
-
for (const f of scanLegacy(text)) problems.push(`${name}:${f.line} ${f.
|
|
480
|
+
if (!text.includes(BLOCK_BEGIN)) problems.push(it('docInstrNoBlock', { file: name }));
|
|
481
|
+
for (const f of scanLegacy(text)) problems.push(`${name}:${f.line} ${it(`docLegacy.${f.id}`)}: ${f.snippet}`);
|
|
487
482
|
}
|
|
488
|
-
if (seen === 0) return { check: 'instructions', status: 'ok', detail: '
|
|
489
|
-
if (problems.length === 0) return { check: 'instructions', status: 'ok', detail: '
|
|
483
|
+
if (seen === 0) return { check: 'instructions', status: 'ok', detail: it('docInstrNone') };
|
|
484
|
+
if (problems.length === 0) return { check: 'instructions', status: 'ok', detail: it('docInstrOk') };
|
|
490
485
|
return { check: 'instructions', status: 'warn', detail: problems.join('\n') };
|
|
491
486
|
}
|
|
492
487
|
|
|
@@ -508,6 +503,8 @@ async function runDoctor(options = {}) {
|
|
|
508
503
|
require('./resolved-customizations').discoverSkillCustomizeTomls({ projectRoot: root, wizzDir: dir }),
|
|
509
504
|
readResolvedIndex = (dir) => require('./resolved-customizations').readIndex(dir),
|
|
510
505
|
rtkOnPath = () => hasCommand('rtk'),
|
|
506
|
+
runHookProcess,
|
|
507
|
+
platform = process.platform,
|
|
511
508
|
skip = [],
|
|
512
509
|
} = deps;
|
|
513
510
|
|
|
@@ -519,7 +516,7 @@ async function runDoctor(options = {}) {
|
|
|
519
516
|
|
|
520
517
|
// 1) hooks — só faz sentido com _wizz/ presente.
|
|
521
518
|
if (skip.includes('hooks')) {
|
|
522
|
-
results.push({ check: 'hooks', status: 'ok', detail: '
|
|
519
|
+
results.push({ check: 'hooks', status: 'ok', detail: it('docSkipped') });
|
|
523
520
|
} else if (wizzDir) {
|
|
524
521
|
results.push(
|
|
525
522
|
checkHooks({
|
|
@@ -536,14 +533,22 @@ async function runDoctor(options = {}) {
|
|
|
536
533
|
results.push({
|
|
537
534
|
check: 'hooks',
|
|
538
535
|
status: 'warn',
|
|
539
|
-
detail: '
|
|
536
|
+
detail: it('docNoInstallHooks'),
|
|
540
537
|
});
|
|
541
538
|
}
|
|
542
539
|
|
|
543
|
-
//
|
|
544
|
-
|
|
540
|
+
// 1b) hooks-run — executa cada hook instalado com payload mínimo (smoke).
|
|
541
|
+
if (skip.includes('hooks-run')) {
|
|
542
|
+
results.push({ check: 'hooks-run', status: 'ok', detail: it('docSkipped') });
|
|
543
|
+
} else if (wizzDir) {
|
|
544
|
+
results.push(checkHooksRun({ wizzDir, projectDir, fileExists, rtkOnPath: rtkOnPath(), runHookProcess }));
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// 2) rtk (com piso de versão do hook) e 3) jq (presença). No Windows o hook
|
|
548
|
+
// registrado é o rtk-rewrite.js, que não usa jq: não cobrar.
|
|
549
|
+
for (const cli of platform === 'win32' ? ['rtk'] : ['rtk', 'jq']) {
|
|
545
550
|
if (skip.includes(cli)) {
|
|
546
|
-
results.push({ check: cli, status: 'ok', detail: '
|
|
551
|
+
results.push({ check: cli, status: 'ok', detail: it('docSkipped') });
|
|
547
552
|
continue;
|
|
548
553
|
}
|
|
549
554
|
const onPath = hasCommand(cli);
|
|
@@ -561,14 +566,14 @@ async function runDoctor(options = {}) {
|
|
|
561
566
|
|
|
562
567
|
// 4) python — detector stubável (mesmo padrão de checkPythonEnvironment).
|
|
563
568
|
if (skip.includes('python')) {
|
|
564
|
-
results.push({ check: 'python', status: 'ok', detail: '
|
|
569
|
+
results.push({ check: 'python', status: 'ok', detail: it('docSkipped') });
|
|
565
570
|
} else {
|
|
566
571
|
results.push(checkPython({ detected: detectPy() }));
|
|
567
572
|
}
|
|
568
573
|
|
|
569
574
|
// 5) drift — só com _wizz/ presente; sem instalação, skip informado.
|
|
570
575
|
if (skip.includes('drift')) {
|
|
571
|
-
results.push({ check: 'drift', status: 'ok', detail: '
|
|
576
|
+
results.push({ check: 'drift', status: 'ok', detail: it('docSkipped') });
|
|
572
577
|
} else if (wizzDir) {
|
|
573
578
|
const manifestPath = path.join(wizzDir, '_config', 'files-manifest.csv');
|
|
574
579
|
const manifestContent = (await fileExists(manifestPath)) ? await readFile(manifestPath) : null;
|
|
@@ -577,13 +582,13 @@ async function runDoctor(options = {}) {
|
|
|
577
582
|
results.push({
|
|
578
583
|
check: 'drift',
|
|
579
584
|
status: 'warn',
|
|
580
|
-
detail: '
|
|
585
|
+
detail: it('docNoInstallDrift'),
|
|
581
586
|
});
|
|
582
587
|
}
|
|
583
588
|
|
|
584
589
|
// 6) resolved — snapshots de customização contra o hash das entradas.
|
|
585
590
|
if (skip.includes('resolved')) {
|
|
586
|
-
results.push({ check: 'resolved', status: 'ok', detail: '
|
|
591
|
+
results.push({ check: 'resolved', status: 'ok', detail: it('docSkipped') });
|
|
587
592
|
} else if (wizzDir) {
|
|
588
593
|
const resolvedDir = path.join(wizzDir, '_resolved');
|
|
589
594
|
results.push(
|
|
@@ -600,13 +605,13 @@ async function runDoctor(options = {}) {
|
|
|
600
605
|
results.push({
|
|
601
606
|
check: 'resolved',
|
|
602
607
|
status: 'warn',
|
|
603
|
-
detail: '
|
|
608
|
+
detail: it('docNoInstallResolved'),
|
|
604
609
|
});
|
|
605
610
|
}
|
|
606
611
|
|
|
607
612
|
// 7) instructions — CLAUDE.md/AGENTS.md com bloco Wizz e sem regra legada.
|
|
608
613
|
if (skip.includes('instructions')) {
|
|
609
|
-
results.push({ check: 'instructions', status: 'ok', detail: '
|
|
614
|
+
results.push({ check: 'instructions', status: 'ok', detail: it('docSkipped') });
|
|
610
615
|
} else if (wizzDir) {
|
|
611
616
|
results.push(await checkProjectInstructions({ projectDir, fileExists, readFile }));
|
|
612
617
|
}
|
|
@@ -22,24 +22,53 @@ const HOOKS_DIR = '_wizz/hooks';
|
|
|
22
22
|
// event/matcher/timeout replicam 1:1 o que tools/hooks/README.md documenta e
|
|
23
23
|
// o que já roda hoje na máquina do mantenedor (npm run sync:global + registro
|
|
24
24
|
// manual em ~/.claude/settings.json) — nenhum formato novo foi inventado.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
// rtk-rewrite: o .sh precisa de bash e jq, que o Windows não garante. Lá entra
|
|
26
|
+
// a versão Node, com o mesmo comportamento (test/test-rtk-hook-node.js), desde
|
|
27
|
+
// 2026-10-15. Os dois arquivos são copiados; só o da plataforma é registrado.
|
|
28
|
+
function rtkHookDef(platform) {
|
|
29
|
+
return platform === 'win32'
|
|
30
|
+
? { id: 'rtk-rewrite.js', event: 'PreToolUse', matcher: 'Bash', kind: 'node', requires: 'rtk' }
|
|
31
|
+
: { id: 'rtk-rewrite.sh', event: 'PreToolUse', matcher: 'Bash', kind: 'shell', requires: 'rtk' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hooks registrados nesta plataforma.
|
|
36
|
+
* @param {string} platform - process.platform
|
|
37
|
+
*/
|
|
38
|
+
function hookDefsFor(platform) {
|
|
39
|
+
return [
|
|
40
|
+
{ id: 'session-rules.js', event: 'SessionStart', kind: 'node' },
|
|
41
|
+
{ id: 'wizz-router-enforce.js', event: 'UserPromptSubmit', kind: 'node' },
|
|
42
|
+
{ id: 'security-defensive-context.js', event: 'PreToolUse', matcher: 'Skill', kind: 'node' },
|
|
43
|
+
// rtk é opcional (item 4 da decisão RT-C1): só entra em hookEntries quando
|
|
44
|
+
// `rtkOnPath` é true — ver buildHookEntries abaixo.
|
|
45
|
+
rtkHookDef(platform),
|
|
46
|
+
{ id: 'wizz-decision-trace.js', event: 'Stop', kind: 'node' },
|
|
47
|
+
// Aviso (systemMessage, nunca bloqueia) de feature entregue com fechamento
|
|
48
|
+
// pendente (2026-10-15). Sem subprocesso; silencioso fora de projeto Wizz.
|
|
49
|
+
{ id: 'wizz-close-reminder.js', event: 'Stop', kind: 'node' },
|
|
50
|
+
// Avisa artefato fora da pasta da feature e arquivo solto na raiz (2026-10-02).
|
|
51
|
+
// Nunca bloqueia: PostToolUse só devolve contexto para o agente corrigir.
|
|
52
|
+
// Edit/MultiEdit desde 2026-10-15: frontmatter quebrado por edição também avisa.
|
|
53
|
+
{ id: 'wizz-artifact-guard.js', event: 'PostToolUse', matcher: 'Write|Edit|MultiEdit', kind: 'node' },
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const HOOK_DEFS = hookDefsFor(process.platform);
|
|
58
|
+
const RTK_HOOK_ID = rtkHookDef(process.platform).id;
|
|
37
59
|
|
|
38
60
|
// Arquivos de apoio copiados junto com os hooks, mas NUNCA registrados em
|
|
39
61
|
// settings.json: bibliotecas que os hooks importam e as CLIs que os agentes
|
|
40
62
|
// rodam (`node _wizz/hooks/wizz-feature.js definir <slug>`). Sem eles a
|
|
41
63
|
// organização automática cala em silêncio (fail-open), então o doctor cobra.
|
|
42
|
-
const SUPPORT_FILES = [
|
|
64
|
+
const SUPPORT_FILES = [
|
|
65
|
+
'wizz-feature-lib.js',
|
|
66
|
+
'wizz-layout-lib.js',
|
|
67
|
+
'wizz-cerebro-lib.js',
|
|
68
|
+
'wizz-debug-lib.js',
|
|
69
|
+
'wizz-feature.js',
|
|
70
|
+
'wizz-arrumar.js',
|
|
71
|
+
];
|
|
43
72
|
|
|
44
73
|
const DEFAULT_TIMEOUT = 5;
|
|
45
74
|
|
|
@@ -138,8 +167,16 @@ function mergeHooksIntoSettings(existingSettings, hookEntries) {
|
|
|
138
167
|
|
|
139
168
|
for (const entry of hookEntries || []) {
|
|
140
169
|
const { event, matcher, id, command, timeout } = entry;
|
|
141
|
-
const blocks = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
142
170
|
const sameMatcher = (block) => (matcher ? block.matcher === matcher : block.matcher === undefined);
|
|
171
|
+
// 0) Matcher mudou entre versões (ex.: Write → Write|Edit|MultiEdit): tira
|
|
172
|
+
// o nosso hook do bloco com o matcher antigo, senão ele roda 2x. Bloco que
|
|
173
|
+
// fica vazio sai; hook de terceiro no mesmo bloco fica.
|
|
174
|
+
const blocks = (Array.isArray(hooks[event]) ? hooks[event] : []).flatMap((block) => {
|
|
175
|
+
if (sameMatcher(block) || !Array.isArray(block.hooks)) return [block];
|
|
176
|
+
const rest = block.hooks.filter((h) => !isOurHookCommand(h && h.command, id));
|
|
177
|
+
if (rest.length === block.hooks.length) return [block];
|
|
178
|
+
return rest.length > 0 ? [{ ...block, hooks: rest }] : [];
|
|
179
|
+
});
|
|
143
180
|
|
|
144
181
|
// 1) Já presente, idêntico — nada a fazer (idempotência).
|
|
145
182
|
const alreadyPresent = blocks.some(
|
|
@@ -177,12 +214,49 @@ function mergeHooksIntoSettings(existingSettings, hookEntries) {
|
|
|
177
214
|
return { ...settings, hooks };
|
|
178
215
|
}
|
|
179
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Inverso de mergeHooksIntoSettings (uninstall): tira de `settings.hooks` todo
|
|
219
|
+
* comando que aponta para `_wizz/hooks/<hook nosso>`. Hook de terceiro, blocos
|
|
220
|
+
* com hook de terceiro e as demais chaves ficam; bloco, evento e a chave
|
|
221
|
+
* `hooks` que ficarem vazios saem. Função pura (não muta a entrada).
|
|
222
|
+
* @param {object} existingSettings - conteúdo de .claude/settings.json
|
|
223
|
+
* @returns {{settings: object, removed: number}}
|
|
224
|
+
*/
|
|
225
|
+
function removeHooksFromSettings(existingSettings) {
|
|
226
|
+
const settings = cloneJson(existingSettings);
|
|
227
|
+
if (!settings.hooks || typeof settings.hooks !== 'object' || Array.isArray(settings.hooks)) {
|
|
228
|
+
return { settings, removed: 0 };
|
|
229
|
+
}
|
|
230
|
+
const isOurs = (h) => HOOK_DEFS.some((def) => isOurHookCommand(h && h.command, def.id));
|
|
231
|
+
let removed = 0;
|
|
232
|
+
const hooks = {};
|
|
233
|
+
for (const [event, blocks] of Object.entries(settings.hooks)) {
|
|
234
|
+
if (!Array.isArray(blocks)) {
|
|
235
|
+
hooks[event] = blocks;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const kept = blocks.flatMap((block) => {
|
|
239
|
+
if (!block || !Array.isArray(block.hooks)) return [block];
|
|
240
|
+
const rest = block.hooks.filter((h) => !isOurs(h));
|
|
241
|
+
removed += block.hooks.length - rest.length;
|
|
242
|
+
if (rest.length === block.hooks.length) return [block];
|
|
243
|
+
return rest.length > 0 ? [{ ...block, hooks: rest }] : [];
|
|
244
|
+
});
|
|
245
|
+
if (kept.length > 0 || blocks.length === 0) hooks[event] = kept;
|
|
246
|
+
}
|
|
247
|
+
const { hooks: _old, ...others } = settings;
|
|
248
|
+
return { settings: Object.keys(hooks).length > 0 ? { ...others, hooks } : others, removed };
|
|
249
|
+
}
|
|
250
|
+
|
|
180
251
|
module.exports = {
|
|
181
252
|
SUPPORT_FILES,
|
|
182
253
|
HOOKS_DIR,
|
|
183
254
|
HOOK_DEFS,
|
|
255
|
+
RTK_HOOK_ID,
|
|
256
|
+
hookDefsFor,
|
|
184
257
|
buildHookEntries,
|
|
185
258
|
filterHooksRegisteredGlobally,
|
|
186
259
|
mergeHooksIntoSettings,
|
|
260
|
+
removeHooksFromSettings,
|
|
187
261
|
isOurHookCommand,
|
|
188
262
|
};
|