wizz-method 2.18.0 → 2.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/package.json +5 -3
  2. package/removals.txt +10 -0
  3. package/skills-registry.yaml +60 -39
  4. package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +9 -9
  5. package/src/bmm-skills/2-plan-workflows/wizz-ux/references/creative-tools.md +2 -0
  6. package/src/bmm-skills/2-plan-workflows/wizz-ux/references/design-md-spec.md +2 -0
  7. package/src/bmm-skills/4-implementation/wizz-create-story/SKILL.md +1 -1
  8. package/src/bmm-skills/4-implementation/wizz-dev-story/SKILL.md +1 -1
  9. package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/SKILL.md +1 -1
  10. package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/checklist.md +1 -1
  11. package/src/bmm-skills/4-implementation/wizz-quick-dev/SKILL.md +1 -1
  12. package/src/bmm-skills/4-implementation/wizz-sprint-status/SKILL.md +5 -5
  13. package/src/bmm-skills/module-help.csv +1 -1
  14. package/src/bmm-skills/module.yaml +1 -1
  15. package/src/core-skills/_shared/handoff-protocol.md +2 -2
  16. package/src/modules/lowticket/skills/wizz-lowticket-funil/SKILL.md +1 -1
  17. package/src/modules/lowticket/skills/wizz-lowticket-guia/SKILL.md +1 -1
  18. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/SKILL.md +1 -1
  19. package/src/modules/lowticket/skills/wizz-lowticket-minerador/SKILL.md +1 -1
  20. package/src/modules/lowticket/skills/wizz-lowticket-pagina/SKILL.md +1 -1
  21. package/src/modules/lowticket/skills/wizz-lowticket-trafego/SKILL.md +1 -1
  22. package/src/modules/wizz/_shared/cerebro.md +7 -7
  23. package/src/modules/wizz/_shared/communication-rules.md +1 -1
  24. package/src/modules/wizz/_shared/encerramento.md +4 -3
  25. package/src/modules/wizz/_shared/organizacao.md +8 -4
  26. package/src/modules/wizz/_shared/token-economy.md +1 -1
  27. package/src/modules/wizz/_shared/video-pipeline.md +4 -4
  28. package/src/modules/wizz/agents/wizz-ads/SKILL.md +4 -1
  29. package/src/modules/wizz/agents/wizz-ads/customize.toml +12 -3
  30. package/src/modules/wizz/agents/wizz-ads/knowledge/playbook.md +36 -0
  31. package/src/modules/wizz/agents/wizz-copy/SKILL.md +4 -1
  32. package/src/modules/wizz/agents/wizz-copy/customize.toml +2 -3
  33. package/src/modules/wizz/agents/wizz-copy/knowledge/playbook.md +34 -0
  34. package/src/modules/wizz/agents/wizz-designer/SKILL.md +2 -2
  35. package/src/modules/wizz/agents/wizz-designer/customize.toml +2 -3
  36. package/src/modules/wizz/agents/wizz-growth/SKILL.md +4 -1
  37. package/src/modules/wizz/agents/wizz-growth/customize.toml +33 -4
  38. package/src/modules/wizz/agents/wizz-growth/knowledge/playbook.md +39 -0
  39. package/src/modules/wizz/agents/wizz-maestro/SKILL.md +6 -8
  40. package/src/modules/wizz/agents/wizz-maestro/customize.toml +2 -3
  41. package/src/modules/wizz/agents/wizz-memoria/SKILL.md +7 -7
  42. package/src/modules/wizz/agents/wizz-memoria/customize.toml +5 -6
  43. package/src/modules/wizz/agents/wizz-qa/SKILL.md +5 -6
  44. package/src/modules/wizz/agents/wizz-qa/customize.toml +3 -4
  45. package/src/modules/wizz/agents/wizz-seo/SKILL.md +4 -1
  46. package/src/modules/wizz/agents/wizz-seo/customize.toml +2 -3
  47. package/src/modules/wizz/agents/wizz-seo/knowledge/playbook.md +31 -0
  48. package/src/modules/wizz/agents/wizz-social/SKILL.md +3 -3
  49. package/src/modules/wizz/agents/wizz-social/customize.toml +2 -3
  50. package/src/modules/wizz/overrides/wizz-agent-analyst.toml +2 -2
  51. package/src/modules/wizz/overrides/wizz-agent-architect.toml +3 -3
  52. package/src/modules/wizz/overrides/wizz-agent-dev.toml +3 -3
  53. package/src/modules/wizz/overrides/wizz-agent-pm.toml +2 -2
  54. package/src/modules/wizz/overrides/wizz-agent-tech-writer.toml +2 -2
  55. package/src/modules/wizz/overrides/wizz-agent-ux-designer.toml +2 -2
  56. package/src/skills-lib/_shared/complexity-signal.md +8 -9
  57. package/src/skills-lib/_shared/dispatch-rule.yaml +21 -0
  58. package/src/skills-lib/{cerebro → wizz-cerebro}/SKILL.md +1 -1
  59. package/src/skills-lib/{ctc-align → wizz-ctc-align}/SKILL.md +1 -1
  60. package/src/skills-lib/wizz-decision-maker/references/output-format.md +21 -0
  61. package/src/skills-lib/wizz-decision-maker/references/references-and-extraction.md +32 -1
  62. package/src/skills-lib/wizz-design-needs/references/design-state.md +1 -1
  63. package/src/skills-lib/wizz-design-needs/references/gap-analysis.md +3 -1
  64. package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/SKILL.md +1 -1
  65. package/src/skills-lib/{find-skills → wizz-find-skills}/SKILL.md +1 -1
  66. package/src/skills-lib/wizz-huashu-design/references/sfx-library.md +1 -1
  67. package/src/skills-lib/wizz-huashu-design/scripts/add-music.sh +1 -1
  68. package/src/skills-lib/wizz-huashu-design/scripts/html2pptx.js +45 -37
  69. package/src/skills-lib/wizz-huashu-design/scripts/mix-voiceover.sh +1 -1
  70. package/src/skills-lib/wizz-huashu-design/scripts/narrate-pipeline.mjs +11 -5
  71. package/src/skills-lib/wizz-huashu-design/scripts/test_scripts.py +209 -0
  72. package/src/skills-lib/wizz-huashu-design/scripts/tts-doubao.mjs +42 -28
  73. package/src/skills-lib/wizz-ig-profile-optimizer/SKILL.md +1 -1
  74. package/src/skills-lib/wizz-inicio-de-projeto/SKILL.md +1 -1
  75. package/src/skills-lib/wizz-launch-readiness/SKILL.md +7 -7
  76. package/src/skills-lib/wizz-launch-readiness/references/02-seguranca.md +3 -3
  77. package/src/skills-lib/wizz-launch-readiness/references/03-seo-descoberta.md +5 -5
  78. package/src/skills-lib/wizz-launch-readiness/references/04-analytics-medicao.md +1 -1
  79. package/src/skills-lib/wizz-launch-readiness/references/05-conteudo-prova-social.md +6 -6
  80. package/src/skills-lib/wizz-launch-readiness/references/06-legal-lgpd.md +1 -1
  81. package/src/skills-lib/wizz-launch-readiness/references/08-fusao-priorizacao.md +1 -1
  82. package/src/skills-lib/wizz-offer-forge/SKILL.md +3 -3
  83. package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/README.md +1 -1
  84. package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/SKILL.md +1 -1
  85. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/component-sources.md +23 -23
  86. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-first-protocol.md +15 -3
  87. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-links.md +14 -8
  88. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/web-access-and-evidence.md +4 -3
  89. package/src/skills-lib/wizz-router/SKILL.md +3 -3
  90. package/src/skills-lib/wizz-router/references/routing-table-flat.md +10 -9
  91. package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/SKILL.md +1 -1
  92. package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/SKILL.md +1 -1
  93. package/src/skills-lib/wizz-ui-component-curator/SKILL.md +1 -1
  94. package/src/skills-lib/wizz-video-effects/SKILL.md +3 -3
  95. package/src/skills-lib/wizz-video-effects/references/data-contract.md +3 -3
  96. package/src/skills-lib/wizz-video-effects/scripts/fill_breaths.py +15 -1
  97. package/src/skills-lib/wizz-video-effects/scripts/test_fill_breaths.py +20 -0
  98. package/src/skills-lib/wizz-video-styles/SKILL.md +2 -2
  99. package/src/skills-lib/wizz-video-styles/data/recipes.json +32 -1
  100. package/src/skills-lib/wizz-video-styles/data/references.json +211 -1
  101. package/src/skills-lib/wizz-video-styles/data/styles.json +54 -0
  102. package/src/skills-lib/wizz-video-styles/references/generated-prompts.md +13 -3
  103. package/src/skills-lib/wizz-video-styles/references/style-picker.md +4 -2
  104. package/src/skills-lib/wizz-video-styles/references/styles/ui-morph.md +68 -0
  105. package/src/skills-lib/wizz-video-styles/scripts/analyze.sh +21 -9
  106. package/src/skills-lib/wizz-video-styles/scripts/references.mjs +1 -0
  107. package/src/skills-lib/wizz-web-security/SKILL.md +1 -1
  108. package/tools/fetch-assets.mjs +1 -1
  109. package/tools/hooks/README.md +18 -14
  110. package/tools/hooks/rtk-rewrite.js +95 -0
  111. package/tools/hooks/security-defensive-context.js +17 -1
  112. package/tools/hooks/session-rules.js +25 -5
  113. package/tools/hooks/wizz-arrumar.js +126 -12
  114. package/tools/hooks/wizz-artifact-guard.js +41 -7
  115. package/tools/hooks/wizz-cerebro-lib.js +239 -0
  116. package/tools/hooks/wizz-close-reminder.js +124 -0
  117. package/tools/hooks/wizz-debug-lib.js +55 -0
  118. package/tools/hooks/wizz-decision-trace.js +19 -2
  119. package/tools/hooks/wizz-feature.js +20 -4
  120. package/tools/hooks/wizz-router-enforce.js +30 -13
  121. package/tools/installer/command-text.js +243 -0
  122. package/tools/installer/commands/catalog.js +2 -0
  123. package/tools/installer/commands/doctor.js +10 -12
  124. package/tools/installer/commands/fetch-assets.js +5 -5
  125. package/tools/installer/commands/install.js +6 -7
  126. package/tools/installer/commands/resolve.js +17 -17
  127. package/tools/installer/commands/trace-report.js +40 -37
  128. package/tools/installer/core/doctor.js +60 -55
  129. package/tools/installer/core/hooks-settings.js +88 -14
  130. package/tools/installer/core/hooks-smoke.js +134 -0
  131. package/tools/installer/core/installer.js +61 -52
  132. package/tools/installer/core/quick-update.js +2 -3
  133. package/tools/installer/guided/labels.js +5 -0
  134. package/tools/installer/install-text.js +201 -0
  135. package/tools/installer/modules/channel-plan.js +2 -2
  136. package/tools/installer/modules/env-vars.js +5 -6
  137. package/tools/installer/modules/skills-lib.js +1 -1
  138. package/tools/installer/ui.js +17 -9
  139. /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-pesquisa-e-memoria.md +0 -0
  140. /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-rotina-e-captura.md +0 -0
  141. /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-sessao.md +0 -0
  142. /package/src/skills-lib/{ctc-align → wizz-ctc-align}/scripts/align.py +0 -0
  143. /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/README.md +0 -0
  144. /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/references/KEYWORDS.md +0 -0
  145. /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/assets/template.html +0 -0
  146. /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/prompts.md +0 -0
  147. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-1-validacao-entrada.md +0 -0
  148. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-2-autorizacao.md +0 -0
  149. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-3-abuso-volume.md +0 -0
  150. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-4-vazamento-resposta.md +0 -0
  151. /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-5-fusao-plano.md +0 -0
  152. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/01-cta-principal.md +0 -0
  153. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/02-barra-fixa-mobile.md +0 -0
  154. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/03-tempo-de-resposta.md +0 -0
  155. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/04-prova-social.md +0 -0
  156. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/05-faq-decisao.md +0 -0
  157. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/06-imagens-reais.md +0 -0
  158. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/07-endereco-como-chegar.md +0 -0
  159. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/08-titles.md +0 -0
  160. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/09-open-graph.md +0 -0
  161. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/10-breadcrumbs.md +0 -0
  162. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/11-alt-text.md +0 -0
  163. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/12-schema-local.md +0 -0
  164. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/13-robots-sitemap.md +0 -0
  165. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/14-privacidade-termos.md +0 -0
  166. /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/15-medicao.md +0 -0
