wizz-method 2.21.0 → 2.23.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 (179) hide show
  1. package/THIRD-PARTY-NOTICES.md +24 -0
  2. package/package.json +4 -2
  3. package/removals.txt +3 -0
  4. package/skills-registry.yaml +133 -4
  5. package/src/bmm-skills/1-analysis/research/wizz-market-research/steps/step-06-research-completion.md +27 -0
  6. package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +1 -1
  7. package/src/bmm-skills/4-implementation/wizz-code-review/SKILL.md +1 -1
  8. package/src/bmm-skills/4-implementation/wizz-create-story/SKILL.md +1 -1
  9. package/src/bmm-skills/4-implementation/wizz-dev-story/SKILL.md +8 -1
  10. package/src/bmm-skills/4-implementation/wizz-quick-dev/SKILL.md +2 -2
  11. package/src/bmm-skills/4-implementation/wizz-quick-dev/step-03-implement.md +2 -0
  12. package/src/bmm-skills/4-implementation/wizz-sprint-status/SKILL.md +1 -1
  13. package/src/core-skills/wizz-party-mode/customize.toml +3 -3
  14. package/src/modules/lowticket/README.md +2 -1
  15. package/src/modules/lowticket/module.yaml +1 -1
  16. package/src/modules/lowticket/skills/wizz-clone-do-expert/SKILL.md +86 -0
  17. package/src/modules/lowticket/skills/wizz-clone-do-expert/references/clone-prompt-template.md +21 -0
  18. package/src/modules/lowticket/skills/wizz-lowticket-trafego/references/reguas.md +2 -0
  19. package/src/modules/wizz/_shared/cerebro.md +1 -1
  20. package/src/modules/wizz/_shared/organizacao.md +2 -2
  21. package/src/modules/wizz/_shared/video-pipeline.md +2 -0
  22. package/src/modules/wizz/agents/wizz-copy/SKILL.md +1 -0
  23. package/src/modules/wizz/agents/wizz-copy/customize.toml +5 -0
  24. package/src/modules/wizz/agents/wizz-copy/knowledge/playbook.md +3 -2
  25. package/src/modules/wizz/agents/wizz-growth/customize.toml +7 -2
  26. package/src/modules/wizz/agents/wizz-growth/knowledge/playbook.md +3 -2
  27. package/src/modules/wizz/agents/wizz-social/knowledge/blueprint-roteiro-viral.md +14 -0
  28. package/src/modules/wizz/agents/wizz-social/knowledge/legenda-instagram.md +12 -0
  29. package/src/skills-lib/wizz-cerebro/SKILL.md +18 -5
  30. package/src/skills-lib/wizz-design-needs/SKILL.md +7 -0
  31. package/src/skills-lib/wizz-design-needs/references/design-state.md +34 -2
  32. package/src/skills-lib/wizz-design-needs/references/gap-analysis.md +1 -1
  33. package/src/skills-lib/wizz-design-needs/scripts/design_state.py +305 -0
  34. package/src/skills-lib/wizz-design-needs/scripts/tests/test_design_state.py +143 -0
  35. package/src/skills-lib/wizz-impeccable/references/design-rules.md +18 -0
  36. package/src/skills-lib/wizz-ios-build/SKILL.md +61 -0
  37. package/src/skills-lib/wizz-motion-3d-director/references/motion-paths.md +2 -0
  38. package/src/skills-lib/wizz-offer-forge/SKILL.md +2 -2
  39. package/src/skills-lib/wizz-paid-ads/SKILL.md +2 -1
  40. package/src/skills-lib/wizz-paywall-upgrade-cro/SKILL.md +1 -0
  41. package/src/skills-lib/{wizz-pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico}/README.md +13 -13
  42. package/src/skills-lib/{wizz-pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico}/SKILL.md +11 -7
  43. package/src/skills-lib/{wizz-pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico}/assets/template.html +8 -21
  44. package/src/skills-lib/wizz-pesquisa-de-publico/references/interview-mode.md +69 -0
  45. package/src/skills-lib/wizz-premium-landing-ui-researcher/SKILL.md +6 -2
  46. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/audit-protocol.md +7 -34
  47. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/component-sources.md +25 -0
  48. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/design-sources.yaml +325 -0
  49. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/mandatory-process.md +1 -1
  50. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/mining-rubric.md +123 -0
  51. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-first-protocol.md +10 -4
  52. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/web-access-and-evidence.md +1 -1
  53. package/src/skills-lib/wizz-premium-landing-ui-researcher/scripts/design_sources.py +221 -0
  54. package/src/skills-lib/wizz-premium-landing-ui-researcher/scripts/tests/test_design_sources.py +130 -0
  55. package/src/skills-lib/wizz-router/references/routing-table-flat.md +7 -1
  56. package/src/skills-lib/wizz-seo-audit/SKILL.md +4 -0
  57. package/src/skills-lib/wizz-seo-audit/references/gsc-regex-perguntas.md +64 -0
  58. package/src/skills-lib/wizz-signup-flow-cro/SKILL.md +1 -0
  59. package/src/skills-lib/wizz-signup-flow-cro/references/trial-abuse.md +40 -0
  60. package/src/skills-lib/wizz-taste-skill/references/anti-slop-tells.md +8 -0
  61. package/src/skills-lib/wizz-ui-component-curator/SKILL.md +8 -7
  62. package/src/skills-lib/wizz-ui-ux-pro-max/SKILL.md +1 -1
  63. package/src/skills-lib/wizz-ui-ux-pro-max/references/common-rules-checklist.md +11 -0
  64. package/src/skills-lib/wizz-video-effects/LICENSE-motionmaxxing.txt +202 -0
  65. package/src/skills-lib/wizz-video-effects/SKILL.md +7 -2
  66. package/src/skills-lib/wizz-video-effects/assets/chrome/collect_layout.js +113 -0
  67. package/src/skills-lib/wizz-video-effects/assets/effects.config.template.json +45 -0
  68. package/src/skills-lib/wizz-video-effects/references/data-contract.md +32 -1
  69. package/src/skills-lib/wizz-video-effects/references/gates.md +4 -1
  70. package/src/skills-lib/wizz-video-effects/references/one-take.md +41 -0
  71. package/src/skills-lib/wizz-video-effects/scripts/check_chrome.py +376 -0
  72. package/src/skills-lib/wizz-video-effects/scripts/check_dead_frames.py +193 -0
  73. package/src/skills-lib/wizz-video-effects/scripts/check_one_take.py +159 -0
  74. package/src/skills-lib/wizz-video-effects/scripts/config.py +13 -0
  75. package/src/skills-lib/wizz-video-effects/scripts/test_chrome.py +181 -0
  76. package/src/skills-lib/wizz-video-effects/scripts/test_dead_frames.py +171 -0
  77. package/src/skills-lib/wizz-video-effects/scripts/test_one_take.py +126 -0
  78. package/src/skills-lib/wizz-video-styles/LICENSE-lemo-opuscar.txt +21 -0
  79. package/src/skills-lib/wizz-video-styles/SKILL.md +16 -7
  80. package/src/skills-lib/wizz-video-styles/assets/film-styles/dark-keynote.html +78 -0
  81. package/src/skills-lib/wizz-video-styles/assets/film-styles/remotion/Riso.tsx +63 -0
  82. package/src/skills-lib/wizz-video-styles/assets/film-styles/risograph.html +69 -0
  83. package/src/skills-lib/wizz-video-styles/assets/film-styles/swiss-motion.html +84 -0
  84. package/src/skills-lib/wizz-video-styles/references/check-ins.md +25 -0
  85. package/src/skills-lib/wizz-video-styles/references/film-styles/DIRECTOR.md +139 -0
  86. package/src/skills-lib/wizz-video-styles/references/film-styles/archive-lighttable.md +109 -0
  87. package/src/skills-lib/wizz-video-styles/references/film-styles/art-deco.md +112 -0
  88. package/src/skills-lib/wizz-video-styles/references/film-styles/ascii-crt.md +115 -0
  89. package/src/skills-lib/wizz-video-styles/references/film-styles/backrooms.md +125 -0
  90. package/src/skills-lib/wizz-video-styles/references/film-styles/blueprint.md +120 -0
  91. package/src/skills-lib/wizz-video-styles/references/film-styles/brick-toy.md +114 -0
  92. package/src/skills-lib/wizz-video-styles/references/film-styles/briefing-board.md +122 -0
  93. package/src/skills-lib/wizz-video-styles/references/film-styles/caption-news.md +137 -0
  94. package/src/skills-lib/wizz-video-styles/references/film-styles/cel-anime-80s.md +119 -0
  95. package/src/skills-lib/wizz-video-styles/references/film-styles/city-symphony.md +142 -0
  96. package/src/skills-lib/wizz-video-styles/references/film-styles/countdown.md +132 -0
  97. package/src/skills-lib/wizz-video-styles/references/film-styles/crayon-book.md +109 -0
  98. package/src/skills-lib/wizz-video-styles/references/film-styles/dark-keynote.md +117 -0
  99. package/src/skills-lib/wizz-video-styles/references/film-styles/data-physical.md +119 -0
  100. package/src/skills-lib/wizz-video-styles/references/film-styles/dataviz.md +123 -0
  101. package/src/skills-lib/wizz-video-styles/references/film-styles/engraving.md +123 -0
  102. package/src/skills-lib/wizz-video-styles/references/film-styles/game-show.md +118 -0
  103. package/src/skills-lib/wizz-video-styles/references/film-styles/glass-product.md +110 -0
  104. package/src/skills-lib/wizz-video-styles/references/film-styles/halftone-dossier.md +122 -0
  105. package/src/skills-lib/wizz-video-styles/references/film-styles/hd-2d.md +111 -0
  106. package/src/skills-lib/wizz-video-styles/references/film-styles/hologram-hud.md +129 -0
  107. package/src/skills-lib/wizz-video-styles/references/film-styles/impasto.md +124 -0
  108. package/src/skills-lib/wizz-video-styles/references/film-styles/ink-wash.md +120 -0
  109. package/src/skills-lib/wizz-video-styles/references/film-styles/iso-infographic.md +117 -0
  110. package/src/skills-lib/wizz-video-styles/references/film-styles/kino-montage.md +151 -0
  111. package/src/skills-lib/wizz-video-styles/references/film-styles/lidar-survey.md +118 -0
  112. package/src/skills-lib/wizz-video-styles/references/film-styles/living-screencast.md +124 -0
  113. package/src/skills-lib/wizz-video-styles/references/film-styles/lowpoly-island.md +112 -0
  114. package/src/skills-lib/wizz-video-styles/references/film-styles/microgame.md +122 -0
  115. package/src/skills-lib/wizz-video-styles/references/film-styles/microscope.md +108 -0
  116. package/src/skills-lib/wizz-video-styles/references/film-styles/midcentury-toon.md +124 -0
  117. package/src/skills-lib/wizz-video-styles/references/film-styles/news-package.md +158 -0
  118. package/src/skills-lib/wizz-video-styles/references/film-styles/newsreel.md +136 -0
  119. package/src/skills-lib/wizz-video-styles/references/film-styles/one-line.md +114 -0
  120. package/src/skills-lib/wizz-video-styles/references/film-styles/paper-lantern.md +116 -0
  121. package/src/skills-lib/wizz-video-styles/references/film-styles/paper-popup.md +119 -0
  122. package/src/skills-lib/wizz-video-styles/references/film-styles/papercut-red.md +110 -0
  123. package/src/skills-lib/wizz-video-styles/references/film-styles/pictogram-motion.md +119 -0
  124. package/src/skills-lib/wizz-video-styles/references/film-styles/pixel-rpg.md +117 -0
  125. package/src/skills-lib/wizz-video-styles/references/film-styles/risograph.md +125 -0
  126. package/src/skills-lib/wizz-video-styles/references/film-styles/rubber-hose.md +112 -0
  127. package/src/skills-lib/wizz-video-styles/references/film-styles/satellite-briefing.md +116 -0
  128. package/src/skills-lib/wizz-video-styles/references/film-styles/scifi-toon.md +121 -0
  129. package/src/skills-lib/wizz-video-styles/references/film-styles/shadow-puppet.md +124 -0
  130. package/src/skills-lib/wizz-video-styles/references/film-styles/silent-film.md +109 -0
  131. package/src/skills-lib/wizz-video-styles/references/film-styles/silkscreen-poster.md +140 -0
  132. package/src/skills-lib/wizz-video-styles/references/film-styles/spy-titles.md +116 -0
  133. package/src/skills-lib/wizz-video-styles/references/film-styles/stained-glass.md +123 -0
  134. package/src/skills-lib/wizz-video-styles/references/film-styles/swiss-motion.md +115 -0
  135. package/src/skills-lib/wizz-video-styles/references/film-styles/teardown.md +127 -0
  136. package/src/skills-lib/wizz-video-styles/references/film-styles/telestrator.md +137 -0
  137. package/src/skills-lib/wizz-video-styles/references/film-styles/tilt-shift.md +107 -0
  138. package/src/skills-lib/wizz-video-styles/references/film-styles/ukiyoe.md +112 -0
  139. package/src/skills-lib/wizz-video-styles/references/film-styles/urban-sketch.md +126 -0
  140. package/src/skills-lib/wizz-video-styles/references/film-styles/visual-investigation.md +140 -0
  141. package/src/skills-lib/wizz-video-styles/references/film-styles/watercolor.md +113 -0
  142. package/src/skills-lib/wizz-video-styles/references/film-styles/white-model.md +115 -0
  143. package/src/skills-lib/wizz-video-styles/references/film-styles/whiteboard.md +118 -0
  144. package/src/skills-lib/wizz-video-styles/references/film-styles/woodcut.md +122 -0
  145. package/src/skills-lib/wizz-video-styles/references/film-styles.md +140 -0
  146. package/src/skills-lib/wizz-video-styles/references/insert-recipes.md +33 -0
  147. package/src/skills-lib/wizz-video-styles/references/motion-laws.md +62 -0
  148. package/src/skills-lib/wizz-video-styles/references/styles/desafio-numero.md +1 -0
  149. package/src/skills-lib/wizz-video-styles/scripts/beat_grid.py +38 -6
  150. package/src/skills-lib/wizz-video-styles/scripts/blind_review.py +159 -0
  151. package/src/skills-lib/wizz-video-styles/scripts/taste.mjs +110 -0
  152. package/src/skills-lib/wizz-video-styles/scripts/test_blind_review.py +82 -0
  153. package/src/skills-lib/wizz-vsl/LICENSE +49 -0
  154. package/src/skills-lib/wizz-vsl/SKILL.md +122 -0
  155. package/src/skills-lib/wizz-vsl/references/assembly-qa.md +123 -0
  156. package/src/skills-lib/wizz-vsl/references/blocks-and-leads.md +149 -0
  157. package/src/skills-lib/wizz-vsl/references/diagnosis.md +95 -0
  158. package/src/skills-lib/wizz-vsl/references/intake.md +200 -0
  159. package/src/skills-lib/wizz-vsl/references/mechanisms.md +138 -0
  160. package/src/skills-lib/wizz-vsl/references/niches.md +40 -0
  161. package/src/skills-lib/wizz-vsl/references/offer.md +91 -0
  162. package/src/skills-lib/wizz-vsl/references/production.md +75 -0
  163. package/src/skills-lib/wizz-web-security/SKILL.md +2 -0
  164. package/tools/hooks/README.md +10 -9
  165. package/tools/hooks/wizz-arrumar.js +1 -0
  166. package/tools/hooks/wizz-artifact-guard.js +31 -5
  167. package/tools/hooks/wizz-cerebro-lib.js +36 -13
  168. package/tools/hooks/wizz-feature-lib.js +4 -1
  169. package/tools/hooks/wizz-feature.js +66 -76
  170. package/tools/hooks/wizz-sprint-archive-lib.js +311 -0
  171. package/tools/installer/core/hooks-settings.js +1 -0
  172. package/tools/installer/core/installer.js +6 -0
  173. package/tools/installer/guided/labels.js +16 -0
  174. package/tools/installer/install-text.js +2 -0
  175. package/tools/installer/modules/cli-config.js +3 -0
  176. package/tools/installer/modules/core-seed.js +72 -0
  177. package/tools/installer/modules/mcp-config.js +3 -0
  178. package/tools/installer/ui.js +22 -26
  179. /package/src/skills-lib/{wizz-pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico}/prompts.md +0 -0
