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.
- package/package.json +3 -2
- package/skills-registry.yaml +4 -0
- package/src/bmm-skills/3-solutioning/wizz-architecture/scripts/__pycache__/lint_spine.cpython-313.pyc +0 -0
- package/src/bmm-skills/3-solutioning/wizz-architecture/scripts/tests/__pycache__/test_lint_spine.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/SKILL.md +3 -5
- package/src/bmm-skills/4-implementation/wizz-code-review/customize.toml +71 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/references/claims-check.md +14 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/references/deletion-check.md +14 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/review-prompts/edge-case-hunter.md +110 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/review-prompts/verification-gap.md +113 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-01-gather-context.md +32 -23
- package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-02-review.md +10 -13
- package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-03-triage.md +16 -22
- package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-04-present.md +4 -2
- package/src/bmm-skills/4-implementation/wizz-quick-dev/customize.toml +76 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/references/claims-check.md +14 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/references/deletion-check.md +14 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/review-prompts/edge-case-hunter.md +110 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/review-prompts/verification-gap.md +113 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/step-04-review.md +38 -16
- package/src/bmm-skills/4-implementation/wizz-retrospective/SKILL.md +56 -1488
- package/src/bmm-skills/4-implementation/wizz-retrospective/customize.toml +3 -5
- package/src/bmm-skills/4-implementation/wizz-retrospective/references/acceptance-verdict.md +55 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/references/aggregate-views.md +17 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/references/evidence-gathering.md +30 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/references/retro-document.md +84 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/references/team-discussion.md +22 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/__pycache__/sprint_status.cpython-313.pyc +0 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/git_evidence.py +304 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/sprint_status.py +746 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/__pycache__/test_git_evidence.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/__pycache__/test_sprint_status.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/fixtures/sprint-status-template.yaml +71 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/test_git_evidence.py +750 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/test_sprint_status.py +1579 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/SKILL.md +39 -296
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/customize.toml +1 -3
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/fix-sprint-status.md +30 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/generate-tracking.md +25 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/readiness-gate.md +20 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/status-view.md +14 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/validate.md +10 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/__pycache__/sprint_plan.cpython-313.pyc +0 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/sprint_plan.py +697 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/tests/__pycache__/test_sprint_plan.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/tests/test_sprint_plan.py +524 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/sprint-status-template.yaml +9 -7
- package/src/bmm-skills/module-help.csv +3 -3
- package/src/core-skills/_shared/handoff-protocol.md +7 -8
- package/src/core-skills/module-help.csv +1 -0
- package/src/core-skills/wizz-advanced-elicitation/SKILL.md +36 -114
- package/src/core-skills/wizz-advanced-elicitation/{methods.csv → assets/methods.csv} +29 -27
- package/src/core-skills/wizz-advanced-elicitation/customize.toml +54 -0
- package/src/core-skills/wizz-advanced-elicitation/scripts/__pycache__/pick_methods.cpython-313.pyc +0 -0
- package/src/core-skills/wizz-advanced-elicitation/scripts/pick_methods.py +233 -0
- package/src/core-skills/wizz-advanced-elicitation/scripts/tests/__pycache__/test_pick_methods.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/core-skills/wizz-advanced-elicitation/scripts/tests/test_pick_methods.py +228 -0
- package/src/core-skills/wizz-brainstorming/SKILL.md +2 -2
- package/src/core-skills/wizz-brainstorming/assets/brain-selector.html +2 -0
- package/src/core-skills/wizz-brainstorming/references/converge.md +1 -1
- package/src/core-skills/wizz-brainstorming/references/finalize.md +1 -1
- package/src/core-skills/wizz-brainstorming/references/headless.md +1 -1
- package/src/core-skills/wizz-brainstorming/references/mode-autonomous.md +1 -1
- package/src/core-skills/wizz-brainstorming/scripts/__pycache__/brain.cpython-313.pyc +0 -0
- package/src/core-skills/wizz-brainstorming/scripts/brain.py +36 -6
- package/src/core-skills/wizz-brainstorming/scripts/tests/__pycache__/test_brain.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/core-skills/wizz-brainstorming/scripts/tests/__pycache__/test_brain.cpython-314.pyc +0 -0
- package/src/core-skills/wizz-brainstorming/scripts/tests/test_brain.py +24 -2
- package/src/core-skills/wizz-customize/scripts/__pycache__/list_customizable_skills.cpython-313.pyc +0 -0
- package/src/core-skills/wizz-customize/scripts/tests/__pycache__/test_list_customizable_skills.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/core-skills/wizz-forge-idea/SKILL.md +107 -0
- package/src/core-skills/wizz-forge-idea/customize.toml +41 -0
- package/src/core-skills/wizz-forge-idea/scripts/__pycache__/resolve_personas.cpython-313.pyc +0 -0
- package/src/core-skills/wizz-forge-idea/scripts/__pycache__/resolve_personas.cpython-314.pyc +0 -0
- package/src/core-skills/wizz-forge-idea/scripts/resolve_personas.py +275 -0
- package/src/core-skills/wizz-forge-idea/scripts/tests/__pycache__/test_resolve_personas.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/core-skills/wizz-forge-idea/scripts/tests/__pycache__/test_resolve_personas.cpython-314.pyc +0 -0
- package/src/core-skills/wizz-forge-idea/scripts/tests/test_resolve_personas.py +138 -0
- package/src/core-skills/wizz-party-mode/SKILL.md +37 -54
- package/src/core-skills/wizz-party-mode/customize.toml +61 -2
- package/src/core-skills/wizz-party-mode/references/create-party.md +8 -3
- package/src/core-skills/wizz-party-mode/references/mode-agent-team.md +3 -1
- package/src/core-skills/wizz-party-mode/references/mode-subagent.md +16 -4
- package/src/core-skills/wizz-party-mode/references/party-memory.md +51 -0
- package/src/core-skills/wizz-party-mode/scripts/__pycache__/resolve_party.cpython-313.pyc +0 -0
- package/src/core-skills/wizz-party-mode/scripts/__pycache__/resolve_party.cpython-314.pyc +0 -0
- package/src/core-skills/wizz-party-mode/scripts/resolve_party.py +22 -7
- package/src/core-skills/wizz-party-mode/scripts/tests/__pycache__/test_resolve_party.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/core-skills/wizz-party-mode/scripts/tests/__pycache__/test_resolve_party.cpython-314.pyc +0 -0
- package/src/core-skills/wizz-party-mode/scripts/tests/{test-resolve_party.py → test_resolve_party.py} +8 -0
- package/src/core-skills/wizz-review-edge-case-hunter/SKILL.md +17 -3
- package/src/modules/wizz/README.md +1 -1
- package/src/modules/wizz/_shared/model-ladder.md +22 -0
- package/src/modules/wizz/_shared/token-economy.md +6 -2
- package/src/modules/wizz/agents/wizz-ads/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-copy/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-designer/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-growth/customize.toml +11 -1
- package/src/modules/wizz/agents/wizz-maestro/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-memoria/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-qa/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-seo/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-social/customize.toml +1 -1
- package/src/modules/wizz/overrides/wizz-agent-analyst.toml +1 -1
- package/src/modules/wizz/overrides/wizz-agent-architect.toml +1 -1
- package/src/modules/wizz/overrides/wizz-agent-dev.toml +1 -1
- package/src/modules/wizz/overrides/wizz-agent-pm.toml +1 -1
- package/src/modules/wizz/overrides/wizz-agent-tech-writer.toml +1 -1
- package/src/modules/wizz/overrides/wizz-agent-ux-designer.toml +1 -1
- package/src/modules/wizz/subagents/codex/wizz-exec-haiku.toml +16 -0
- package/src/modules/wizz/subagents/codex/wizz-exec-opus.toml +18 -0
- package/src/modules/wizz/subagents/codex/wizz-exec-review.toml +17 -0
- package/src/modules/wizz/subagents/codex/wizz-exec-sonnet.toml +17 -0
- package/src/modules/wizz/subagents/gemini/wizz-exec-haiku.md +15 -0
- package/src/modules/wizz/subagents/gemini/wizz-exec-opus.md +17 -0
- package/src/modules/wizz/subagents/gemini/wizz-exec-review.md +15 -0
- package/src/modules/wizz/subagents/gemini/wizz-exec-sonnet.md +16 -0
- package/src/modules/wizz/subagents/opencode/wizz-exec-haiku.md +14 -0
- package/src/modules/wizz/subagents/opencode/wizz-exec-opus.md +16 -0
- package/src/modules/wizz/subagents/opencode/wizz-exec-review.md +16 -0
- package/src/modules/wizz/subagents/opencode/wizz-exec-sonnet.md +15 -0
- package/src/modules/wizz/subagents/wizz-exec-opus.md +16 -0
- package/src/modules/wizz/subagents/wizz-exec-review.md +15 -0
- package/src/scripts/__pycache__/memlog.cpython-313.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_memlog.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_resolve_customization.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/skills-lib/pesquisa-de-publico-do-piva/README.md +83 -0
- package/src/skills-lib/pesquisa-de-publico-do-piva/SKILL.md +65 -0
- package/src/skills-lib/pesquisa-de-publico-do-piva/assets/template.html +414 -0
- package/src/skills-lib/pesquisa-de-publico-do-piva/prompts.md +131 -0
- package/src/skills-lib/wizz-offer-forge/README.md +13 -0
- package/src/skills-lib/wizz-offer-forge/SKILL.md +74 -0
- package/src/skills-lib/wizz-offer-forge/references/empilhamento-valor.md +25 -0
- package/src/skills-lib/wizz-offer-forge/references/garantia-reversa.md +22 -0
- package/src/skills-lib/wizz-offer-forge/references/mecanismo-unico.md +28 -0
- package/src/skills-lib/wizz-offer-forge/references/ponte-trafego.md +21 -0
- package/src/skills-lib/wizz-router/references/routing-table-flat.md +2 -0
- package/tools/installer/core/installer.js +57 -0
- package/tools/installer/ide/_config-driven.js +44 -30
- package/tools/installer/ide/platform-codes.yaml +42 -3
- package/tools/installer/modules/external-manager.js +140 -2
- package/tools/installer/modules/official-modules.js +68 -21
- package/tools/installer/prompts.js +45 -103
- package/tools/installer/ui.js +64 -7
- package/wizz-modules.yaml +65 -11
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/checklist.md +0 -34
- package/src/core-skills/wizz-brainstorming/scripts/memlog.py +0 -202
- 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
|
-
|
|
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
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* @param {Object}
|
|
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
|
|
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
|
-
//
|
|
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,
|
|
396
|
+
await this.copyModuleWithFiltering(skillPath, skillTarget, fileTrackingCallback, moduleConfig);
|
|
367
397
|
}
|
|
368
398
|
|
|
369
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
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
|
|
646
|
-
*
|
|
647
|
-
*
|
|
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
|
|
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
|
-
* @
|
|
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
|
-
|
|
666
|
-
|
|
634
|
+
const prompt = new core.TextPrompt({
|
|
635
|
+
...(options.input ? { input: options.input } : {}),
|
|
636
|
+
...(options.output ? { output: options.output } : {}),
|
|
667
637
|
initialValue: options.default,
|
|
668
|
-
|
|
669
|
-
|
|
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
|
-
|
|
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
|
/**
|
package/tools/installer/ui.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.');
|