@@ -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: `Faltam arquivos de hook em _wizz/hooks/: ${missingFiles.join(', ')}. Execute wizz update para repor.`,
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
- `Arquivos ok, mas sem registro no settings do PROJETO (${settingsFile}): ${missingSettings.join(', ')}. ` +
190
- `Também não estão no settings GLOBAL (${globalSettingsFile || '~/.claude/settings.json'}). ` +
191
- `Rode wizz install para registrar no projeto, ou registre no global.`,
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
- ? ` (${skippedGlobal.length} já registrado(s) no settings global ${globalSettingsFile || '~/.claude/settings.json'}, ok)`
200
+ ? it('docHooksSkippedNote', { n: skippedGlobal.length, global: globalSettingsFile || '~/.claude/settings.json' })
198
201
  : '';
199
- return { check: 'hooks', status: 'ok', detail: `${HOOK_DEFS.length} hooks verificados${skippedNote}.` };
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: `rtk não encontrado no PATH. Opcional, mas sem ele o hook rtk-rewrite.sh não é registrado e a economia de tokens não roda. Instale: cargo install rtk`,
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: `rtk ${installed} é mais antigo que o piso do hook (${RTK_HOOK_FLOOR}); o rewrite falha em warn. Atualize: cargo install rtk`,
228
+ detail: it('docRtkOld', { installed, floor: RTK_HOOK_FLOOR }),
226
229
  };
