wizz-method 1.14.0 → 1.16.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 (148) hide show
  1. package/package.json +3 -2
  2. package/skills-registry.yaml +4 -0
  3. package/src/bmm-skills/3-solutioning/wizz-architecture/scripts/__pycache__/lint_spine.cpython-313.pyc +0 -0
  4. package/src/bmm-skills/3-solutioning/wizz-architecture/scripts/tests/__pycache__/test_lint_spine.cpython-313-pytest-9.1.1.pyc +0 -0
  5. package/src/bmm-skills/4-implementation/wizz-code-review/SKILL.md +3 -5
  6. package/src/bmm-skills/4-implementation/wizz-code-review/customize.toml +71 -0
  7. package/src/bmm-skills/4-implementation/wizz-code-review/references/claims-check.md +14 -0
  8. package/src/bmm-skills/4-implementation/wizz-code-review/references/deletion-check.md +14 -0
  9. package/src/bmm-skills/4-implementation/wizz-code-review/review-prompts/edge-case-hunter.md +110 -0
  10. package/src/bmm-skills/4-implementation/wizz-code-review/review-prompts/verification-gap.md +113 -0
  11. package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-01-gather-context.md +32 -23
  12. package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-02-review.md +10 -13
  13. package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-03-triage.md +16 -22
  14. package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-04-present.md +4 -2
  15. package/src/bmm-skills/4-implementation/wizz-quick-dev/customize.toml +76 -0
  16. package/src/bmm-skills/4-implementation/wizz-quick-dev/references/claims-check.md +14 -0
  17. package/src/bmm-skills/4-implementation/wizz-quick-dev/references/deletion-check.md +14 -0
  18. package/src/bmm-skills/4-implementation/wizz-quick-dev/review-prompts/edge-case-hunter.md +110 -0
  19. package/src/bmm-skills/4-implementation/wizz-quick-dev/review-prompts/verification-gap.md +113 -0
  20. package/src/bmm-skills/4-implementation/wizz-quick-dev/step-04-review.md +38 -16
  21. package/src/bmm-skills/4-implementation/wizz-retrospective/SKILL.md +56 -1488
  22. package/src/bmm-skills/4-implementation/wizz-retrospective/customize.toml +3 -5
  23. package/src/bmm-skills/4-implementation/wizz-retrospective/references/acceptance-verdict.md +55 -0
  24. package/src/bmm-skills/4-implementation/wizz-retrospective/references/aggregate-views.md +17 -0
  25. package/src/bmm-skills/4-implementation/wizz-retrospective/references/evidence-gathering.md +30 -0
  26. package/src/bmm-skills/4-implementation/wizz-retrospective/references/retro-document.md +84 -0
  27. package/src/bmm-skills/4-implementation/wizz-retrospective/references/team-discussion.md +22 -0
  28. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/__pycache__/sprint_status.cpython-313.pyc +0 -0
  29. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/git_evidence.py +304 -0
  30. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/sprint_status.py +746 -0
  31. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/__pycache__/test_git_evidence.cpython-313-pytest-9.1.1.pyc +0 -0
  32. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/__pycache__/test_sprint_status.cpython-313-pytest-9.1.1.pyc +0 -0
  33. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/fixtures/sprint-status-template.yaml +71 -0
  34. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/test_git_evidence.py +750 -0
  35. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/test_sprint_status.py +1579 -0
  36. package/src/bmm-skills/4-implementation/wizz-sprint-planning/SKILL.md +39 -296
  37. package/src/bmm-skills/4-implementation/wizz-sprint-planning/customize.toml +1 -3
  38. package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/fix-sprint-status.md +30 -0
  39. package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/generate-tracking.md +25 -0
  40. package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/readiness-gate.md +20 -0
  41. package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/status-view.md +14 -0
  42. package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/validate.md +10 -0
  43. package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/__pycache__/sprint_plan.cpython-313.pyc +0 -0
  44. package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/sprint_plan.py +697 -0
  45. package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/tests/__pycache__/test_sprint_plan.cpython-313-pytest-9.1.1.pyc +0 -0
  46. package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/tests/test_sprint_plan.py +524 -0
  47. package/src/bmm-skills/4-implementation/wizz-sprint-planning/sprint-status-template.yaml +9 -7
  48. package/src/bmm-skills/module-help.csv +3 -3
  49. package/src/core-skills/_shared/handoff-protocol.md +7 -8
  50. package/src/core-skills/module-help.csv +1 -0
  51. package/src/core-skills/wizz-advanced-elicitation/SKILL.md +36 -114
  52. package/src/core-skills/wizz-advanced-elicitation/{methods.csv → assets/methods.csv} +29 -27
  53. package/src/core-skills/wizz-advanced-elicitation/customize.toml +54 -0
  54. package/src/core-skills/wizz-advanced-elicitation/scripts/__pycache__/pick_methods.cpython-313.pyc +0 -0
  55. package/src/core-skills/wizz-advanced-elicitation/scripts/pick_methods.py +233 -0
  56. package/src/core-skills/wizz-advanced-elicitation/scripts/tests/__pycache__/test_pick_methods.cpython-313-pytest-9.1.1.pyc +0 -0
  57. package/src/core-skills/wizz-advanced-elicitation/scripts/tests/test_pick_methods.py +228 -0
  58. package/src/core-skills/wizz-brainstorming/SKILL.md +2 -2
  59. package/src/core-skills/wizz-brainstorming/assets/brain-selector.html +2 -0
  60. package/src/core-skills/wizz-brainstorming/references/converge.md +1 -1
  61. package/src/core-skills/wizz-brainstorming/references/finalize.md +1 -1
  62. package/src/core-skills/wizz-brainstorming/references/headless.md +1 -1
  63. package/src/core-skills/wizz-brainstorming/references/mode-autonomous.md +1 -1
  64. package/src/core-skills/wizz-brainstorming/scripts/__pycache__/brain.cpython-313.pyc +0 -0
  65. package/src/core-skills/wizz-brainstorming/scripts/brain.py +36 -6
  66. package/src/core-skills/wizz-brainstorming/scripts/tests/__pycache__/test_brain.cpython-313-pytest-9.1.1.pyc +0 -0
  67. package/src/core-skills/wizz-brainstorming/scripts/tests/__pycache__/test_brain.cpython-314.pyc +0 -0
  68. package/src/core-skills/wizz-brainstorming/scripts/tests/test_brain.py +24 -2
  69. package/src/core-skills/wizz-customize/scripts/__pycache__/list_customizable_skills.cpython-313.pyc +0 -0
  70. package/src/core-skills/wizz-customize/scripts/tests/__pycache__/test_list_customizable_skills.cpython-313-pytest-9.1.1.pyc +0 -0
  71. package/src/core-skills/wizz-forge-idea/SKILL.md +107 -0
  72. package/src/core-skills/wizz-forge-idea/customize.toml +41 -0
  73. package/src/core-skills/wizz-forge-idea/scripts/__pycache__/resolve_personas.cpython-313.pyc +0 -0
  74. package/src/core-skills/wizz-forge-idea/scripts/__pycache__/resolve_personas.cpython-314.pyc +0 -0
  75. package/src/core-skills/wizz-forge-idea/scripts/resolve_personas.py +275 -0
  76. package/src/core-skills/wizz-forge-idea/scripts/tests/__pycache__/test_resolve_personas.cpython-313-pytest-9.1.1.pyc +0 -0
  77. package/src/core-skills/wizz-forge-idea/scripts/tests/__pycache__/test_resolve_personas.cpython-314.pyc +0 -0
  78. package/src/core-skills/wizz-forge-idea/scripts/tests/test_resolve_personas.py +138 -0
  79. package/src/core-skills/wizz-party-mode/SKILL.md +37 -54
  80. package/src/core-skills/wizz-party-mode/customize.toml +61 -2
  81. package/src/core-skills/wizz-party-mode/references/create-party.md +8 -3
  82. package/src/core-skills/wizz-party-mode/references/mode-agent-team.md +3 -1
  83. package/src/core-skills/wizz-party-mode/references/mode-subagent.md +16 -4
  84. package/src/core-skills/wizz-party-mode/references/party-memory.md +51 -0
  85. package/src/core-skills/wizz-party-mode/scripts/__pycache__/resolve_party.cpython-313.pyc +0 -0
  86. package/src/core-skills/wizz-party-mode/scripts/__pycache__/resolve_party.cpython-314.pyc +0 -0
  87. package/src/core-skills/wizz-party-mode/scripts/resolve_party.py +22 -7
  88. package/src/core-skills/wizz-party-mode/scripts/tests/__pycache__/test_resolve_party.cpython-313-pytest-9.1.1.pyc +0 -0
  89. package/src/core-skills/wizz-party-mode/scripts/tests/__pycache__/test_resolve_party.cpython-314.pyc +0 -0
  90. package/src/core-skills/wizz-party-mode/scripts/tests/{test-resolve_party.py → test_resolve_party.py} +8 -0
  91. package/src/core-skills/wizz-review-edge-case-hunter/SKILL.md +17 -3
  92. package/src/modules/wizz/README.md +1 -1
  93. package/src/modules/wizz/_shared/model-ladder.md +22 -0
  94. package/src/modules/wizz/_shared/token-economy.md +6 -2
  95. package/src/modules/wizz/agents/wizz-ads/customize.toml +1 -1
  96. package/src/modules/wizz/agents/wizz-copy/customize.toml +1 -1
  97. package/src/modules/wizz/agents/wizz-designer/customize.toml +1 -1
  98. package/src/modules/wizz/agents/wizz-growth/customize.toml +11 -1
  99. package/src/modules/wizz/agents/wizz-maestro/customize.toml +1 -1
  100. package/src/modules/wizz/agents/wizz-memoria/customize.toml +1 -1
  101. package/src/modules/wizz/agents/wizz-qa/customize.toml +1 -1
  102. package/src/modules/wizz/agents/wizz-seo/customize.toml +1 -1
  103. package/src/modules/wizz/agents/wizz-social/customize.toml +1 -1
  104. package/src/modules/wizz/overrides/wizz-agent-analyst.toml +1 -1
  105. package/src/modules/wizz/overrides/wizz-agent-architect.toml +1 -1
  106. package/src/modules/wizz/overrides/wizz-agent-dev.toml +1 -1
  107. package/src/modules/wizz/overrides/wizz-agent-pm.toml +1 -1
  108. package/src/modules/wizz/overrides/wizz-agent-tech-writer.toml +1 -1
  109. package/src/modules/wizz/overrides/wizz-agent-ux-designer.toml +1 -1
  110. package/src/modules/wizz/subagents/codex/wizz-exec-haiku.toml +16 -0
  111. package/src/modules/wizz/subagents/codex/wizz-exec-opus.toml +18 -0
  112. package/src/modules/wizz/subagents/codex/wizz-exec-review.toml +17 -0
  113. package/src/modules/wizz/subagents/codex/wizz-exec-sonnet.toml +17 -0
  114. package/src/modules/wizz/subagents/gemini/wizz-exec-haiku.md +15 -0
  115. package/src/modules/wizz/subagents/gemini/wizz-exec-opus.md +17 -0
  116. package/src/modules/wizz/subagents/gemini/wizz-exec-review.md +15 -0
  117. package/src/modules/wizz/subagents/gemini/wizz-exec-sonnet.md +16 -0
  118. package/src/modules/wizz/subagents/opencode/wizz-exec-haiku.md +14 -0
  119. package/src/modules/wizz/subagents/opencode/wizz-exec-opus.md +16 -0
  120. package/src/modules/wizz/subagents/opencode/wizz-exec-review.md +16 -0
  121. package/src/modules/wizz/subagents/opencode/wizz-exec-sonnet.md +15 -0
  122. package/src/modules/wizz/subagents/wizz-exec-opus.md +16 -0
  123. package/src/modules/wizz/subagents/wizz-exec-review.md +15 -0
  124. package/src/scripts/__pycache__/memlog.cpython-313.pyc +0 -0
  125. package/src/scripts/tests/__pycache__/test_memlog.cpython-313-pytest-9.1.1.pyc +0 -0
  126. package/src/scripts/tests/__pycache__/test_resolve_customization.cpython-313-pytest-9.1.1.pyc +0 -0
  127. package/src/skills-lib/pesquisa-de-publico-do-piva/README.md +83 -0
  128. package/src/skills-lib/pesquisa-de-publico-do-piva/SKILL.md +65 -0
  129. package/src/skills-lib/pesquisa-de-publico-do-piva/assets/template.html +414 -0
  130. package/src/skills-lib/pesquisa-de-publico-do-piva/prompts.md +131 -0
  131. package/src/skills-lib/wizz-offer-forge/README.md +13 -0
  132. package/src/skills-lib/wizz-offer-forge/SKILL.md +74 -0
  133. package/src/skills-lib/wizz-offer-forge/references/empilhamento-valor.md +25 -0
  134. package/src/skills-lib/wizz-offer-forge/references/garantia-reversa.md +22 -0
  135. package/src/skills-lib/wizz-offer-forge/references/mecanismo-unico.md +28 -0
  136. package/src/skills-lib/wizz-offer-forge/references/ponte-trafego.md +21 -0
  137. package/src/skills-lib/wizz-router/references/routing-table-flat.md +2 -0
  138. package/tools/installer/core/installer.js +57 -0
  139. package/tools/installer/ide/_config-driven.js +44 -30
  140. package/tools/installer/ide/platform-codes.yaml +42 -3
  141. package/tools/installer/modules/external-manager.js +140 -2
  142. package/tools/installer/modules/official-modules.js +68 -21
  143. package/tools/installer/prompts.js +45 -103
  144. package/tools/installer/ui.js +64 -7
  145. package/wizz-modules.yaml +65 -11
  146. package/src/bmm-skills/4-implementation/wizz-sprint-planning/checklist.md +0 -34
  147. package/src/core-skills/wizz-brainstorming/scripts/memlog.py +0 -202
  148. package/src/core-skills/wizz-brainstorming/scripts/tests/test_memlog.py +0 -265
