wizz-method 2.12.0 → 2.14.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.
Files changed (119) hide show
  1. package/README.md +2 -1
  2. package/THIRD-PARTY-NOTICES.md +2 -0
  3. package/package.json +10 -3
  4. package/skills-registry.yaml +48 -48
  5. package/src/bmm-skills/1-analysis/research/wizz-domain-research/customize.toml +4 -3
  6. package/src/bmm-skills/1-analysis/research/wizz-market-research/customize.toml +4 -3
  7. package/src/bmm-skills/1-analysis/research/wizz-technical-research/customize.toml +3 -1
  8. package/src/bmm-skills/1-analysis/wizz-agent-analyst/customize.toml +3 -1
  9. package/src/bmm-skills/1-analysis/wizz-agent-tech-writer/customize.toml +4 -3
  10. package/src/bmm-skills/1-analysis/wizz-document-project/customize.toml +4 -3
  11. package/src/bmm-skills/1-analysis/wizz-prfaq/customize.toml +4 -3
  12. package/src/bmm-skills/1-analysis/wizz-product-brief/customize.toml +5 -6
  13. package/src/bmm-skills/2-plan-workflows/wizz-agent-pm/customize.toml +3 -1
  14. package/src/bmm-skills/2-plan-workflows/wizz-agent-ux-designer/customize.toml +3 -1
  15. package/src/bmm-skills/2-plan-workflows/wizz-create-prd/customize.toml +4 -3
  16. package/src/bmm-skills/2-plan-workflows/wizz-edit-prd/customize.toml +4 -3
  17. package/src/bmm-skills/2-plan-workflows/wizz-prd/customize.toml +5 -6
  18. package/src/bmm-skills/2-plan-workflows/wizz-ux/customize.toml +4 -3
  19. package/src/bmm-skills/2-plan-workflows/wizz-validate-prd/customize.toml +4 -3
  20. package/src/bmm-skills/3-solutioning/wizz-agent-architect/customize.toml +3 -1
  21. package/src/bmm-skills/3-solutioning/wizz-architecture/customize.toml +5 -6
  22. package/src/bmm-skills/3-solutioning/wizz-check-implementation-readiness/customize.toml +4 -3
  23. package/src/bmm-skills/3-solutioning/wizz-create-architecture/customize.toml +4 -3
  24. package/src/bmm-skills/3-solutioning/wizz-create-epics-and-stories/customize.toml +4 -3
  25. package/src/bmm-skills/3-solutioning/wizz-generate-project-context/steps/step-03-complete.md +7 -0
  26. package/src/bmm-skills/4-implementation/wizz-agent-dev/customize.toml +3 -1
  27. package/src/bmm-skills/4-implementation/wizz-checkpoint-preview/customize.toml +4 -3
  28. package/src/bmm-skills/4-implementation/wizz-code-review/customize.toml +4 -3
  29. package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-03-triage.md +23 -14
  30. package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-04-present.md +2 -2
  31. package/src/bmm-skills/4-implementation/wizz-correct-course/customize.toml +4 -3
  32. package/src/bmm-skills/4-implementation/wizz-create-story/customize.toml +4 -3
  33. package/src/bmm-skills/4-implementation/wizz-dev-story/customize.toml +4 -3
  34. package/src/bmm-skills/4-implementation/wizz-investigate/customize.toml +4 -3
  35. package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/customize.toml +4 -3
  36. package/src/bmm-skills/4-implementation/wizz-quick-dev/customize.toml +4 -3
  37. package/src/bmm-skills/4-implementation/wizz-quick-dev/spec-template.md +6 -0
  38. package/src/bmm-skills/4-implementation/wizz-quick-dev/step-04-review.md +27 -16
  39. package/src/bmm-skills/4-implementation/wizz-quick-dev/step-oneshot.md +27 -6
  40. package/src/bmm-skills/4-implementation/wizz-retrospective/customize.toml +4 -3
  41. package/src/bmm-skills/4-implementation/wizz-sprint-planning/customize.toml +4 -3
  42. package/src/bmm-skills/4-implementation/wizz-sprint-status/customize.toml +4 -3
  43. package/src/bmm-skills/module.yaml +1 -1
  44. package/src/core-skills/wizz-brainstorming/customize.toml +5 -6
  45. package/src/core-skills/wizz-brainstorming/scripts/brain.py +19 -0
  46. package/src/core-skills/wizz-brainstorming/scripts/tests/test_brain.py +33 -0
  47. package/src/core-skills/wizz-forge-idea/scripts/resolve_personas.py +3 -1
  48. package/src/core-skills/wizz-forge-idea/scripts/tests/test_resolve_personas.py +21 -0
  49. package/src/core-skills/wizz-help/SKILL.md +5 -3
  50. package/src/core-skills/wizz-party-mode/customize.toml +5 -4
  51. package/src/core-skills/wizz-party-mode/scripts/resolve_party.py +3 -1
  52. package/src/core-skills/wizz-party-mode/scripts/tests/test_resolve_party.py +21 -0
  53. package/src/core-skills/wizz-spec/customize.toml +4 -5
  54. package/src/modules/wizz/_shared/model-ladder.md +4 -4
  55. package/src/modules/wizz/_shared/video-pipeline.md +1 -1
  56. package/src/modules/wizz/agents/wizz-maestro/SKILL.md +1 -1
  57. package/src/modules/wizz/subagents/opencode/wizz-exec-review.md +2 -2
  58. package/src/modules/wizz/subagents/opencode/wizz-exec-standard.md +2 -2
  59. package/src/modules/wizz/subagents/opencode/wizz-exec-strong.md +2 -2
  60. package/src/modules/wizz/subagents/spec.yaml +3 -3
  61. package/src/scripts/resolve_config.py +8 -0
  62. package/src/scripts/resolve_customization.py +24 -3
  63. package/src/scripts/tests/test_resolve_config.py +42 -0
  64. package/src/scripts/tests/test_resolve_customization.py +45 -0
  65. package/src/skills-lib/ctc-align/SKILL.md +1 -1
  66. package/src/skills-lib/wizz-agent-browser/SKILL.md +5 -0
  67. package/src/skills-lib/wizz-huashu-design/scripts/html2pptx.js +902 -1061
  68. package/src/skills-lib/wizz-humanizer/README.md +134 -71
  69. package/src/skills-lib/wizz-humanizer/SKILL.md +51 -37
  70. package/src/skills-lib/wizz-humanizer/references/ai-writing-patterns.md +208 -190
  71. package/src/skills-lib/wizz-router/references/routing-table-flat.md +2 -2
  72. package/tools/installer/banner.js +80 -29
  73. package/tools/installer/commands/catalog.js +87 -0
  74. package/tools/installer/commands/install.js +9 -0
  75. package/tools/installer/core/doctor.js +31 -1
  76. package/tools/installer/core/installer.js +32 -0
  77. package/tools/installer/core/project-instructions.js +199 -0
  78. package/tools/installer/guided/detect-tools.js +53 -0
  79. package/tools/installer/guided/flow.js +203 -0
  80. package/tools/installer/guided/labels.js +185 -0
  81. package/tools/installer/guided/messages.js +132 -0
  82. package/tools/installer/guided/outro.js +32 -0
  83. package/tools/installer/guided/profiles.js +95 -0
  84. package/tools/installer/guided/steps.js +154 -0
  85. package/tools/installer/install-messages.yaml +3 -3
  86. package/tools/installer/modules/external-manager.js +4 -0
  87. package/tools/installer/ui.js +57 -22
  88. package/wizz-modules.yaml +1 -0
  89. package/src/bmm-skills/3-solutioning/wizz-architecture/scripts/__pycache__/lint_spine.cpython-313.pyc +0 -0
  90. package/src/bmm-skills/3-solutioning/wizz-architecture/scripts/tests/__pycache__/test_lint_spine.cpython-313-pytest-9.1.1.pyc +0 -0
  91. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/__pycache__/sprint_status.cpython-313.pyc +0 -0
  92. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/__pycache__/test_git_evidence.cpython-313-pytest-9.1.1.pyc +0 -0
  93. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/__pycache__/test_sprint_status.cpython-313-pytest-9.1.1.pyc +0 -0
  94. package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/__pycache__/sprint_plan.cpython-313.pyc +0 -0
  95. package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/tests/__pycache__/test_sprint_plan.cpython-313-pytest-9.1.1.pyc +0 -0
  96. package/src/core-skills/wizz-advanced-elicitation/scripts/__pycache__/pick_methods.cpython-313.pyc +0 -0
  97. package/src/core-skills/wizz-advanced-elicitation/scripts/tests/__pycache__/test_pick_methods.cpython-313-pytest-9.1.1.pyc +0 -0
  98. package/src/core-skills/wizz-brainstorming/scripts/__pycache__/brain.cpython-313.pyc +0 -0
  99. package/src/core-skills/wizz-brainstorming/scripts/tests/__pycache__/test_brain.cpython-313-pytest-9.1.1.pyc +0 -0
  100. package/src/core-skills/wizz-brainstorming/scripts/tests/__pycache__/test_brain.cpython-314.pyc +0 -0
  101. package/src/core-skills/wizz-customize/scripts/__pycache__/list_customizable_skills.cpython-313.pyc +0 -0
  102. package/src/core-skills/wizz-customize/scripts/tests/__pycache__/test_list_customizable_skills.cpython-313-pytest-9.1.1.pyc +0 -0
  103. package/src/core-skills/wizz-forge-idea/scripts/__pycache__/resolve_personas.cpython-313.pyc +0 -0
  104. package/src/core-skills/wizz-forge-idea/scripts/__pycache__/resolve_personas.cpython-314.pyc +0 -0
  105. package/src/core-skills/wizz-forge-idea/scripts/tests/__pycache__/test_resolve_personas.cpython-313-pytest-9.1.1.pyc +0 -0
  106. package/src/core-skills/wizz-forge-idea/scripts/tests/__pycache__/test_resolve_personas.cpython-314.pyc +0 -0
  107. package/src/core-skills/wizz-party-mode/scripts/__pycache__/resolve_party.cpython-313.pyc +0 -0
  108. package/src/core-skills/wizz-party-mode/scripts/__pycache__/resolve_party.cpython-314.pyc +0 -0
  109. package/src/core-skills/wizz-party-mode/scripts/tests/__pycache__/test_resolve_party.cpython-313-pytest-9.1.1.pyc +0 -0
  110. package/src/core-skills/wizz-party-mode/scripts/tests/__pycache__/test_resolve_party.cpython-314.pyc +0 -0
  111. package/src/scripts/__pycache__/resolve_customization.cpython-314.pyc +0 -0
  112. package/src/skills-lib/wizz-huashu-design/package-lock.json +0 -846
  113. package/src/skills-lib/wizz-humanizer/WARP.md +0 -53
  114. package/src/skills-lib/wizz-humanizer/references/full-example.md +0 -72
  115. package/src/skills-lib/wizz-humanizer/references/personality-and-soul.md +0 -36
  116. package/src/skills-lib/wizz-react-components/package-lock.json +0 -231
  117. package/src/skills-lib/wizz-supabase-postgres-best-practices/CLAUDE.md +0 -68
  118. package/src/skills-lib/wizz-ui-ux-pro-max/scripts/__pycache__/core.cpython-314.pyc +0 -0
  119. package/src/skills-lib/wizz-ui-ux-pro-max/scripts/__pycache__/design_system.cpython-314.pyc +0 -0