227
230
  }
228
- return { check, status: 'ok', detail: `rtk ${installed || '(versão não informada)'} no PATH.` };
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: 'jq no PATH.' };
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 = `Opcional: o núcleo roda só com Node; Python só habilita as skills com script próprio (${PYTHON_ONLY_SKILLS}).`;
270
- if (status === 'full' && detected.isRuntimeCommand) {
271
- return `Python ${detected.version.raw} via ${detected.command} (tomllib ok): as skills Python também rodam.`;
272
- }
273
- if (status === 'full') {
274
- return `Python ${detected.version.raw} existe via ${detected.command}, mas os scripts chamam python3, que não está no PATH. ${optional}`;
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: 'files-manifest.csv não encontrado em _wizz/_config/. Sem ele não dá para detectar drift; rode wizz update para gerar.',
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: 'files-manifest.csv vazio, nada a comparar.' };
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: `${rows.length} arquivos batem com o files-manifest.csv.` };
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(`${drifted.length} alterado(s)`);
340
- if (missing.length > 0) parts.push(`${missing.length} ausente(s)`);
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: `Drift em _wizz/: ${parts.join(' e ')} contra o files-manifest.csv (ex.: ${[...drifted, ...missing].slice(0, 3).join(', ')}). Edita manuais em _wizz/ são perdidos no próximo update — mova para _wizz/custom/ e rode wizz update.`,
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: 'Nenhuma skill com customize.toml instalada; nada a resolver.' };
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: `Índice ${path.join(resolvedDir, '.index.json')} ausente ou ilegível: não dá para saber se os ${skills.length} snapshots estão atualizados. Rode wizz resolve.`,
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: `${skills.length} snapshots em _wizz/_resolved/ batem com as entradas que os geraram.`,
424
+ detail: it('docResolvedOk', { n: skills.length }),
430
425
  };