@@ -94,6 +94,13 @@ class ExternalModuleManager {
94
94
  // ExternalModuleManager) sees resolutions made during install.
95
95
  static _resolutions = new Map();
96
96
 
97
+ // moduleCode → ResolvedModule (do PluginResolver). Preenchido para módulos do
98
+ // registry marcados com `marketplace-plugin: true`, cujas skills instaláveis
99
+ // vivem fora de um único diretório de module.yaml e precisam ser resolvidas a
100
+ // partir de .claude-plugin/marketplace.json. Compartilhado entre instâncias
101
+ // para o install() aproveitar a resolução feita no findModuleSource.
102
+ static _pluginResolutions = new Map();
103
+
97
104
  constructor() {}
98
105
 
99
106
  /**
@@ -105,6 +112,15 @@ class ExternalModuleManager {
105
112
  return ExternalModuleManager._resolutions.get(moduleCode) || null;
106
113
  }
107
114
 
115
+ /**
116
+ * Resolução marketplace-plugin em cache para um módulo (se houver).
117
+ * @param {string} moduleCode
118
+ * @returns {Object|null} ResolvedModule do PluginResolver, ou null
119
+ */
120
+ getPluginResolution(moduleCode) {
121
+ return ExternalModuleManager._pluginResolutions.get(moduleCode) || null;
122
+ }
123
+
108
124
  /**
109
125
  * Load the official modules registry from the bundled YAML file.
110
126
  * @returns {Object} Parsed YAML content with modules array
@@ -147,8 +163,16 @@ class ExternalModuleManager {
147
163
  npmPackage: mod.npm_package || mod.npmPackage || null,
148
164
  pluginName: mod.plugin_name || mod.pluginName || null,
149
165
  defaultChannel: normalizeChannelName(mod.default_channel || mod.defaultChannel) || 'stable',
166
+ deprecated: mod.deprecated === true,
167
+ deprecationMessage: mod.deprecation_message || mod['deprecation-message'] || mod.deprecationMessage || null,
168
+ marketplacePlugin: mod.marketplace_plugin === true || mod['marketplace-plugin'] === true || mod.marketplacePlugin === true,
169
+ postInstallMessage: mod.post_install_message || mod['post-install-message'] || mod.postInstallMessage || null,
150
170
  builtIn: mod.built_in === true,
151
171
  isExternal: mod.built_in !== true,
172
+ // Códigos anteriores sob os quais este módulo foi registrado (ex.: `bmad-loop`
173
+ // era `bauto`). Deixa um módulo renomeado continuar resolvendo instalações
174
+ // existentes em vez de órfã-las — ver getModuleByCode().
175
+ aliases: Array.isArray(mod.aliases) ? mod.aliases : [],
152
176
  };
153
177
  }
154
178
 
@@ -173,13 +197,27 @@ class ExternalModuleManager {
173
197
  }
174
198
 
175
199
  /**
176
- * Get module info by code
200
+ * Get module info by code. Cai no `aliases` da entrada do registry, de modo
201
+ * que um módulo renomeado (com `code` alterado) ainda resolve para instalações
202
+ * gravadas sob o código anterior.
177
203
  * @param {string} code - The module code (e.g., 'cis')
178
204
  * @returns {Object|null} Module info or null if not found
179
205
  */
180
206
  async getModuleByCode(code) {
181
207
  const modules = await this.listAvailable();
182
- return modules.find((m) => m.code === code) || null;
208
+ return modules.find((m) => m.code === code) || modules.find((m) => m.aliases.includes(code)) || null;
209
+ }
210
+
211
+ /**
212
+ * Resolve a possibly-legacy module code to its current canonical code.
213
+ * Returns the input unchanged if it doesn't match any registry entry or
214
+ * alias (e.g. a custom/unknown module).
215
+ * @param {string} code
216
+ * @returns {Promise<string>}
217
+ */
218
+ async resolveCanonicalCode(code) {
219
+ const info = await this.getModuleByCode(code);
220
+ return info ? info.code : code;
183
221
  }
184
222
 
185
223
  /**
@@ -502,6 +540,24 @@ class ExternalModuleManager {
502
540
  return null;
503
541
  }
504
542
 
543
+ // Normaliza para o código canônico — um install gravado sob um alias
544
+ // (ex.: `bauto`) precisa resolver para a entrada atual do registry.
545
+ moduleCode = moduleInfo.code;
546
+
547
+ // Módulos marketplace-plugin (entradas do registry marcadas com
548
+ // `marketplace-plugin`) trazem um .claude-plugin/marketplace.json e mantêm o
549
+ // module.yaml dentro do assets/ de uma skill, e não num diretório único ao
550
+ // lado das skills. Resolve pelo PluginResolver (a mesma máquina que os
551
+ // installs por URL custom usam) e devolve o diretório que contém o
552
+ // module.yaml, para os chamadores de config/versão/diretório funcionarem;
553
+ // o install() copia os diretórios de skill resolvidos.
554
+ if (moduleInfo.marketplacePlugin) {
555
+ const pluginMod = await this.resolvePluginModule(moduleCode, options);
556
+ if (pluginMod && pluginMod.moduleYamlPath) {
557
+ return path.dirname(pluginMod.moduleYamlPath);
558
+ }
559
+ }
560
+
505
561
  // Clone the external module repo
506
562
  const cloneDir = await this.cloneExternalModule(moduleCode, options);
507
563
 
@@ -555,6 +611,88 @@ class ExternalModuleManager {
555
611
  `The repository may have been restructured after this release was tagged.${channelHint}`,
556
612
  );
557
613
  }
614
+ /**
615
+ * Resolve um módulo marketplace-plugin do registry para uma definição de
616
+ * plugin instalável. Clona o repo (respeitando o plano de canal), lê o
617
+ * .claude-plugin/marketplace.json e roda o PluginResolver contra o plugin que
618
+ * corresponde a este módulo. O resultado (skillPaths + module.yaml +
619
+ * module-help.csv) fica em cache para o install() copiar os diretórios de
620
+ * skill resolvidos.
621
+ *
622
+ * @param {string} moduleCode - Código do módulo externo
623
+ * @param {Object} options - Opções repassadas a cloneExternalModule
624
+ * @returns {Promise<Object|null>} ResolvedModule do PluginResolver, ou null
625
+ * quando o módulo não é marketplace plugin ou não pode ser resolvido.
626
+ */
627
+ async resolvePluginModule(moduleCode, options = {}) {
628
+ const moduleInfo = await this.getModuleByCode(moduleCode);
629
+ if (!moduleInfo || moduleInfo.builtIn || !moduleInfo.marketplacePlugin) {
630
+ return null;
631
+ }
632
+
633
+ // Normaliza para o código canônico — ver cloneExternalModule.
634
+ moduleCode = moduleInfo.code;
635
+
636
+ const cloneDir = await this.cloneExternalModule(moduleCode, options);
637
+
638
+ const marketplacePath = path.join(cloneDir, '.claude-plugin', 'marketplace.json');
639
+ if (!(await fs.pathExists(marketplacePath))) {
640
+ return null;
641
+ }
642
+
643
+ let marketplace;
644
+ try {
645
+ marketplace = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
646
+ } catch {
647
+ return null;
648
+ }
649
+
650
+ const plugins = Array.isArray(marketplace.plugins) ? marketplace.plugins : [];
651
+ if (plugins.length === 0) {
652
+ return null;
653
+ }
654
+
655
+ // Prefere o plugin cujo nome bate com o plugin_name do registry (ou o code),
656
+ // caindo para qualquer plugin que declare skills.
657
+ const preferredName = moduleInfo.pluginName || moduleInfo.code;
658
+ const ordered = [...plugins].sort((a, b) => {
659
+ const am = a?.name === preferredName ? 0 : 1;
660
+ const bm = b?.name === preferredName ? 0 : 1;
661
+ return am - bm;
662
+ });
663
+
664
+ const { PluginResolver } = require('./plugin-resolver');
665
+ const resolver = new PluginResolver();
666
+
667
+ for (const plugin of ordered) {
668
+ if (!plugin || !Array.isArray(plugin.skills) || plugin.skills.length === 0) {
669
+ continue;
670
+ }
671
+ let resolvedMods;
672
+ try {
673
+ resolvedMods = await resolver.resolve(cloneDir, plugin);
674
+ } catch {
675
+ continue;
676
+ }
677
+ if (!resolvedMods || resolvedMods.length === 0) {
678
+ continue;
679
+ }
680
+ // Casa pelo código do registry, depois pelo nome preferido do plugin,
681
+ // depois aceita um único módulo resolvido.
682
+ const match =
683
+ resolvedMods.find((mod) => mod.code === moduleCode) ||
684
+ resolvedMods.find((mod) => mod.code === preferredName) ||
685
+ (resolvedMods.length === 1 ? resolvedMods[0] : null);
686
+ if (match) {
687
+ match.repoUrl = moduleInfo.url;
688
+ ExternalModuleManager._pluginResolutions.set(moduleCode, match);
689
+ return match;
690
+ }
691
+ }
692
+
693
+ return null;
694
+ }
695
+
558
696
  cachedModules = null;
559
697
  }