@@ -1,40 +1,91 @@
1
1
  const prompts = require('./prompts');
2
+ const { t } = require('./guided/messages');
3
+
4
+ // Wizz brand palette (brand book): Wizz Orange and its deep shade.
5
+ const BRAND_ORANGE = [255, 69, 0];
6
+ const BRAND_DEEP = [204, 55, 0];
7
+ // Built from the char code on purpose: a literal escape byte in source turns
8
+ // the file binary for git (see the review-prep.mjs incident).
9
+ const ESC = String.fromCodePoint(27);
10
+
11
+ /**
12
+ * Paint text with a 24-bit brand color, or leave it plain when the terminal
13
+ * has no color support (NO_COLOR, pipes, CI logs).
14
+ * @param {number[]} rgb - [r, g, b]
15
+ * @param {string} text - Text to paint
16
+ * @param {boolean} enabled - Whether color is supported
17
+ * @returns {string}
18
+ */
19
+ function paint(rgb, text, enabled) {
20
+ if (!enabled) return text;
21
+ return `${ESC}[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m${text}${ESC}[39m`;
22
+ }
23
+
24
+ const LOGO_WIDE = [
25
+ '██╗ ██╗ ██╗ ███████╗ ███████╗ ███╗ ███╗ ███████╗ ████████╗ ██╗ ██╗ ██████╗ ██████╗ ™',
26
+ '██║ ██║ ██║ ╚══███╔╝ ╚══███╔╝ ████╗ ████║ ██╔════╝ ╚══██╔══╝ ██║ ██║ ██╔═══██╗ ██╔══██╗',
27
+ '██║ █╗ ██║ ██║ ███╔╝ ███╔╝ ██╔████╔██║ █████╗ ██║ ███████║ ██║ ██║ ██║ ██║',
28
+ '██║███╗██║ ██║ ███╔╝ ███╔╝ ██║╚██╔╝██║ ██╔══╝ ██║ ██╔══██║ ██║ ██║ ██║ ██║',
29
+ '╚███╔███╔╝ ██║ ███████╗ ███████╗ ██║ ╚═╝ ██║ ███████╗ ██║ ██║ ██║ ╚██████╔╝ ██████╔╝',
30
+ ' ╚══╝╚══╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ',
31
+ ];
32
+
33
+ const LOGO_NARROW = [
34
+ '██╗ ██╗ ██╗ ███████╗ ███████╗ ™',
35
+ '██║ ██║ ██║ ╚══███╔╝ ╚══███╔╝',
36
+ '██║ █╗ ██║ ██║ ███╔╝ ███╔╝ ',
37
+ '██║███╗██║ ██║ ███╔╝ ███╔╝ ',
38
+ '╚███╔███╔╝ ██║ ███████╗ ███████╗',
39
+ ' ╚══╝╚══╝ ╚═╝ ╚══════╝ ╚══════╝',
40
+ ];
2
41
 