431
426
  }
432
427
 
433
428
  const parts = [];
434
- if (missing.length > 0) parts.push(`${missing.length} sem snapshot (${missing.slice(0, 3).join(', ')})`);
435
- if (stale.length > 0) parts.push(`${stale.length} com snapshot obsoleto (${stale.slice(0, 3).join(', ')})`);
436
- const prefix = stale.length > 0 ? 'snapshot obsoleto: rode wizz resolve' : 'rode wizz resolve';
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(`${name} sem o bloco Wizz no topo (rode wizz install)`);
486
- for (const f of scanLegacy(text)) problems.push(`${name}:${f.line} ${f.why}: ${f.snippet}`);
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: 'Sem CLAUDE.md/AGENTS.md no projeto.' };
489
- if (problems.length === 0) return { check: 'instructions', status: 'ok', detail: 'Bloco Wizz presente, nenhuma regra legada.' };
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: 'pulado.' });
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: 'Sem instalação Wizz neste diretório (_wizz/ ausente); hooks do projeto não verificados. Rode wizz install.',
536
+ detail: it('docNoInstallHooks'),
540
537
  });
541
538
  }
542
539
 
543
- // 2) rtk (com piso de versão do hook) e 3) jq (presença).
544
- for (const cli of ['rtk', 'jq']) {
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: 'pulado.' });
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: 'pulado.' });
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: 'pulado.' });
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: 'Sem instalação Wizz neste diretório; drift não verificado. Rode wizz install.',
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: 'pulado.' });
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: 'Sem instalação Wizz neste diretório; snapshots de _wizz/_resolved/ não verificados. Rode wizz install.',
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: 'pulado.' });
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
- const HOOK_DEFS = [
26
- { id: 'session-rules.js', event: 'SessionStart', kind: 'node' },
27
- { id: 'wizz-router-enforce.js', event: 'UserPromptSubmit', kind: 'node' },
28
- { id: 'security-defensive-context.js', event: 'PreToolUse', matcher: 'Skill', kind: 'node' },
29
- // rtk é opcional (item 4 da decisão RT-C1): só entra em hookEntries quando
30
- // `rtkOnPath` é true — ver buildHookEntries abaixo.
31
- { id: 'rtk-rewrite.sh', event: 'PreToolUse', matcher: 'Bash', kind: 'shell', requires: 'rtk' },
32
- { id: 'wizz-decision-trace.js', event: 'Stop', kind: 'node' },
33
- // Avisa artefato fora da pasta da feature e arquivo solto na raiz (2026-10-02).
34
- // Nunca bloqueia: PostToolUse só devolve contexto para o agente corrigir.
35
- { id: 'wizz-artifact-guard.js', event: 'PostToolUse', matcher: 'Write', kind: 'node' },
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 = ['wizz-feature-lib.js', 'wizz-layout-lib.js', 'wizz-feature.js', 'wizz-arrumar.js'];
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
  };