560
698
 
@@ -316,7 +316,29 @@ class OfficialModules {
316
316
  await fs.remove(targetPath);
317
317
  }
318
318
 
319
- await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig);
319
+ // Módulos marketplace-plugin do registry mantêm as skills instaláveis fora do
320
+ // diretório que contém o module.yaml (sourcePath aponta para o assets/ da
321
+ // skill -setup), então não dá para instalá-los copiando o sourcePath. Copia
322
+ // os diretórios de skill resolvidos, do mesmo jeito que um install custom de
323
+ // marketplace monta um módulo. Todo o resto (manifest, versão) segue pelo
324
+ // caminho padrão de módulo externo abaixo.
325
+ const marketplaceInfo = await this.externalModuleManager.getModuleByCode(moduleName);
326
+ if (marketplaceInfo && marketplaceInfo.marketplacePlugin) {
327
+ const pluginResolution = this.externalModuleManager.getPluginResolution(moduleName);
328
+ // Falha alto: copiar o sourcePath aqui instalaria só o assets/ da skill
329
+ // -setup (module.yaml + module-help.csv) e nenhuma skill — um install
330
+ // parcial e silenciosamente quebrado. Aborta em vez disso.
331
+ if (!pluginResolution || !Array.isArray(pluginResolution.skillPaths) || pluginResolution.skillPaths.length === 0) {
332
+ throw new Error(
333
+ `Module '${moduleName}' is registered as a marketplace plugin but its skills could not be resolved ` +
334
+ `from .claude-plugin/marketplace.json (missing or malformed on the selected channel). ` +
335
+ `Aborting to avoid a partial install with no skills.`,
336
+ );
337
+ }
338
+ await this._copyResolvedSkills(pluginResolution, targetPath, fileTrackingCallback, options.moduleConfig);
339
+ } else {
340
+ await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig);
341
+ }
320
342
 