@@ -0,0 +1,311 @@
1
+ // wizz-sprint-archive-lib.js — arquivamento do sprint que `encerrar` fecha.
2
+ //
3
+ // Fonte de verdade: wizz-method/tools/hooks/. Copiado para `_wizz/hooks/`
4
+ // junto com o wizz-feature.js (SUPPORT_FILES do installer). Não é hook.
5
+ //
6
+ // Planeja tudo antes de tocar no disco e aplica com desfazer: o encerrar do
7
+ // Lucrare (2026-10-19) quebrou com ENOTDIR no meio, num sprint reaberto cujo
8
+ // nome já existia em `_arquivo/<slug>/`, e deixou metade movida.
9
+ //
10
+ // 1. Planejar: os moves (mesclando com o arquivo morto; arquivo de mesmo
11
+ // nome e conteúdo diferente vai para o lado como `<nome>.anterior-<data>`,
12
+ // idêntico só some da origem), o dono do `sprint-status.yaml` da raiz, os
13
+ // links dos `.md` do sprint e as referências de fora (CLAUDE.md, roteiros,
14
+ // html de design). Código e config de fora só são listados, nunca editados.
15
+ // 2. Aplicar: edições, depois moves. Qualquer erro desfaz o que já foi feito,
16
+ // na ordem inversa, e relança.
17
+
18
+ const fs = require('node:fs');
19
+ const path = require('node:path');
20
+ const lib = require('./wizz-feature-lib.js');
21
+ const { walkText } = require('./wizz-arrumar.js');
22
+
23
+ const STATUS_FILE = 'sprint-status.yaml';
24
+ // Texto de gente: a referência ao sprint é reescrita. Código, json e yaml de
25
+ // fora são só listados (reescrever import ou config às cegas quebra build).
26
+ const MARKDOWN_EXT = new Set(['.md', '.mdx', '.markdown']);
27
+ const REWRITE_EXT = new Set([...MARKDOWN_EXT, '.txt', '.html', '.htm']);
28
+
29
+ function escapeRe(s) {
30
+ return s.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
31
+ }
32
+
33
+ const toPosix = (p) => p.split(path.sep).join('/');
34
+
35
+ function sameContent(a, b) {
36
+ try {
37
+ return fs.readFileSync(a).equals(fs.readFileSync(b));
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ /** `spec-x.md` → `spec-x.anterior-<data>.md`, com sufixo -2, -3... se já existir. */
44
+ function asideName(dest, taken) {
45
+ const dir = path.dirname(dest);
46
+ const base = path.basename(dest);
47
+ const dot = base.lastIndexOf('.');
48
+ const [stem, ext] = dot > 0 ? [base.slice(0, dot), base.slice(dot)] : [base, ''];
49
+ for (let n = 1; ; n++) {
50
+ const candidate = path.join(dir, `${stem}.anterior-${lib.today()}${n > 1 ? `-${n}` : ''}${ext}`);
51
+ if (!fs.existsSync(candidate) && !taken.has(candidate)) {
52
+ taken.add(candidate);
53
+ return candidate;
54
+ }
55
+ }
56
+ }
57
+
58
+ /** Planeja levar `src` para `dest`, mesclando com o que o arquivo morto já tem. */
59
+ function planTree(src, dest, plan) {
60
+ if (!fs.existsSync(dest)) {
61
+ plan.renames.push({ from: src, to: dest });
62
+ return;
63
+ }
64
+ const srcIsDir = fs.lstatSync(src).isDirectory();
65
+ const destIsDir = fs.lstatSync(dest).isDirectory();
66
+ if (srcIsDir && destIsDir) {
67
+ for (const name of fs.readdirSync(src)) planTree(path.join(src, name), path.join(dest, name), plan);
68
+ plan.emptied.push(src);
69
+ return;
70
+ }
71
+ if (!srcIsDir && !destIsDir && sameContent(src, dest)) {
72
+ plan.duplicates.push(src);
73
+ return;
74
+ }
75
+ plan.asides.push({ from: dest, to: asideName(dest, plan.taken) });
76
+ plan.renames.push({ from: src, to: dest });
77
+ }
78
+
79
+ function markdownFiles(dir) {
80
+ let entries = [];
81
+ try {
82
+ entries = fs.readdirSync(dir, { withFileTypes: true });
83
+ } catch {
84
+ return [];
85
+ }
86
+ return entries.flatMap((e) => {
87
+ const abs = path.join(dir, e.name);
88
+ if (e.isDirectory()) return markdownFiles(abs);
89
+ return e.isFile() && e.name.endsWith('.md') ? [abs] : [];
90
+ });
91
+ }
92
+
93
+ /** Valor cru de `chave:` no topo de um yaml simples (sem parser: o hook não tem dependência). */
94
+ function yamlTopValue(text, key) {
95
+ const m = new RegExp(String.raw`^${key}:\s*(.*)$`, 'm').exec(text);
96
+ return m
97
+ ? m[1]
98
+ .replace(/\s+#.*$/, '')
99
+ .replaceAll(/^["']|["']$/g, '')
100
+ .trim()
101
+ : '';
102
+ }
103
+
104
+ /** Chaves de `development_status:` (stories, épicos, retros). */
105
+ function statusKeys(text) {
106
+ const lines = text.split('\n');
107
+ const head = lines.findIndex((l) => /^development_status:\s*$/.test(l));
108
+ if (head === -1) return [];
109
+ const keys = [];
110
+ for (let i = head + 1; i < lines.length && (/^\s/.test(lines[i]) || !lines[i].trim()); i++) {
111
+ const m = /^\s+([\w.-]+):/.exec(lines[i]);
112
+ if (m) keys.push(m[1]);
113
+ }
114
+ return keys;
115
+ }
116
+
117
+ function openSprints(dirs) {
118
+ const names = new Set();
119
+ for (const base of [dirs.planning, dirs.implementation]) {
120
+ try {
121
+ for (const e of fs.readdirSync(base, { withFileTypes: true })) if (e.isDirectory() && e.name !== lib.ARCHIVE_DIR) names.add(e.name);
122
+ } catch {
123
+ // pasta de artefato ainda não existe
124
+ }
125
+ }
126
+ return [...names];
127
+ }
128
+
129
+ /**
130
+ * De quem é o `sprint-status.yaml` da raiz da implementação. Só vai junto
131
+ * quando é do sprint que fecha; na dúvida fica (e a linha avisa).
132
+ * @returns {{owned: boolean, motivo: string}}
133
+ */
134
+ function statusOwnership(root, slug, dirs, file) {
135
+ if (fs.existsSync(path.join(dirs.implementation, slug, STATUS_FILE)))
136
+ return { owned: false, motivo: `o sprint já tem o dele em ${slug}/${STATUS_FILE}` };
137
+ const text = fs.readFileSync(file, 'utf8');
138
+ const declared = lib.toSlug(yamlTopValue(text, 'feature') || yamlTopValue(text, 'sprint'));
139
+ if (declared) return declared === slug ? { owned: true, motivo: '' } : { owned: false, motivo: `declara o sprint ${declared}` };
140
+ const location = yamlTopValue(text, 'story_location');
141
+ if (location) {
142
+ const abs = lib.resolveConfigPath(location, root, location);
143
+ const sprint = path.relative(dirs.implementation, abs).split(path.sep)[0];
144
+ if (sprint && sprint !== '..' && sprint !== '' && !path.isAbsolute(sprint) && path.relative(dirs.implementation, abs) !== '')
145
+ return sprint === slug ? { owned: true, motivo: '' } : { owned: false, motivo: `story_location aponta ${sprint}` };
146
+ }
147
+ const sprints = openSprints(dirs);
148
+ const hasStory = (sprint, key) => fs.existsSync(path.join(dirs.implementation, sprint, `${key}.md`));
149
+ const keys = statusKeys(text);
150
+ const owners = new Set(sprints.filter((s) => keys.some((k) => hasStory(s, k))));
151
+ if (owners.size === 1)
152
+ return owners.has(slug) ? { owned: true, motivo: '' } : { owned: false, motivo: `as stories são de ${[...owners][0]}` };
153
+ if (owners.size > 1) return { owned: false, motivo: `as stories são de ${[...owners].join(', ')}` };
154
+ const others = sprints.filter((s) => s !== slug);
155
+ return others.length === 0
156
+ ? { owned: true, motivo: '' }
157
+ : { owned: false, motivo: `dono incerto (sprints abertos: ${others.join(', ')})` };
158
+ }
159
+
160
+ /**
161
+ * Referências de fora do sprint. Texto de gente é reescrito (link relativo
162
+ * de markdown + caminho a partir da raiz); o resto que ainda cita a pasta
163
+ * velha volta como lista.
164
+ */
165
+ function planExternalRefs(root, moves, mapAbs, pending) {
166
+ const skip = moves.map((m) => m.src);
167
+ try {
168
+ for (const e of fs.readdirSync(root, { withFileTypes: true }))
169
+ if (e.isDirectory() && e.name.startsWith('.')) skip.push(path.join(root, e.name));
170
+ } catch {
171
+ // raiz ilegível: sem referências externas
172
+ }
173
+ const prefixes = moves.map((m) => ({
174
+ re: new RegExp(String.raw`(?<![\w-])${escapeRe(toPosix(path.relative(root, m.src)))}(?![\w.-])`, 'g'),
175
+ to: toPosix(path.relative(root, m.dest)),
176
+ }));
177
+ const segments = moves.map(
178
+ (m) => new RegExp(String.raw`(?<![\w-])${escapeRe(`${path.basename(path.dirname(m.src))}/${path.basename(m.src)}`)}(?![\w.-])`),
179
+ );
180
+ const listed = [];
181
+ for (const file of walkText(root, skip)) {
182
+ const text = pending.get(file) ?? fs.readFileSync(file, 'utf8');
183
+ const ext = path.extname(file).toLowerCase();
184
+ const isMarkdown = MARKDOWN_EXT.has(ext);
185
+ if (!isMarkdown && !segments.some((re) => re.test(text))) continue;
186
+ let next = text;
187
+ if (isMarkdown) next = lib.rebaseMarkdownLinks(next, path.dirname(file), path.dirname(file), mapAbs, { onlyMoved: true });
188
+ if (REWRITE_EXT.has(ext)) for (const { re, to } of prefixes) next = next.replace(re, to);
189
+ if (next !== text) pending.set(file, next);
190
+ if (segments.some((re) => re.test(next))) listed.push(path.relative(root, file));
191
+ }
192
+ return listed;
193
+ }
194
+
195
+ /**
196
+ * Planeja o arquivamento sem tocar no disco.
197
+ * @param {string} root
198
+ * @param {string} slug
199
+ * @param {{edits?: Map<string, string>}} [opts] - edições já decididas (a spec com o status final)
200
+ */
201
+ function planArchive(root, slug, { edits = new Map() } = {}) {
202
+ const dirs = lib.artifactDirs(root);
203
+ const pending = new Map(edits);
204
+ const moves = [dirs.planning, dirs.implementation].map((base) => ({
205
+ src: path.join(base, slug),
206
+ dest: path.join(base, lib.ARCHIVE_DIR, slug),
207
+ }));
208
+ const mapAbs = (abs) => {
209
+ const hit = moves.find((m) => abs === m.src || abs.startsWith(m.src + path.sep));
210
+ return hit ? hit.dest + abs.slice(hit.src.length) : abs;
211
+ };
212
+ const plan = { renames: [], asides: [], duplicates: [], emptied: [], taken: new Set(), pending, moves, status: null, externas: [] };
213
+
214
+ // Links relativos (`../`) descem um nível junto com o arquivo.
215
+ for (const { src } of moves) {
216
+ for (const file of markdownFiles(src)) {
217
+ const text = pending.get(file) ?? fs.readFileSync(file, 'utf8');
218
+ const next = lib.rebaseMarkdownLinks(text, path.dirname(file), path.dirname(mapAbs(file)), mapAbs);
219
+ if (next !== text) pending.set(file, next);
220
+ }
221
+ }
222
+ plan.externas = planExternalRefs(root, moves, mapAbs, pending);
223
+ for (const { src, dest } of moves) if (fs.existsSync(src)) planTree(src, dest, plan);
224
+
225
+ const status = path.join(dirs.implementation, STATUS_FILE);
226
+ if (fs.existsSync(status)) {
227
+ const { owned, motivo } = statusOwnership(root, slug, dirs, status);
228
+ const dest = path.join(moves[1].dest, STATUS_FILE);
229
+ plan.status = { file: status, dest, owned, motivo };
230
+ if (owned) {
231
+ plan.externas = plan.externas.filter((f) => path.join(root, f) !== status);
232
+ if (fs.existsSync(dest) || plan.renames.some((r) => r.to === dest)) plan.asides.push({ from: dest, to: asideName(dest, plan.taken) });
233
+ plan.renames.push({ from: status, to: dest });
234
+ }
235
+ }
236
+ return plan;
237
+ }
238
+
239
+ /** mkdir -p que devolve o desfazer (só as pastas que ele criou). */
240
+ function mkdirTracked(dir, undo) {
241
+ const first = fs.mkdirSync(dir, { recursive: true });
242
+ if (!first) return;
243
+ const created = [];
244
+ for (let d = dir; ; d = path.dirname(d)) {
245
+ created.push(d);
246
+ if (d === first || path.dirname(d) === d) break;
247
+ }
248
+ undo.push(() => {
249
+ for (const d of created) fs.rmdirSync(d);
250
+ });
251
+ }
252
+
253
+ /**
254
+ * Aplica o plano. Erro no meio desfaz tudo na ordem inversa e relança.
255
+ * @returns {{asides: string[]}}
256
+ */
257
+ function applyArchive(plan) {
258
+ const undo = [];
259
+ const rename = (from, to) => {
260
+ mkdirTracked(path.dirname(to), undo);
261
+ fs.renameSync(from, to);
262
+ undo.push(() => fs.renameSync(to, from));
263
+ };
264
+ try {
265
+ for (const [file, text] of plan.pending) {
266
+ const before = fs.existsSync(file) ? fs.readFileSync(file) : null;
267
+ mkdirTracked(path.dirname(file), undo);
268
+ fs.writeFileSync(file, text);
269
+ undo.push(() => (before === null ? fs.unlinkSync(file) : fs.writeFileSync(file, before)));
270
+ }
271
+ // O que já está no arquivo morto vai para o lado antes de o novo chegar.
272
+ for (const { from, to } of plan.asides) rename(from, to);
273
+ for (const { from, to } of plan.renames) rename(from, to);
274
+ for (const file of plan.duplicates) {
275
+ const content = fs.readFileSync(file);
276
+ fs.unlinkSync(file);
277
+ undo.push(() => fs.writeFileSync(file, content));
278
+ }
279
+ // Mais fundo primeiro: a pasta-mãe só esvazia depois das filhas.
280
+ for (const dir of [...plan.emptied].sort((a, b) => b.length - a.length)) {
281
+ fs.rmdirSync(dir);
282
+ undo.push(() => fs.mkdirSync(dir));
283
+ }
284
+ } catch (error) {
285
+ const failures = [];
286
+ for (const step of undo.toReversed()) {
287
+ try {
288
+ step();
289
+ } catch (undoError) {
290
+ failures.push(undoError.message);
291
+ }
292
+ }
293
+ error.desfeito = failures.length === 0;
294
+ if (failures.length > 0) error.message += ` (desfazer incompleto: ${failures.join('; ')})`;
295
+ throw error;
296
+ }
297
+ return { asides: plan.asides.map((a) => a.to) };
298
+ }
299
+
300
+ /**
301
+ * O sprint já foi fechado: nenhuma pasta aberta e o arquivo morto existe.
302
+ * @param {string} root
303
+ * @param {string} slug
304
+ */
305
+ function isArchived(root, slug) {
306
+ const dirs = lib.artifactDirs(root);
307
+ const bases = [dirs.planning, dirs.implementation];
308
+ return !bases.some((b) => fs.existsSync(path.join(b, slug))) && bases.some((b) => fs.existsSync(path.join(b, lib.ARCHIVE_DIR, slug)));
309
+ }
310
+
311
+ module.exports = { planArchive, applyArchive, isArchived, statusOwnership, asideName, STATUS_FILE };
@@ -66,6 +66,7 @@ const SUPPORT_FILES = [
66
66
  'wizz-layout-lib.js',
67
67
  'wizz-cerebro-lib.js',
68
68
  'wizz-debug-lib.js',
69
+ 'wizz-sprint-archive-lib.js',
69
70
  'wizz-feature.js',
70
71
  'wizz-arrumar.js',
71
72
  ];
@@ -24,6 +24,7 @@ const { warnPreNativeSkillsLegacy } = require('./legacy-warnings');
24
24
  const { applyProjectInstructions, removeProjectInstructions } = require('./project-instructions');
25
25
  const userFilePreservation = require('./user-file-preservation');
26
26
  const moduleConfigWriter = require('./module-config-writer');
27
+ const { absoluteOutputFolder } = require('../modules/core-seed');
27
28
  const helpCatalog = require('./help-catalog');
28
29
  const quickUpdateModule = require('./quick-update');
29
30
 
@@ -295,6 +296,11 @@ class Installer {
295
296
  ) {
296
297
  const isQuickUpdate = config.isQuickUpdate();
297
298
  const moduleConfigs = officialModules.moduleConfigs;
299
+ // Quick update copies the prior answers verbatim: a relative output_folder
300
+ // left by 2.20.1 heals here, before any config file is written.
301
+ if (moduleConfigs.core?.output_folder) {
302
+ moduleConfigs.core = { ...moduleConfigs.core, output_folder: absoluteOutputFolder(moduleConfigs.core.output_folder) };
303
+ }
298
304
 
299
305
  const dirResults = { createdDirs: [], movedDirs: [], createdWdsFolders: [] };
300
306
  // Set true inside configTask the moment the A14 tmp→real swap actually
@@ -79,6 +79,7 @@ const NEED_LABELS = {
79
79
  docker: { en: 'Docker', pt: 'Docker' },
80
80
  python: { en: 'Python', pt: 'Python' },
81
81
  ffmpeg: { en: 'ffmpeg', pt: 'ffmpeg' },
82
+ chrome: { en: 'Chrome', pt: 'Chrome' },
82
83
  heavy: { en: 'heavy download', pt: 'download pesado' },
83
84
  };
84
85
 
@@ -123,6 +124,16 @@ const CLI_LABELS = {
123
124
  en: 'review native iOS/macOS UI against Apple HIG',
124
125
  pt: 'revisar UI nativa iOS/macOS pelo guia da Apple',
125
126
  },
127
+ 'claude-animation-base': {
128
+ needs: ['ffmpeg', 'chrome'],
129
+ en: 'hand-painted cartoon with Clawd, the Claude character',
130
+ pt: 'desenho animado pintado à mão com o Clawd, do Claude',
131
+ },
132
+ onetake: {
133
+ needs: ['ffmpeg', 'python'],
134
+ en: 'one-take product film (personal use free; commercial needs Pro)',
135
+ pt: 'filme de produto em plano-sequência (pessoal grátis; comercial é Pro)',
136
+ },
126
137
  distribb: {
127
138
  needs: ['account', 'paid'],
128
139
  en: 'publish SEO articles and build backlinks',
@@ -143,6 +154,11 @@ const CLI_LABELS = {
143
154
  en: 'draw validated architecture diagrams',
144
155
  pt: 'desenhar diagramas de arquitetura validados',
145
156
  },
157
+ xtool: {
158
+ needs: [],
159
+ en: 'build and install an iOS app on the iPhone without Xcode',
160
+ pt: 'compilar e instalar app iOS no iPhone sem o Xcode',
161
+ },
146
162
  'agent-browser': {
147
163
  needs: [],
148
164
  en: 'drive a real browser for checks and screenshots',
@@ -64,6 +64,7 @@ const MESSAGES = {
64
64
  mcpPluginSkipped: 'MCPs already covered by a Claude Code plugin (user scope), skipped: {list}',
65
65
  mcpQuestion: 'Which MCPs should go into .mcp.json? (unticked ones become a command to run later)',
66
66
  unknownClis: 'Unknown or already installed CLIs ignored: {list}',
67
+ cliNotice: 'Before choosing {id}: {notice}',
67
68
  cliQuestion: 'Which CLIs should be installed now? (unticked ones become a command to run later)',
68
69
  traceQuestion: 'Turn on the routing meter? (local, writes only to ~/.claude/wizz-trace.jsonl on your machine, never leaves it)',
69
70
  quickUpdateNoSkills:
@@ -123,6 +124,7 @@ const MESSAGES = {
123
124
  mcpPluginSkipped: 'MCPs já cobertos por plugin do Claude Code (user scope), pulados: {list}',
124
125
  mcpQuestion: 'Quais MCPs configurar no .mcp.json? (não marcados viram comando pra rodar depois)',
125
126
  unknownClis: 'CLIs desconhecidos/já instalados ignorados: {list}',
127
+ cliNotice: 'Antes de escolher {id}: {notice}',
126
128
  cliQuestion: 'Quais CLIs instalar agora? (não marcados viram comando pra rodar depois)',
127
129
  traceQuestion: 'Quer ligar o medidor de roteamento? (local, grava só em ~/.claude/wizz-trace.jsonl na sua máquina, nunca sai daqui)',
128
130
  quickUpdateNoSkills:
@@ -109,6 +109,9 @@ function resolveClis(registry, selectedAreas, platformTag = currentPlatformTag()
109
109
  // opt-in multiselect (e.g. rtk — the router assumes it active). Coerced
110
110
  // to a real boolean so a missing field is `false`, never undefined.
111
111
  recommended: !!cli.recommended,
112
+ // notice (2026-10-25): license/cost warning the UI shows before the
113
+ // choice. Coerced to '' so callers test truthiness, never undefined.
114
+ notice: cli.notice || '',
112
115
  }),
113
116
  });
114
117
  }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Semente do [core] para os caminhos sem pergunta (`--yes`, flags de linha
3
+ * de comando e o install guiado), fora do ui.js.
4
+ *
5
+ * Duas regras que o sync da 2.20.1 quebrou no Lucrare (2026-10-19):
6
+ * 1. Reinstalar nunca troca uma resposta já dada. O caminho `--yes` lia as
7
+ * respostas antigas de `collectedConfig.core` (vazio àquela altura) em
8
+ * vez do armazém do `loadExistingConfig`, e caía nos defaults fixos:
9
+ * `user_name` virava o login da máquina e o idioma virava English.
10
+ * 2. `output_folder` é sempre `{project-root}/<pasta>`, igual ao `result:`
11
+ * do core/module.yaml e a planning/implementation. Valor cru (default
12
+ * fixo, `--output-folder`, install guiado) pulava o template e gravava
13
+ * relativo; skills que trocam `{output_folder}` literal escreviam
14
+ * relativo ao cwd (worktrees em `.claude/worktrees/`).
15
+ */
16
+
17
+ const os = require('node:os');
18
+ const path = require('node:path');
19
+
20
+ const PROJECT_ROOT = '{project-root}';
21
+
22
+ /**
23
+ * `_wizz-output` → `{project-root}/_wizz-output`. Valor que já começa com
24
+ * `{project-root}` ou é absoluto fica como está.
25
+ * @param {string} value
26
+ * @returns {string}
27
+ */
28
+ function absoluteOutputFolder(value) {
29
+ if (typeof value !== 'string' || !value.trim()) return value;
30
+ const raw = value.trim();
31
+ if (raw.startsWith(PROJECT_ROOT) || path.isAbsolute(raw)) return raw;
32
+ const clean = raw.replace(/^\.\//, '').replaceAll('\\', '/').replace(/\/+$/, '');
33
+ return `${PROJECT_ROOT}/${clean}`;
34
+ }
35
+
36
+ function systemUsername() {
37
+ let name;
38
+ try {
39
+ name = os.userInfo().username;
40
+ } catch {
41
+ name = process.env.USER || process.env.USERNAME || 'User';
42
+ }
43
+ return name.charAt(0).toUpperCase() + name.slice(1);
44
+ }
45
+
46
+ /**
47
+ * Defaults do [core] para um install do zero com `--yes`.
48
+ * {directory_name} default per src/core-skills/module.yaml — matches what the
49
+ * interactive flow resolves via buildQuestion()'s {directory_name} placeholder.
50
+ * @param {string} directory
51
+ */
52
+ function defaultCoreConfig(directory) {
53
+ return {
54
+ user_name: systemUsername(),
55
+ project_name: path.basename(directory),
56
+ communication_language: 'English',
57
+ document_output_language: 'English',
58
+ output_folder: absoluteOutputFolder('_wizz-output'),
59
+ };
60
+ }
61
+
62
+ /**
63
+ * O [core] da instalação anterior, como o `loadExistingConfig` leu
64
+ * (config.toml + config.user.toml). `{}` sem instalação.
65
+ * @param {{_existingConfig?: Record<string, Record<string, unknown>> | null}} collector
66
+ */
67
+ function existingCoreConfig(collector) {
68
+ const core = collector && collector._existingConfig && collector._existingConfig.core;
69
+ return core && typeof core === 'object' && !Array.isArray(core) ? { ...core } : {};
70
+ }
71
+
72
+ module.exports = { absoluteOutputFolder, defaultCoreConfig, existingCoreConfig };
@@ -54,6 +54,9 @@ function resolveMcps(registry, selectedAreas) {
54
54
  // verify it boots. Carried here so prepareMcps() can act on it; dropped
55
55
  // from what toServerConfig() writes (it never belongs in .mcp.json).
56
56
  setup: mcp.setup || null,
57
+ // `optional` (2026-10-24): the UI leaves it UNCHECKED in the multiselect,
58
+ // so it is only recommended unless the user picks it explicitly.
59
+ optional: mcp.optional === true,
57
60
  }),
58
61
  });
59
62
  }
@@ -28,6 +28,7 @@ const { resolveMcps, partitionGloballyConfigured } = require('./modules/mcp-conf
28
28
  const { resolveClis, detectClis } = require('./modules/cli-config');
29
29
  const prompts = require('./prompts');
30
30
  const { parseSetEntries } = require('./set-overrides');
31
+ const { absoluteOutputFolder, defaultCoreConfig, existingCoreConfig } = require('./modules/core-seed');
31
32
  const { isGuidedEligible, runGuidedInstall } = require('./guided/flow');
32
33
  const guidedSteps = require('./guided/steps');
33
34
  const { normalizeLang, t: guidedT } = require('./guided/messages');
@@ -739,11 +740,13 @@ class UI {
739
740
  const selected = await prompts.multiselect({
740
741
  message: options.mcpMessage || it('mcpQuestion'),
741
742
  options: resolved.map((m) => ({
742
- label: m.id,
743
+ label: m.optional ? `${m.id} (opcional)` : m.id,
743
744
  value: m.id,
744
745
  hint: m.when,
745
746
  })),
746
- initialValues: resolved.map((m) => m.id),
747
+ // Entries flagged `optional` in the registry start unchecked: they are
748
+ // recommended, never written to .mcp.json without an explicit pick.
749
+ initialValues: resolved.filter((m) => !m.optional).map((m) => m.id),
747
750
  required: false,
748
751
  });
749
752
 
@@ -780,6 +783,12 @@ class UI {
780
783
  const missing = detected.filter((c) => !c.installed);
781
784
  if (missing.length === 0) return { toInstall: [], toRecommend: [], alreadyInstalled };
782
785
 
786
+ // Registry `notice` (license or cost) is shown before any path decides,
787
+ // so --clis, --yes and the multiselect all see it before the choice.
788
+ for (const c of missing.filter((m) => m.notice)) {
789
+ await prompts.log.warn(it('cliNotice', { id: c.id, notice: c.notice }));
790
+ }
791
+
783
792
  const byId = new Map(missing.map((c) => [c.id, c]));
784
793
  const split = (installIds) => {
785
794
  const installSet = new Set(installIds);
@@ -1239,14 +1248,16 @@ class UI {
1239
1248
  if (!options.quiet) await prompts.log.info(`Using document output language from command-line: ${options.documentOutputLanguage}`);
1240
1249
  }
1241
1250
  if (options.outputFolder) {
1242
- coreConfig.output_folder = options.outputFolder;
1251
+ coreConfig.output_folder = absoluteOutputFolder(options.outputFolder);
1243
1252
  if (!options.quiet) await prompts.log.info(`Using output folder from command-line: ${options.outputFolder}`);
1244
1253
  }
1245
1254
 
1246
- // Load existing config to merge with provided options
1255
+ // Load existing config to merge with provided options: a flag changes
1256
+ // only its own key, the other prior answers stay.
1247
1257
  await configCollector.loadExistingConfig(directory);
1248
- const existingConfig = configCollector.collectedConfig.core || {};
1249
- configCollector.collectedConfig.core = { ...existingConfig, ...coreConfig };
1258
+ const merged = { ...existingCoreConfig(configCollector), ...coreConfig };
1259
+ if (merged.output_folder) merged.output_folder = absoluteOutputFolder(merged.output_folder);
1260
+ configCollector.collectedConfig.core = merged;
1250
1261
 
1251
1262
  // If not all options are provided, collect the missing ones interactively (unless --yes flag)
1252
1263
  if (
@@ -1256,27 +1267,12 @@ class UI {
1256
1267
  await configCollector.collectModuleConfig('core', directory, false, true);
1257
1268
  }
1258
1269
  } else if (options.yes) {
1259
- // Use all defaults when --yes flag is set
1270
+ // Use all defaults when --yes flag is set. With a prior install, core is
1271
+ // NOT seeded here: collectAllConfigurations collects it with the prior
1272
+ // answers as defaults (reinstall never changes config.user.toml).
1260
1273
  await configCollector.loadExistingConfig(directory);
1261
- const existingConfig = configCollector.collectedConfig.core || {};
1262
-
1263
- if (Object.keys(existingConfig).length === 0) {
1264
- let safeUsername;
1265
- try {
1266
- safeUsername = os.userInfo().username;
1267
- } catch {
1268
- safeUsername = process.env.USER || process.env.USERNAME || 'User';
1269
- }
1270
- const defaultUsername = safeUsername.charAt(0).toUpperCase() + safeUsername.slice(1);
1271
- configCollector.collectedConfig.core = {
1272
- user_name: defaultUsername,
1273
- // {directory_name} default per src/core-skills/module.yaml — matches what the
1274
- // interactive flow resolves via buildQuestion()'s {directory_name} placeholder.
1275
- project_name: path.basename(directory),
1276
- communication_language: 'English',
1277
- document_output_language: 'English',
1278
- output_folder: '_wizz-output',
1279
- };
1274
+ if (Object.keys(existingCoreConfig(configCollector)).length === 0) {
1275
+ configCollector.collectedConfig.core = defaultCoreConfig(directory);
1280
1276
  if (!options.quiet) await prompts.log.info('Using default configuration (--yes flag)');
1281
1277
  }
1282
1278
  }