@@ -0,0 +1,134 @@
1
+ // hooks-smoke.js — o `wizz doctor` EXECUTA cada hook instalado em
2
+ // `_wizz/hooks/` com um payload mínimo válido (por evento) e reporta o que
3
+ // quebrou. Existe porque os hooks são fail-open: uma cópia parcial (lib
4
+ // faltando, arquivo corrompido) faz a feature sumir em silêncio, e checar só
5
+ // "o arquivo existe" não pega isso.
6
+ //
7
+ // Roda com WIZZ_DEBUG=1, então os erros que o hook engole (ver
8
+ // tools/hooks/wizz-debug-lib.js) chegam em stderr e entram no relatório.
9
+ // Nada é gravado: WIZZ_DEBUG_FILE aponta para o null device, WIZZ_TRACE fica
10
+ // vazio e o payload do artifact-guard usa um arquivo que não existe.
11
+ //
12
+ // Rápido de propósito: ~40 ms por hook, timeout curto, sem rede.
13
+
14
+ const os = require('node:os');
15
+ const path = require('node:path');
16
+ const { it } = require('../install-text');
17
+ const { HOOK_DEFS } = require('./hooks-settings');
18
+
19
+ const HOOK_TIMEOUT_MS = 4000;
20
+
21
+ // Falha ao carregar o código do próprio hook = feature morta. É o caso que o
22
+ // smoke existe para pegar, então vira error (o resto é warn).
23
+ const LOAD_FAILURE_RE = /require |Cannot find module|MODULE_NOT_FOUND|SyntaxError/;
24
+
25
+ /**
26
+ * Payload mínimo válido por hook (formato real que o Claude Code envia).
27
+ * @param {string} id - basename do hook
28
+ * @param {string} projectDir
29
+ * @returns {object}
30
+ */
31
+ function probePayload(id, projectDir) {
32
+ const base = { cwd: projectDir };
33
+ switch (id) {
34
+ case 'session-rules.js': {
35
+ return { ...base, hook_event_name: 'SessionStart', source: 'startup' };
36
+ }
37
+ case 'wizz-router-enforce.js': {
38
+ return { ...base, hook_event_name: 'UserPromptSubmit', prompt: 'implementa a feature de login com testes e validação' };
39
+ }
40
+ case 'security-defensive-context.js': {
41
+ return { ...base, hook_event_name: 'PreToolUse', tool_name: 'Skill', tool_input: { skill: 'security-review' } };
42
+ }
43
+ case 'rtk-rewrite.sh':
44
+ case 'rtk-rewrite.js': {
45
+ return { ...base, hook_event_name: 'PreToolUse', tool_name: 'Bash', tool_input: { command: 'echo wizz-doctor' } };
46
+ }
47
+ case 'wizz-artifact-guard.js': {
48
+ return {
49
+ ...base,
50
+ hook_event_name: 'PostToolUse',
51
+ tool_name: 'Write',
52
+ tool_input: { file_path: path.join(projectDir, '__wizz-doctor-probe__.txt'), content: '' },
53
+ };
54
+ }
55
+ case 'wizz-decision-trace.js':
56
+ case 'wizz-close-reminder.js': {
57
+ return { ...base, hook_event_name: 'Stop', stop_hook_active: false };
58
+ }
59
+ default: {
60
+ return base;
61
+ }
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Execução real de um hook. Injetável em `deps.runHookProcess` nos testes.
67
+ * @param {{file: string, kind: 'node'|'shell', payload: object, cwd: string}} args
68
+ * @returns {{status: number|null, stderr: string, error: string|null}}
69
+ */
70
+ function defaultRunHookProcess({ file, kind, payload, cwd }) {
71
+ const { spawnSync } = require('node:child_process');
72
+ const [cmd, args] = kind === 'shell' ? ['bash', [file]] : [process.execPath, [file]];
73
+ const res = spawnSync(cmd, args, {
74
+ cwd,
75
+ input: JSON.stringify(payload),
76
+ encoding: 'utf8',
77
+ timeout: HOOK_TIMEOUT_MS,
78
+ env: { ...process.env, WIZZ_DEBUG: '1', WIZZ_DEBUG_FILE: os.devNull, WIZZ_TRACE: '', CLAUDE_PROJECT_DIR: cwd },
79
+ });
80
+ return { status: res.status, stderr: res.stderr || '', error: res.error ? res.error.message : null };
81
+ }
82
+
83
+ function debugLines(stderr) {
84
+ return String(stderr)
85
+ .split('\n')
86
+ .filter((line) => line.startsWith('[wizz-debug]'));
87
+ }
88
+
89
+ /**
90
+ * Executa cada hook instalado e classifica o resultado.
91
+ * @param {object} deps
92
+ * @param {string} deps.wizzDir
93
+ * @param {string} deps.projectDir
94
+ * @param {(p: string) => boolean} deps.fileExists
95
+ * @param {boolean} deps.rtkOnPath - sem rtk no PATH o rtk-rewrite.sh é pulado
96
+ * @param {Function} [deps.runHookProcess]
97
+ * @returns {{check: 'hooks-run', status: 'ok'|'warn'|'error', detail: string}}
98
+ */
99
+ function checkHooksRun({ wizzDir, projectDir, fileExists, rtkOnPath, runHookProcess = defaultRunHookProcess }) {
100
+ const hooksDir = path.join(wizzDir, 'hooks');
101
+ const errors = [];
102
+ const warnings = [];
103
+ let ran = 0;
104
+
105
+ for (const def of HOOK_DEFS) {
106
+ if (def.requires === 'rtk' && !rtkOnPath) continue;
107
+ const file = path.join(hooksDir, def.id);
108
+ if (!fileExists(file)) continue; // ausência é do check `hooks`
109
+ ran++;
110
+ const res = runHookProcess({ file, kind: def.kind, payload: probePayload(def.id, projectDir), cwd: projectDir });
111
+ const lines = debugLines(res.stderr);
112
+ if (res.error || res.status !== 0) {
113
+ const why = res.error || `exit ${res.status}`;
114
+ const tail = String(res.stderr).trim().split('\n').slice(-2).join(' | ');
115
+ errors.push(it('hrBroke', { id: def.id, why, tail: tail ? `: ${tail}` : '' }));
116
+ } else if (lines.some((l) => LOAD_FAILURE_RE.test(l))) {
117
+ errors.push(it('hrNoLibs', { id: def.id, line: lines.find((l) => LOAD_FAILURE_RE.test(l)) }));
118
+ } else if (lines.length > 0) {
119
+ warnings.push(it('hrSwallowed', { id: def.id, line: lines[0].replace('[wizz-debug] ', '') }));
120
+ }
121
+ }
122
+
123
+ if (errors.length > 0) {
124
+ return {
125
+ check: 'hooks-run',
126
+ status: 'error',
127
+ detail: `${errors.join('\n')}${warnings.length > 0 ? `\n${warnings.join('\n')}` : ''}\n${it('hrPartialCopy')}`,
128
+ };
129
+ }
130
+ if (warnings.length > 0) return { check: 'hooks-run', status: 'warn', detail: warnings.join('\n') };
131
+ return { check: 'hooks-run', status: 'ok', detail: it('hrOk', { n: ran }) };
132
+ }
133
+
134
+ module.exports = { checkHooksRun, probePayload, defaultRunHookProcess, HOOK_TIMEOUT_MS };