3
42
  const CLIUtils = {
4
43
  /**
5
- * Display Wizz Method logo and version using @clack intro + box
44
+ * Display the Wizz Method opening screen. With `steps`, it also lists the
45
+ * guided path's four steps so the user knows what comes next.
46
+ * @param {Object} [opts]
47
+ * @param {boolean} [opts.steps=false] - Show the "what happens next" list
48
+ * @param {'en'|'pt'} [opts.lang='en'] - Display language
49
+ * @param {string} [opts.version] - Version shown under the logo
6
50
  */
7
- async displayLogo() {
51
+ async displayLogo(opts = {}) {
52
+ const { steps = false, lang = 'en', version } = opts;
8
53
  const color = await prompts.getColor();
54
+ const colorOn = color.isColorSupported !== false;
9
55
  const termWidth = process.stdout.columns || 80;
10
56
 
11
- // Full "WIZZ METHOD" logo for wide terminals, "WIZZ" only for narrow
12
- const logoWide = [
13
- '██╗ ██╗ ██╗ ███████╗ ███████╗ ███╗ ███╗ ███████╗ ████████╗ ██╗ ██╗ ██████╗ ██████╗ ™',
14
- '██║ ██║ ██║ ╚══███╔╝ ╚══███╔╝ ████╗ ████║ ██╔════╝ ╚══██╔══╝ ██║ ██║ ██╔═══██╗ ██╔══██╗',
15
- '██║ █╗ ██║ ██║ ███╔╝ ███╔╝ ██╔████╔██║ █████╗ ██║ ███████║ ██║ ██║ ██║ ██║',
16
- '██║███╗██║ ██║ ███╔╝ ███╔╝ ██║╚██╔╝██║ ██╔══╝ ██║ ██╔══██║ ██║ ██║ ██║ ██║',
17
- '╚███╔███╔╝ ██║ ███████╗ ███████╗ ██║ ╚═╝ ██║ ███████╗ ██║ ██║ ██║ ╚██████╔╝ ██████╔╝',
18
- ' ╚══╝╚══╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ',
19
- ];
20
-
21
- const logoNarrow = [
22
- ' ██╗ ██╗ ██╗ ███████╗ ███████╗ ™',
23
- ' ██║ ██║ ██║ ╚══███╔╝ ╚══███╔╝',
24
- ' ██║ █╗ ██║ ██║ ███╔╝ ███╔╝ ',
25
- ' ██║███╗██║ ██║ ███╔╝ ███╔╝ ',
26
- ' ╚███╔███╔╝ ██║ ███████╗ ███████╗',
27
- ' ╚══╝╚══╝ ╚═╝ ╚══════╝ ╚══════╝',
28
- ];
29
-
30
- const logoLines = termWidth >= 95 ? logoWide : logoNarrow;
31
- const logo = logoLines.map((line) => color.blue(line)).join('\n');
32
- const tagline = color.white(' Método de agência orientado por IA\n © Wizz! comms. · method.wizzcomms.com');
33
-
34
- await prompts.box(`${logo}\n${tagline}`, '', {
35
- contentAlign: 'center',
57
+ const wide = termWidth >= 100;
58
+ const logoLines = wide ? LOGO_WIDE : LOGO_NARROW;
59
+ // Top half in Wizz Orange, bottom half in the deep shade: reads as a
60
+ // light-to-shadow gradient without a per-character color ramp.
61
+ const half = Math.ceil(logoLines.length / 2);
62
+ const logo = logoLines.map((line, i) => paint(i < half ? BRAND_ORANGE : BRAND_DEEP, line, colorOn));
63
+ if (!wide) logo.push(paint(BRAND_ORANGE, 'M E T H O D', colorOn));
64
+
65
+ const lines = [...logo, ''];
66
+ const versionTag = version ? color.dim(`v${version}`) : '';
67
+ lines.push(`${color.white(t(lang, 'tagline'))} ${versionTag}`.trimEnd());
68
+
69
+ if (steps) {
70
+ lines.push('', `${color.bold(t(lang, 'whatHappens'))} ${color.dim(`(${t(lang, 'duration')})`)}`);
71
+ for (const [i, name] of t(lang, 'stepNames').entries()) {
72
+ lines.push(`${paint(BRAND_ORANGE, String(i + 1), colorOn)} ▸ ${name}`);
73
+ }
74
+ }
75
+
76
+ lines.push('', color.dim('© Wizz! comms. · method.wizzcomms.com'));
77
+
78
+ // A terminal that reports 0 columns (some ptys, expect, CI with a TTY)
79
+ // makes clack's box compute a negative width and throw. Print the same
80
+ // lines without the frame instead of failing the install.
81
+ if (!process.stdout.columns || process.stdout.columns < 40) {
82
+ process.stdout.write(`${lines.join('\n')}\n\n`);
83
+ return;
84
+ }
85
+ await prompts.box(lines.join('\n'), '', {
86
+ contentAlign: steps ? 'left' : 'center',
36
87
  rounded: true,
37
- formatBorder: color.blue,
88
+ formatBorder: (text) => paint(BRAND_DEEP, text, colorOn),
38
89
  });
39
90
  },
40
91
 
@@ -50,4 +101,4 @@ const CLIUtils = {
50
101
  },
51
102
  };
52
103
 
53
- module.exports = { CLIUtils };
104
+ module.exports = { CLIUtils, paint, BRAND_ORANGE, BRAND_DEEP };
@@ -0,0 +1,87 @@
1
+ const path = require('node:path');
2
+ const os = require('node:os');
3
+ const fs = require('node:fs');
4
+ const { getProjectRoot } = require('../project-root');
5
+ const { loadYamlCached } = require('../core/yaml-cache');
6
+ const { AREA_LABELS, cliBadges, cliDescription } = require('../guided/labels');
7
+ const { normalizeLang, t } = require('../guided/messages');
8
+ const { paint, BRAND_ORANGE } = require('../banner');
9
+
10
+ const SKILL_DIRS = ['.claude/skills', '.agents/skills'];
11
+
12
+ /**
13
+ * Whether a skill folder exists in the project or in the global Claude
14
+ * skills folder. Only a hint for the ✓ mark; nothing depends on it.
15
+ */
16
+ function isSkillInstalled(id, projectDir, home) {
17
+ const places = [...SKILL_DIRS.map((dir) => path.join(projectDir, dir, id)), path.join(home, '.claude', 'skills', id)];
18
+ return places.some((place) => fs.existsSync(place));
19
+ }
20
+
21
+ /** First sentence of a registry `when:`, cut to fit one terminal line. */
22
+ function shortWhen(when, max = 90) {
23
+ const first = String(when || '').split(/(?<=[.!?])\s/)[0];
24
+ return first.length > max ? `${first.slice(0, max - 1)}…` : first;
25
+ }
26
+
27
+ /**
28
+ * Render the catalog as plain text lines.
29
+ * @param {Object} registry - Parsed skills-registry.yaml
30
+ * @param {Object} opts
31
+ * @param {'en'|'pt'} opts.lang - Display language
32
+ * @param {string|null} opts.area - Only this area, or null for all
33
+ * @param {(id: string) => boolean} opts.installed - Installed check
34
+ * @param {boolean} opts.color - Whether to paint
35
+ * @returns {string[]}
36
+ */
37
+ function renderCatalog(registry, opts) {
38
+ const { lang, area, installed, color } = opts;
39
+ const areas = Object.entries(registry.areas || {}).filter(([key]) => !area || key === area);
40
+ const lines = [];
41
+ for (const [key, entry] of areas) {
42
+ const label = AREA_LABELS[key] ? AREA_LABELS[key][lang] : { name: key, help: entry.summary || '' };
43
+ lines.push('', `${paint(BRAND_ORANGE, `■ ${label.name}`, color)} (${key}) · ${label.help}`);
44
+ for (const skill of entry.skills || []) {
45
+ const mark = installed(skill.id) ? '✓' : ' ';
46
+ lines.push(` ${mark} ${skill.id.padEnd(40)} ${shortWhen(skill.when)}`);
47
+ }
48
+ for (const cli of entry.clis || []) {
49
+ const badges = cliBadges(cli, lang);
50
+ const needs = badges.length > 0 ? ` [${badges.join(', ')}]` : ` [${t(lang, 'free')}]`;
51
+ lines.push(` ⌘ ${cli.id.padEnd(40)} ${cliDescription(cli.id, lang)}${needs}`);
52
+ }
53
+ for (const mcp of entry.mcps || []) {
54
+ lines.push(` ◇ ${mcp.id.padEnd(40)} ${shortWhen(mcp.when)}`);
55
+ }
56
+ }
57
+ return lines;
58
+ }
59
+
60
+ module.exports = {
61
+ command: 'catalog',
62
+ description: 'List every area, skill and tool Wizz Method can install (✓ = installed)',
63
+ options: [
64
+ ['--area <area>', 'Only one area, e.g. social or dev'],
65
+ ['--lang <lang>', 'Language for area names and tool descriptions: en (default) or pt'],
66
+ ],
67
+ action: async (options) => {
68
+ const registry = await loadYamlCached(path.join(getProjectRoot(), 'skills-registry.yaml'));
69
+ if (options.area && !(registry.areas || {})[options.area]) {
70
+ process.stderr.write(`Unknown area "${options.area}". Areas: ${Object.keys(registry.areas).join(', ')}\n`);
71
+ process.exitCode = 1;
72
+ return;
73
+ }
74
+ const projectDir = process.cwd();
75
+ const home = os.homedir();
76
+ const lang = normalizeLang(options.lang) || 'en';
77
+ const lines = renderCatalog(registry, {
78
+ lang,
79
+ area: options.area || null,
80
+ installed: (id) => isSkillInstalled(id, projectDir, home),
81
+ color: !!process.stdout.isTTY && !process.env.NO_COLOR,
82
+ });
83
+ lines.push('', t(lang, 'catalogLegend'), '');
84
+ process.stdout.write(lines.join('\n'));
85
+ },
86
+ renderCatalog,
87
+ };
@@ -47,6 +47,11 @@ module.exports = {
47
47
  ['--output-folder <path>', 'Output folder path relative to project root (default: _wizz-output)'],
48
48
  ['--custom-source <sources>', 'Comma-separated Git URLs or local paths to install custom modules from'],
49
49
  ['-y, --yes', 'Accept all defaults and skip prompts where possible'],
50
+ ['--lang <lang>', 'Installer language for the guided install: en (default) or pt'],
51
+ [
52
+ '--advanced',
53
+ 'Skip the guided install and ask every question: module list, community modules, release channels, artifact folders and the routing meter',
54
+ ],
50
55
  [
51
56
  '--channel <channel>',
52
57
  'Apply channel (stable|next) to all external modules being installed. --all-stable and --all-next are aliases.',
@@ -171,6 +176,10 @@ module.exports = {
171
176
  process.exit(1);
172
177
  }
173
178
  if (result.success) {
179
+ if (config.guided) {
180
+ const { showGuidedOutro } = require('../guided/outro');
181
+ await showGuidedOutro(config);
182
+ }
174
183
  process.exit(0);
175
184
  }
176
185
  }
@@ -25,7 +25,7 @@ const { HOOK_DEFS, isOurHookCommand } = require('./hooks-settings');
25
25
  const { classifyPython, PYTHON_ONLY_SKILLS } = require('./python-check');
26
26
 
27
27
  // Piso de versão do rtk que o hook rtk-rewrite.sh exige para `rtk rewrite`
28
- // (o registry pin do CLI é mais alto, 0.45.0, e é aplicado pelo detectClis
28
+ // (o registry pin do CLI é mais alto, 0.50.0, e é aplicado pelo detectClis
29
29
  // no install; aqui reportamos o piso que quebra o hook hoje).
30
30
  const RTK_HOOK_FLOOR = '0.23.0';
31
31
 
@@ -464,6 +464,28 @@ async function currentInputHash({ projectDir, relPath, skill, sources, fileExist
464
464
  * @param {object} [options.deps] - checkers injetáveis (ver chamadas abaixo)
465
465
  * @returns {Promise<{results: Array<{check, status, detail}>, exitCode: number, hasError: boolean}>}
466
466
  */
467
+ /**
468
+ * CLAUDE.md/AGENTS.md: bloco Wizz presente e sem regra legada fora dele.
469
+ * Sem o bloco, um CLAUDE.md antigo vence as regras do Wizz (2026-09-30).
470
+ * @returns {Promise<{check: 'instructions', status: 'ok'|'warn', detail: string}>}
471
+ */
472
+ async function checkProjectInstructions({ projectDir, fileExists, readFile }) {
473
+ const { INSTRUCTION_FILES, BLOCK_BEGIN, scanLegacy } = require('./project-instructions');
474
+ const problems = [];
475
+ let seen = 0;
476
+ for (const name of INSTRUCTION_FILES) {
477
+ const file = path.join(projectDir, name);
478
+ if (!(await fileExists(file))) continue;
479
+ seen += 1;
480
+ const text = await readFile(file);
481
+ if (!text.includes(BLOCK_BEGIN)) problems.push(`${name} sem o bloco Wizz no topo (rode wizz install)`);
482
+ for (const f of scanLegacy(text)) problems.push(`${name}:${f.line} ${f.why}: ${f.snippet}`);
483
+ }
484
+ if (seen === 0) return { check: 'instructions', status: 'ok', detail: 'Sem CLAUDE.md/AGENTS.md no projeto.' };
485
+ if (problems.length === 0) return { check: 'instructions', status: 'ok', detail: 'Bloco Wizz presente, nenhuma regra legada.' };
486
+ return { check: 'instructions', status: 'warn', detail: problems.join('\n') };
487
+ }
488
+
467
489
  async function runDoctor(options = {}) {
468
490
  const deps = options.deps || {};
469
491
  const {
@@ -578,6 +600,13 @@ async function runDoctor(options = {}) {
578
600
  });
579
601
  }
580
602
 
603
+ // 7) instructions — CLAUDE.md/AGENTS.md com bloco Wizz e sem regra legada.
604
+ if (skip.includes('instructions')) {
605
+ results.push({ check: 'instructions', status: 'ok', detail: 'pulado.' });
606
+ } else if (wizzDir) {
607
+ results.push(await checkProjectInstructions({ projectDir, fileExists, readFile }));
608
+ }
609
+
581
610
  const hasError = results.some((r) => r.status === 'error');
582
611
  return { results, exitCode: hasError ? 1 : 0, hasError };
583
612
  }
@@ -631,6 +660,7 @@ function safeReadJson(file) {
631
660
  }
632
661
 
633
662
  module.exports = {
663
+ checkProjectInstructions,
634
664
  CHECKS,
635
665
  RTK_HOOK_FLOOR,
636
666
  extractVersion,
@@ -20,6 +20,7 @@ const { buildHookEntries, filterHooksRegisteredGlobally, mergeHooksIntoSettings
20
20
 
21
21
  const { ExistingInstall } = require('./existing-install');
22
22
  const { warnPreNativeSkillsLegacy } = require('./legacy-warnings');
23
+ const { applyProjectInstructions, removeProjectInstructions } = require('./project-instructions');
23
24
  const userFilePreservation = require('./user-file-preservation');
24
25
  const moduleConfigWriter = require('./module-config-writer');
25
26
  const helpCatalog = require('./help-catalog');
@@ -808,6 +809,11 @@ class Installer {
808
809
  addResult('Hooks settings', 'ok', `${pendingHooksSettingsWrite.count} hook(s) → .claude/settings.json${skippedDetail}`);
809
810
  }
810
811
 
812
+ // Bloco gerenciado no CLAUDE.md/AGENTS.md (2026-09-30): sem ele, um
813
+ // CLAUDE.md antigo vence as regras do Wizz, que chegam só por hook.
814
+ // Depois do swap pelo mesmo motivo do settings.json: vive fora de _wizz/.
815
+ await this._applyProjectInstructions(paths, config, addResult);
816
+
811
817
  await this._appendPreservedSkillManifestRows(paths.wizzDir, previousSkillManifestRows, preservedModules);
812
818
 
813
819
  // Apply post-install --set TOML patches. Runs after writeCentralConfig
@@ -1223,6 +1229,31 @@ class Installer {
1223
1229
  * @param {string} file
1224
1230
  * @returns {Promise<{settings: object, invalidJson: boolean}>}
1225
1231
  */
1232
+ async _applyProjectInstructions(paths, config, addResult) {
1233
+ if (!(await fs.pathExists(path.join(paths.wizzDir, 'wizz')))) return;
1234
+ try {
1235
+ const results = await applyProjectInstructions({
1236
+ projectRoot: paths.projectRoot,
1237
+ fs,
1238
+ createIfMissing: (config.ides || []).includes('claude-code'),
1239
+ });
1240
+ for (const r of results) {
1241
+ const name = path.basename(r.file);
1242
+ const legacy = r.findings.length > 0 ? `, ${r.findings.length} trecho(s) legado(s) marcados` : '';
1243
+ addResult('Project instructions', r.findings.length > 0 ? 'warn' : 'ok', `bloco Wizz no topo de ${name}${legacy}`);
1244
+ }
1245
+ const totalLegacy = results.reduce((n, r) => n + r.findings.length, 0);
1246
+ if (totalLegacy > 0) {
1247
+ await prompts.log.warn(
1248
+ `${totalLegacy} regra(s) antiga(s) no CLAUDE.md/AGENTS.md (GSD, superpowers, BMAD puro ou modelo aposentado). O bloco Wizz no topo tem prioridade, mas vale limpar: rode wizz doctor para ver as linhas.`,
1249
+ );
1250
+ }
1251
+ } catch (error) {
1252
+ await prompts.log.warn(`Falha ao gravar o bloco Wizz no CLAUDE.md/AGENTS.md: ${error.message}`);
1253
+ addResult('Project instructions', 'warn', error.message);
1254
+ }
1255
+ }
1256
+
1226
1257
  async _readSettingsJson(file) {
1227
1258
  if (!(await fs.pathExists(file))) return { settings: {}, invalidJson: false };
1228
1259
  try {
@@ -1600,6 +1631,7 @@ class Installer {
1600
1631
  // 2. IDE CLEANUP (before _wizz/ deletion so configs are accessible)
1601
1632
  if (options.removeIdeConfigs !== false) {
1602
1633
  await this.uninstallIdeConfigs(projectDir, existingInstall, { silent: options.silent });
1634
+ await removeProjectInstructions({ projectRoot: projectDir, fs });
1603
1635
  removed.ideConfigs = true;
1604
1636
  }
1605
1637
 
@@ -0,0 +1,199 @@
1
+ // project-instructions.js — bloco gerenciado no CLAUDE.md/AGENTS.md do projeto.
2
+ //
3
+ // Motivo (2026-09-30): o Claude Code carrega o CLAUDE.md do projeto como
4
+ // instrução de projeto (prioridade alta), enquanto as regras do Wizz chegam
5
+ // por hook e skill (contexto extra, prioridade menor). Num projeto que já
6
+ // tinha CLAUDE.md antigo (GSD, superpowers, BMAD puro, modelos aposentados),
7
+ // a regra velha vencia a nova. O installer agora grava um bloco no TOPO do
8
+ // arquivo dizendo que o Wizz vence em conflito e listando os trechos legados
9
+ // detectados. Nada fora do bloco é apagado ou reescrito.
10
+ //
11
+ // Lógica pura (texto → texto) + um aplicador fino com fs injetável, para os
12
+ // testes não dependerem de disco real.
13
+
14
+ const path = require('node:path');
15
+ const fsp = require('node:fs/promises');
16
+
17
+ const BLOCK_BEGIN = '<!-- wizz:begin -->';
18
+ const BLOCK_END = '<!-- wizz:end -->';
19
+ const INSTRUCTION_FILES = ['CLAUDE.md', 'AGENTS.md'];
20
+
21
+ // IDs de modelo aposentados. Compartilhado com tools/validate-model-ids.mjs.
22
+ // claude-haiku-4-5 continua atual (não há Haiku mais novo em 2026-09-30).
23
+ // `claude-(opus|sonnet)-5` sem sufixo foi trocado por 5-5; o lookahead evita
24
+ // casar o próprio `-5-5`.
25
+ const RETIRED_MODEL_RE =
26
+ /\bclaude-(?:instant|2|3|3-5|3-7)(?:-[a-z0-9]+)*\b|\bclaude-(?:opus|sonnet)-4(?:-\d+)?\b|\bclaude-(?:opus|sonnet)-5\b(?!-)/gi;
27
+
28
+ const LEGACY_RULES = [
29
+ { id: 'gsd', re: /\bGSD\b|get-shit-done|\/gsd[:-]/, why: 'GSD foi substituído pelas fases e checkpoints do Wizz Method' },
30
+ { id: 'superpowers', re: /\bsuperpowers\b/i, why: 'superpowers foi substituído pelo Wizz Method' },
31
+ {
32
+ id: 'bmad',
33
+ re: /_bmad\/|\/bmad[-:]|\bbmad-(?:master|agent|method|help)\b/i,
34
+ why: 'comandos e pastas do BMAD puro viraram wizz-* e _wizz/',
35
+ },
36
+ { id: 'modelo-aposentado', re: RETIRED_MODEL_RE, why: 'ID de modelo aposentado; a escada do Wizz usa os apelidos atuais' },
37
+ ];
38
+
39
+ const MAX_SNIPPET = 120;
40
+
41
+ // project-context.md (2026-09-30, porte do BMAD 6.12 #2750): em vez de cada
42
+ // skill procurar o arquivo em toda ativação (persistent_facts), o bloco
43
+ // importa uma vez por sessão. Busca rasa, pulando pastas de dependência/build.
44
+ const CONTEXT_FILE = 'project-context.md';
45
+ const CONTEXT_MAX_DEPTH = 4;
46
+ const CONTEXT_SKIP_DIRS = new Set(['node_modules', '.git', '_wizz', '.claude', 'build', 'dist', '.next', 'vendor', '.venv']);
47
+
48
+ /** Caminho relativo (posix) do project-context.md mais raso, ou null. */
49
+ async function findProjectContext(projectRoot) {
50
+ let level = [projectRoot];
51
+ for (let depth = 0; depth <= CONTEXT_MAX_DEPTH && level.length > 0; depth++) {
52
+ const next = [];
53
+ for (const dir of level.sort()) {
54
+ let entries;
55
+ try {
56
+ entries = await fsp.readdir(dir, { withFileTypes: true });
57
+ } catch {
58
+ continue;
59
+ }
60
+ if (entries.some((e) => e.isFile() && e.name === CONTEXT_FILE)) {
61
+ return path.relative(projectRoot, path.join(dir, CONTEXT_FILE)).split(path.sep).join('/');
62
+ }
63
+ for (const e of entries) if (e.isDirectory() && !CONTEXT_SKIP_DIRS.has(e.name)) next.push(path.join(dir, e.name));
64
+ }
65
+ level = next;
66
+ }
67
+ return null;
68
+ }
69
+
70
+ /** Linha de contexto por arquivo: CLAUDE.md importa com @; AGENTS.md só aponta. */
71
+ function contextLine(fileName, contextPath) {
72
+ if (!contextPath) return null;
73
+ return fileName === 'CLAUDE.md'
74
+ ? `Contexto do projeto (regras de stack e padrões, importado uma vez por sessão):\n@${contextPath}`
75
+ : `- Contexto do projeto: leia \`${contextPath}\` antes de implementar (regras de stack e padrões).`;
76
+ }
77
+
78
+ /** Separa o bloco gerenciado do resto do arquivo. */
79
+ function splitManagedBlock(text) {
80
+ const start = text.indexOf(BLOCK_BEGIN);
81
+ const end = text.indexOf(BLOCK_END);
82
+ if (start === -1 || end === -1 || end < start) return { block: null, rest: text };
83
+ const after = text.slice(end + BLOCK_END.length).replace(/^\r?\n+/, '');
84
+ return { block: text.slice(start, end + BLOCK_END.length), rest: text.slice(0, start) + after };
85
+ }
86
+
87
+ /**
88
+ * Procura regras legadas no texto, pulando as linhas do bloco gerenciado.
89
+ * `line` é a linha real no arquivo (1-based), para o doctor apontar.
90
+ * @returns {{id: string, line: number, snippet: string, why: string}[]}
91
+ */
92
+ function scanLegacy(text) {
93
+ const findings = [];
94
+ let inBlock = false;
95
+ for (const [i, line] of text.split(/\r?\n/).entries()) {
96
+ if (line.includes(BLOCK_BEGIN)) inBlock = true;
97
+ if (inBlock) {
98
+ if (line.includes(BLOCK_END)) inBlock = false;
99
+ continue;
100
+ }
101
+ const rule = LEGACY_RULES.find((r) => new RegExp(r.re.source, r.re.flags.replace('g', '')).test(line));
102
+ if (rule) findings.push({ id: rule.id, line: i + 1, snippet: line.trim().slice(0, MAX_SNIPPET), why: rule.why });
103
+ }
104
+ return findings;
105
+ }
106
+
107
+ /** Monta o bloco. Sem número de linha: o próprio bloco desloca as linhas. */
108
+ function renderManagedBlock(findings = [], context = null) {
109
+ const lines = [
110
+ BLOCK_BEGIN,
111
+ '<!-- Gerenciado pelo Wizz Method: o install reescreve este bloco. Edite fora dele. -->',
112
+ '## Wizz Method: estas regras têm prioridade',
113
+ '',
114
+ 'Este projeto usa o Wizz Method (`_wizz/`). Se alguma instrução deste arquivo, fora deste bloco, contradizer o Wizz Method, vale o Wizz Method.',
115
+ '',
116
+ '- Pedido não trivial: uma área vai para o agente da área; duas ou mais áreas vão para o `wizz-maestro`. Edição trivial de 1 linha e conversa executam direto.',
117
+ '- Planejamento, TDD, checkpoints e review seguem o Wizz Method. Não usar GSD, superpowers nem os comandos antigos do BMAD (`/bmad-*`, `_bmad/`).',
118
+ '- Modelos seguem a escada do Wizz (`wizz-exec-mechanical`, `-standard`, `-strong`, `-ultra`, `-review`), que usa os apelidos atuais. Ignore IDs de modelo fixos deste arquivo.',
119
+ ];
120
+ if (context) lines.push('', ...context.split('\n'));
121
+ if (findings.length > 0) {
122
+ lines.push('', 'Trechos legados detectados abaixo (não valem mais; `wizz doctor` lista de novo):');
123
+ for (const f of findings) lines.push(`- \`${f.snippet.replaceAll('`', "'")}\` (${f.why})`);
124
+ }
125
+ lines.push(BLOCK_END);
126
+ return lines.join('\n');
127
+ }
128
+
129
+ /** Insere ou substitui o bloco no topo. Idempotente. */
130
+ function upsertManagedBlock(text, context = null) {
131
+ const { rest } = splitManagedBlock(text);
132
+ const findings = scanLegacy(rest);
133
+ const block = renderManagedBlock(findings, context);
134
+ const body = rest.replace(/^\s+/, '');
135
+ return { text: body ? `${block}\n\n${body}` : `${block}\n`, findings };
136
+ }
137
+
138
+ /** Remove o bloco (uninstall). Devolve o texto intacto se não houver bloco. */
139
+ function removeManagedBlock(text) {
140
+ return splitManagedBlock(text).rest;
141
+ }
142
+
143
+ /**
144
+ * Aplica o bloco nos arquivos de instrução que existem na raiz do projeto.
145
+ * Sem nenhum dos dois, cria CLAUDE.md só se `createIfMissing`.
146
+ * Com project-context.md no projeto, o bloco importa (CLAUDE.md) ou aponta (AGENTS.md) para ele.
147
+ * @returns {Promise<{file: string, changed: boolean, findings: object[]}[]>}
148
+ */
149
+ async function applyProjectInstructions({ projectRoot, fs, createIfMissing = false }) {
150
+ const results = [];
151
+ const targets = [];
152
+ for (const name of INSTRUCTION_FILES) {
153
+ const file = path.join(projectRoot, name);
154
+ if (await fs.pathExists(file)) targets.push(file);
155
+ }
156
+ if (targets.length === 0 && createIfMissing) targets.push(path.join(projectRoot, 'CLAUDE.md'));
157
+
158
+ const contextPath = await findProjectContext(projectRoot);
159
+ for (const file of targets) {
160
+ const before = (await fs.pathExists(file)) ? await fs.readFile(file, 'utf8') : '';
161
+ const { text, findings } = upsertManagedBlock(before, contextLine(path.basename(file), contextPath));
162
+ const changed = text !== before;
163
+ if (changed) await fs.writeFile(file, text, 'utf8');
164
+ results.push({ file, changed, findings });
165
+ }
166
+ return results;
167
+ }
168
+
169
+ /** Tira o bloco dos arquivos de instrução (uninstall). */
170
+ async function removeProjectInstructions({ projectRoot, fs }) {
171
+ const removed = [];
172
+ for (const name of INSTRUCTION_FILES) {
173
+ const file = path.join(projectRoot, name);
174
+ if (!(await fs.pathExists(file))) continue;
175
+ const before = await fs.readFile(file, 'utf8');
176
+ const after = removeManagedBlock(before);
177
+ if (after !== before) {
178
+ await fs.writeFile(file, after, 'utf8');
179
+ removed.push(file);
180
+ }
181
+ }
182
+ return removed;
183
+ }
184
+
185
+ module.exports = {
186
+ BLOCK_BEGIN,
187
+ BLOCK_END,
188
+ INSTRUCTION_FILES,
189
+ findProjectContext,
190
+ contextLine,
191
+ RETIRED_MODEL_RE,
192
+ splitManagedBlock,
193
+ scanLegacy,
194
+ renderManagedBlock,
195
+ upsertManagedBlock,
196
+ removeManagedBlock,
197
+ applyProjectInstructions,
198
+ removeProjectInstructions,
199
+ };
@@ -0,0 +1,53 @@
1
+ const os = require('node:os');
2
+ const path = require('node:path');
3
+ const fs = require('node:fs');
4
+
5
+ /**
6
+ * Signs that an AI tool is present on this machine: a binary on PATH, a
7
+ * config folder in the home directory or an installed app. Any one is
8
+ * enough; this only decides which option comes pre-checked, never what
9
+ * gets installed.
10
+ */
11
+ const SIGNS = {
12
+ 'claude-code': { bins: ['claude'], homeDirs: ['.claude'], apps: [] },
13
+ codex: { bins: ['codex'], homeDirs: ['.codex'], apps: [] },
14
+ cursor: { bins: ['cursor'], homeDirs: ['.cursor'], apps: ['Cursor.app'] },
15
+ 'github-copilot': { bins: [], homeDirs: [], apps: [], vscodeExtension: 'github.copilot' },
16
+ };
17
+
18
+ function onPath(bin) {
19
+ const dirs = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
20
+ const exts = process.platform === 'win32' ? ['.exe', '.cmd', ''] : [''];
21
+ return dirs.some((dir) => exts.some((ext) => fs.existsSync(path.join(dir, bin + ext))));
22
+ }
23
+
24
+ function hasVscodeExtension(prefix, home) {
25
+ const extDir = path.join(home, '.vscode', 'extensions');
26
+ try {
27
+ return fs.readdirSync(extDir).some((name) => name.toLowerCase().startsWith(prefix));
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Which of the preferred AI tools look installed.
35
+ * @param {Object} [deps] - Injection seam for tests
36
+ * @param {string} [deps.home] - Home directory
37
+ * @returns {string[]} Tool ids, e.g. ['claude-code', 'cursor']
38
+ */
39
+ function detectAiTools(deps = {}) {
40
+ const home = deps.home || os.homedir();
41
+ const found = [];
42
+ for (const [id, sign] of Object.entries(SIGNS)) {
43
+ const present =
44
+ sign.bins.some((bin) => onPath(bin)) ||
45
+ sign.homeDirs.some((dir) => fs.existsSync(path.join(home, dir))) ||
46
+ sign.apps.some((app) => fs.existsSync(path.join('/Applications', app))) ||
47
+ (sign.vscodeExtension ? hasVscodeExtension(sign.vscodeExtension, home) : false);
48
+ if (present) found.push(id);
49
+ }
50
+ return found;
51
+ }
52
+
53
+ module.exports = { detectAiTools };