321
343
  if (!options.skipModuleInstaller) {
322
344
  await this.createModuleDirectories(moduleName, wizzDir, options);
@@ -343,41 +365,66 @@ class OfficialModules {
343
365
  }
344
366
 
345
367
  /**
346
- * Install a module from a PluginResolver resolution result.
347
- * Copies specific skill directories and places module-help.csv at the target root.
348
- * @param {Object} resolved - ResolvedModule from PluginResolver
349
- * @param {string} wizzDir - Target wizz directory
350
- * @param {Function} fileTrackingCallback - Optional callback to track installed files
351
- * @param {Object} options - Installation options
368
+ * Assenta em disco uma resolução do PluginResolver: copia cada diretório de
369
+ * skill resolvido (achatado pelo nome da folha) para targetPath e coloca o
370
+ * module-help.csv na raiz do módulo. Compartilhado pelos installs custom de
371
+ * marketplace (installFromResolution) e pelos installs oficiais de módulo
372
+ * marketplace-plugin (install), para os dois caminhos não divergirem.
373
+ * @param {Object} resolved - ResolvedModule do PluginResolver
374
+ * @param {string} targetPath - Diretório de destino do módulo (ex.: wizzDir/<code>)
375
+ * @param {Function} fileTrackingCallback - Callback opcional de rastreio de arquivos
376
+ * @param {Object} moduleConfig - Configuração do módulo repassada ao filtro de cópia
352
377
  */
353
- async installFromResolution(resolved, wizzDir, fileTrackingCallback = null, options = {}) {
354
- const targetPath = path.join(wizzDir, resolved.code);
355
-
356
- if (await fs.pathExists(targetPath)) {
357
- await fs.remove(targetPath);
358
- }
359
-
378
+ async _copyResolvedSkills(resolved, targetPath, fileTrackingCallback = null, moduleConfig = {}) {
360
379
  await fs.ensureDir(targetPath);
361
380
 
362
- // Copy each skill directory, flattened by leaf name
381
+ // Copia cada diretório de skill, achatado pelo nome da folha. Os nomes de
382
+ // folha precisam ser únicos — duas skills que achatam para o mesmo diretório
383
+ // se sobrescreveriam em silêncio, então falha alto em vez disso.
384
+ const seenLeaves = new Map();
363
385
  for (const skillPath of resolved.skillPaths) {
364
386
  const skillDirName = path.basename(skillPath);
387
+ if (seenLeaves.has(skillDirName)) {
388
+ throw new Error(
389
+ `Cannot install module '${resolved.code}': skill directories '${seenLeaves.get(skillDirName)}' and ` +
390
+ `'${skillPath}' share the leaf name '${skillDirName}' and would overwrite each other. ` +
391
+ `Skill directory names must be unique.`,
392
+ );
393
+ }
394
+ seenLeaves.set(skillDirName, skillPath);
365
395
  const skillTarget = path.join(targetPath, skillDirName);
366
- await this.copyModuleWithFiltering(skillPath, skillTarget, fileTrackingCallback, options.moduleConfig);
396
+ await this.copyModuleWithFiltering(skillPath, skillTarget, fileTrackingCallback, moduleConfig);
367
397
  }
368
398
 
369
- // Place module-help.csv at the module root
399
+ // Coloca o module-help.csv na raiz do módulo.
400
+ const helpTarget = path.join(targetPath, 'module-help.csv');
370
401
  if (resolved.moduleHelpCsvPath) {
371
- // Strategies 1-4: copy the existing file
372
- const helpTarget = path.join(targetPath, 'module-help.csv');
402
+ // Estratégias 1-4: copia o arquivo existente.
373
403
  await fs.copy(resolved.moduleHelpCsvPath, helpTarget, { overwrite: true });
374
404
  if (fileTrackingCallback) fileTrackingCallback(helpTarget);
375
405
  } else if (resolved.synthesizedHelpCsv) {
376
- // Strategy 5: write synthesized content
377
- const helpTarget = path.join(targetPath, 'module-help.csv');
406
+ // Estratégia 5: escreve o conteúdo sintetizado.
378
407
  await fs.writeFile(helpTarget, resolved.synthesizedHelpCsv, 'utf8');
379
408
  if (fileTrackingCallback) fileTrackingCallback(helpTarget);
380
409
  }
410
+ }
411
+
412
+ /**
413
+ * Install a module from a PluginResolver resolution result.
414
+ * Copies specific skill directories and places module-help.csv at the target root.
415
+ * @param {Object} resolved - ResolvedModule from PluginResolver
416
+ * @param {string} wizzDir - Target wizz directory
417
+ * @param {Function} fileTrackingCallback - Optional callback to track installed files
418
+ * @param {Object} options - Installation options
419
+ */
420
+ async installFromResolution(resolved, wizzDir, fileTrackingCallback = null, options = {}) {
421
+ const targetPath = path.join(wizzDir, resolved.code);
422
+
423
+ if (await fs.pathExists(targetPath)) {
424
+ await fs.remove(targetPath);
425
+ }
426
+
427
+ await this._copyResolvedSkills(resolved, targetPath, fileTrackingCallback, options.moduleConfig);
381
428
 
382
429
  // Create directories declared in module.yaml (strategies 1-4 may have these)
383
430
  if (!options.skipModuleInstaller) {
@@ -10,7 +10,6 @@
10
10
  let _clack = null;
11
11
  let _clackCore = null;
12
12
  let _picocolors = null;
13
- const fs = require('node:fs');
14
13
  const os = require('node:os');
15
14
  const path = require('node:path');
16
15
 
@@ -578,10 +577,6 @@ async function autocomplete(options) {
578
577
  return result;
579
578
  }
580
579
 
581
- function hasPathSeparator(value) {
582
- return value.endsWith('/') || value.endsWith('\\');
583
- }
584
-
585
580
  function expandHome(input) {
586
581
  if (!input) return input;
587
582
  if (input === '~') return os.homedir();
@@ -591,91 +586,60 @@ function expandHome(input) {
591
586
  return input;
592
587
  }
593
588
 
594
- function toDirectoryOption(value, label = value, synthetic = false) {
595
- return { value, label, synthetic };
596
- }
597
-
598
- function isExistingDirectory(value) {
599
- try {
600
- return fs.existsSync(value) && fs.statSync(value).isDirectory();
601
- } catch {
602
- return false;
603
- }
604
- }
605
-
606
- function listDirectoryOptions(input, options) {
589
+ /**
590
+ * Resolve o texto do prompt de diretório para um caminho absoluto.
591
+ * @param {string} input - Texto submetido
592
+ * @param {Object} [options] - { cwd, default }
593
+ * @returns {string} Caminho absoluto
594
+ */
595
+ function resolveDirectoryInput(input, options = {}) {
607
596
  const cwd = options.cwd || process.cwd();
608
- const rawInput = input.trim();
609
- const expandedInput = expandHome(rawInput);
610
- const trailingSep = hasPathSeparator(rawInput) || hasPathSeparator(expandedInput);
611
- const resolvedInput = expandedInput ? path.resolve(cwd, expandedInput) : cwd;
612
- const browseDir = expandedInput && !trailingSep && !isExistingDirectory(resolvedInput) ? path.dirname(resolvedInput) : resolvedInput;
613
- const prefix = expandedInput && browseDir !== resolvedInput ? path.basename(resolvedInput).toLowerCase() : '';
614
- const results = [];
615
-
616
- if (!trailingSep && isExistingDirectory(resolvedInput)) {
617
- results.push(toDirectoryOption(resolvedInput, `. (use this directory)`));
618
- }
619
-
620
- if (isExistingDirectory(browseDir)) {
621
- try {
622
- for (const entry of fs.readdirSync(browseDir, { withFileTypes: true })) {
623
- if (!entry.isDirectory()) continue;
624
- if (prefix && !entry.name.toLowerCase().startsWith(prefix)) continue;
625
- const fullPath = path.join(browseDir, entry.name);
626
- if (!results.some((option) => option.value === fullPath)) {
627
- results.push(toDirectoryOption(fullPath));
628
- }
629
- }
630
- } catch {
631
- // Skip unreadable directories; validation still reports path issues.
632
- }
633
- }
634
-
635
- const validation = options.validate?.(rawInput);
636
- const hasMatchingOption = results.some((option) => option.value === resolvedInput);
637
- if (expandedInput && !validation && !hasMatchingOption) {
638
- results.unshift(toDirectoryOption(resolvedInput, `Create/use: ${resolvedInput}`, true));
639
- }
640
-
641
- return results;
597
+ const rawInput = typeof input === 'string' ? input.trim() : '';
598
+ // Linha vazia significa "usar o default", resolvido do mesmo jeito que texto
599
+ // digitado, para um default `~/…` ou relativo ainda devolver caminho absoluto.
600
+ const effective = rawInput || (options.default || '').trim();
601
+ if (!effective) return cwd;
602
+ return path.resolve(cwd, expandHome(effective));
642
603
  }
643
604
 
644
605
  /**
645
- * Directory prompt with autocomplete candidates and create-directory support.
646
- * Uses @clack/core directly so typed paths that do not exist yet can still be
647
- * submitted when validation allows creating them.
606
+ * Directory prompt.
607
+ *
608
+ * Entrada de texto simples: o que está na linha é o que é submetido, resolvido
609
+ * para caminho absoluto.
610
+ *
611
+ * A linha vem pré-preenchida com o default (o diretório atual, de onde os
612
+ * installs costumam ser rodados) como texto editável de verdade, e não como
613
+ * placeholder, para poder ser encurtada ou estendida em vez de redigitada.
614
+ * Limpar a linha e apertar Enter ainda aceita o default.
615
+ *
616
+ * Substitui uma AutocompletePrompt cuja lista de candidatos era invisível: o
617
+ * Enter devolvia a opção em foco, não o texto digitado, então dava para instalar
618
+ * num caminho que o usuário nunca escreveu. Com TextPrompt o defeito some por
619
+ * construção — não existe seleção escondida que o Enter possa submeter.
620
+ *
648
621
  * @param {Object} options - Prompt options
649
622
  * @param {string} options.message - Prompt message
650
- * @param {string} [options.default] - Default directory
651
- * @param {string} [options.placeholder] - Placeholder text
623
+ * @param {string} [options.default] - Default directory, pré-preenchido na linha
624
+ * @param {string} [options.placeholder] - Placeholder mostrado só se a linha for limpa
652
625
  * @param {Function} [options.validate] - Sync validation function
653
- * @returns {Promise<string>} Selected or typed directory path
626
+ * @param {Object} [options.input] - Input stream (default process.stdin; testes injetam)
627
+ * @param {Object} [options.output] - Output stream (default process.stdout; testes injetam)
628
+ * @returns {Promise<string>} Caminho absoluto do diretório
654
629
  */
655
630
  async function directory(options) {
656
631
  const core = await getClackCore();
657
632
  const color = await getPicocolors();
658
- const tabCompletion = {
659
- prefix: '',
660
- index: -1,
661
- options: [],
662
- lastValue: '',
663
- };
664
633
 
665
- let prompt;
666
- prompt = new core.AutocompletePrompt({
634
+ const prompt = new core.TextPrompt({
635
+ ...(options.input ? { input: options.input } : {}),
636
+ ...(options.output ? { output: options.output } : {}),
667
637
  initialValue: options.default,
668
- options: () => listDirectoryOptions(prompt?.userInput || '', options),
669
- filter: () => true,
670
- validate: (value) => options.validate?.(value ?? prompt.userInput),
638
+ defaultValue: options.default,
639
+ validate: options.validate,
671
640
  render() {
672
- const title = `${color.gray('◆')} ${options.message}`;
673
641
  const bar = color.gray('│');
674
- const barEnd = color.gray('└');
675
642
  const userInput = this.userInput;
676
- const placeholder = options.placeholder || options.default;
677
- const inputDisplay = userInput ? this.userInputWithCursor : `${color.inverse(color.hidden('_'))}${color.dim(placeholder || '')}`;
678
- const errorLine = this.state === 'error' ? [`${color.yellow('│')} ${color.yellow(this.error)}`] : [];
679
643
 
680
644
  switch (this.state) {
681
645
  case 'submit': {
@@ -685,42 +649,20 @@ async function directory(options) {
685
649
  return `${color.gray('◇')} ${options.message}\n${bar} ${color.strikethrough(color.dim(userInput || ''))}`;
686
650
  }
687
651
  default: {
688
- return [title, `${bar} ${inputDisplay}`, ...errorLine, barEnd].join('\n');
652
+ const placeholder = options.placeholder || options.default;
653
+ const inputDisplay = userInput ? this.userInputWithCursor : `${color.inverse(color.hidden('_'))}${color.dim(placeholder || '')}`;
654
+ const lines = [`${color.gray('◆')} ${options.message}`, `${bar} ${inputDisplay}`];
655
+ if (this.state === 'error') lines.push(`${color.yellow('│')} ${color.yellow(this.error)}`);
656
+ lines.push(color.gray('└'));
657
+ return lines.join('\n');
689
658
  }
690
659
  }
691
660
  },
692
661
  });
693
662
 
694
- const hasSetUserInput = typeof prompt._setUserInput === 'function';
695
- const hasClearUserInput = typeof prompt._clearUserInput === 'function';
696
-
697
- prompt.on('key', (_, key) => {
698
- if (key?.name !== 'tab') return;
699
- if (!hasSetUserInput) return; // @clack/core API surface changed — skip Tab silently.
700
- const currentInput = prompt.userInput;
701
- const isContinuingCycle = tabCompletion.lastValue && currentInput === tabCompletion.lastValue;
702
- const completionOptions = isContinuingCycle ? tabCompletion.options : prompt.filteredOptions.filter((option) => !option.synthetic);
703
- if (completionOptions.length === 0) return;
704
-
705
- if (isContinuingCycle) {
706
- tabCompletion.index = (tabCompletion.index + 1) % completionOptions.length;
707
- } else {
708
- tabCompletion.prefix = currentInput;
709
- tabCompletion.options = completionOptions;
710
- tabCompletion.index = 0;
711
- }
712
-
713
- const focusedOption = completionOptions[tabCompletion.index];
714
- if (!focusedOption) return;
715
- const completedValue = focusedOption.value;
716
- tabCompletion.lastValue = completedValue;
717
- if (hasClearUserInput) prompt._clearUserInput();
718
- prompt._setUserInput(completedValue, true);
719
- });
720
-
721
663
  const result = await prompt.prompt();
722
664
  await handleCancel(result);
723
- return result;
665
+ return resolveDirectoryInput(result, options);
724
666
  }
725
667
 
726
668
  /**
@@ -114,6 +114,45 @@ async function getModuleVersion(moduleCode, { repoUrl = null, registryDefault =
114
114
  * UI utilities for the installer
115
115
  */
116
116
  class UI {
117
+ /**
118
+ * Avisa uma vez para cada módulo selecionado que o registry marca como
119
+ * depreciado.
120
+ *
121
+ * Um módulo depreciado nunca é removido da seleção — uma instalação
122
+ * existente continua funcionando e continua sendo atualizada quando pedido.
123
+ * O aviso é a única mudança de comportamento, e é ele que informa quem usa
124
+ * CLI (`--modules`, `--yes`) o que o picker interativo mostra como dica.
125
+ *
126
+ * @param {Array<string>} selectedModules - Códigos prestes a ser instalados
127
+ * @returns {Promise<Array<string>>} Os códigos depreciados que geraram aviso
128
+ */
129
+ async _warnDeprecatedModules(selectedModules = []) {
130
+ const externalManager = new ExternalModuleManager();
131
+ let registryModules;
132
+ try {
133
+ registryModules = await externalManager.listAvailable();
134
+ } catch {
135
+ return []; // Registry ilegível — nunca travar uma instalação por causa de um aviso.
136
+ }
137
+
138
+ const deprecatedByCode = new Map();
139
+ for (const mod of registryModules) {
140
+ if (!mod.deprecated) continue;
141
+ deprecatedByCode.set(mod.code, mod);
142
+ for (const alias of mod.aliases) deprecatedByCode.set(alias, mod);
143
+ }
144
+
145
+ const warned = [];
146
+ for (const code of selectedModules) {
147
+ const mod = deprecatedByCode.get(code);
148
+ if (!mod || warned.includes(mod.code)) continue;
149
+ warned.push(mod.code);
150
+ const detail = mod.deprecationMessage || 'It is no longer receiving updates.';
151
+ await prompts.log.warn(`${mod.name} (${mod.code}) is deprecated. ${detail}`);
152
+ }
153
+ return warned;
154
+ }
155
+
117
156
  async _retainUnavailableInstalledModules(selectedModules, installedModuleIds, wizzDir, options = {}) {
118
157
  const { OfficialModules } = require('./modules/official-modules');
119
158
  const officialCodes = new Set(['core']);
@@ -274,6 +313,10 @@ class UI {
274
313
 
275
314
  // Handle quick update separately
276
315
  if (actionType === 'quick-update') {
316
+ // O Quick Update nunca mostra o picker de módulos, então este é o único
317
+ // ponto em que uma instalação existente de módulo depreciado ouve o aviso.
318
+ await this._warnDeprecatedModules(existingInstall.moduleIds || []);
319
+
277
320
  return {
278
321
  actionType: 'quick-update',
279
322
  directory: confirmedDirectory,
@@ -338,6 +381,11 @@ class UI {
338
381
  );
339
382
  }
340
383
 
384
+ // Mostra os avisos de depreciação para o que acabou selecionado. O picker
385
+ // interativo só dá a dica na lista de opções, e os caminhos --modules /
386
+ // --yes nunca veem essa lista.
387
+ await this._warnDeprecatedModules(selectedModules);
388
+
341
389
  // For existing installs, resolve per-module update decisions BEFORE
342
390
  // we clone anything. Reads the existing manifest's recorded channel
343
391
  // per module and prompts the user on available upgrades (patch/minor
@@ -423,6 +471,8 @@ class UI {
423
471
  selectedModules.unshift('core');
424
472
  }
425
473
 
474
+ await this._warnDeprecatedModules(selectedModules);
475
+
426
476
  // Interactive channel gate: "Ready to install (all stable)? [Y/n]"
427
477
  // Only shown for fresh installs with no channel flags and an external module
428
478
  // selected. Skipped for prerelease launches because channelOptions.global
@@ -1236,25 +1286,32 @@ class UI {
1236
1286
  }
1237
1287
  }
1238
1288
 
1239
- // Add external registry modules (skip built-in duplicates)
1240
- const externalRegistryModules = registryModules.filter((mod) => !mod.builtIn && !builtInCodes.has(mod.code));
1289
+ // Add external registry modules (skip built-in duplicates and deprecated
1290
+ // modules that are not already installed — um módulo depreciado continua
1291
+ // visível só para quem já o tem instalado poder seguir gerenciando).
1292
+ const externalRegistryModules = registryModules.filter(
1293
+ (mod) => !mod.builtIn && !builtInCodes.has(mod.code) && (!mod.deprecated || installedModuleIds.has(mod.code)),
1294
+ );
1241
1295
  let externalRegistryEntries = [];
1242
1296
  if (externalRegistryModules.length > 0) {
1243
1297
  const spinner = await prompts.spinner();
1244
1298
  spinner.start('Checking latest module versions...');
1245
1299
 
1246
1300
  externalRegistryEntries = await Promise.all(
1247
- externalRegistryModules.map(async (mod) => ({
1248
- code: mod.code,
1249
- entry: await buildModuleEntry(
1301
+ externalRegistryModules.map(async (mod) => {
1302
+ const entry = await buildModuleEntry(
1250
1303
  mod.code,
1251
1304
  mod.name,
1252
1305
  mod.description,
1253
1306
  mod.defaultSelected,
1254
1307
  mod.url || null,
1255
1308
  mod.defaultChannel || null,
1256
- ),
1257
- })),
1309
+ );
1310
+ if (mod.deprecated && mod.deprecationMessage) {
1311
+ entry.hint = entry.hint ? `${entry.hint} — ${mod.deprecationMessage}` : mod.deprecationMessage;
1312
+ }
1313
+ return { code: mod.code, entry };
1314
+ }),
1258
1315
  );
1259
1316
 
1260
1317
  spinner.stop('Checked latest module versions.');