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.
- package/THIRD-PARTY-NOTICES.md +24 -0
- package/package.json +4 -2
- package/removals.txt +3 -0
- package/skills-registry.yaml +133 -4
- package/src/bmm-skills/1-analysis/research/wizz-market-research/steps/step-06-research-completion.md +27 -0
- package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-code-review/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-create-story/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-dev-story/SKILL.md +8 -1
- package/src/bmm-skills/4-implementation/wizz-quick-dev/SKILL.md +2 -2
- package/src/bmm-skills/4-implementation/wizz-quick-dev/step-03-implement.md +2 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-status/SKILL.md +1 -1
- package/src/core-skills/wizz-party-mode/customize.toml +3 -3
- package/src/modules/lowticket/README.md +2 -1
- package/src/modules/lowticket/module.yaml +1 -1
- package/src/modules/lowticket/skills/wizz-clone-do-expert/SKILL.md +86 -0
- package/src/modules/lowticket/skills/wizz-clone-do-expert/references/clone-prompt-template.md +21 -0
- package/src/modules/lowticket/skills/wizz-lowticket-trafego/references/reguas.md +2 -0
- package/src/modules/wizz/_shared/cerebro.md +1 -1
- package/src/modules/wizz/_shared/organizacao.md +2 -2
- package/src/modules/wizz/_shared/video-pipeline.md +2 -0
- package/src/modules/wizz/agents/wizz-copy/SKILL.md +1 -0
- package/src/modules/wizz/agents/wizz-copy/customize.toml +5 -0
- package/src/modules/wizz/agents/wizz-copy/knowledge/playbook.md +3 -2
- package/src/modules/wizz/agents/wizz-growth/customize.toml +7 -2
- package/src/modules/wizz/agents/wizz-growth/knowledge/playbook.md +3 -2
- package/src/modules/wizz/agents/wizz-social/knowledge/blueprint-roteiro-viral.md +14 -0
- package/src/modules/wizz/agents/wizz-social/knowledge/legenda-instagram.md +12 -0
- package/src/skills-lib/wizz-cerebro/SKILL.md +18 -5
- package/src/skills-lib/wizz-design-needs/SKILL.md +7 -0
- package/src/skills-lib/wizz-design-needs/references/design-state.md +34 -2
- package/src/skills-lib/wizz-design-needs/references/gap-analysis.md +1 -1
- package/src/skills-lib/wizz-design-needs/scripts/design_state.py +305 -0
- package/src/skills-lib/wizz-design-needs/scripts/tests/test_design_state.py +143 -0
- package/src/skills-lib/wizz-impeccable/references/design-rules.md +18 -0
- package/src/skills-lib/wizz-ios-build/SKILL.md +61 -0
- package/src/skills-lib/wizz-motion-3d-director/references/motion-paths.md +2 -0
- package/src/skills-lib/wizz-offer-forge/SKILL.md +2 -2
- package/src/skills-lib/wizz-paid-ads/SKILL.md +2 -1
- package/src/skills-lib/wizz-paywall-upgrade-cro/SKILL.md +1 -0
- package/src/skills-lib/{wizz-pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico}/README.md +13 -13
- package/src/skills-lib/{wizz-pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico}/SKILL.md +11 -7
- package/src/skills-lib/{wizz-pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico}/assets/template.html +8 -21
- package/src/skills-lib/wizz-pesquisa-de-publico/references/interview-mode.md +69 -0
- package/src/skills-lib/wizz-premium-landing-ui-researcher/SKILL.md +6 -2
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/audit-protocol.md +7 -34
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/component-sources.md +25 -0
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/design-sources.yaml +325 -0
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/mandatory-process.md +1 -1
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/mining-rubric.md +123 -0
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-first-protocol.md +10 -4
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/web-access-and-evidence.md +1 -1
- package/src/skills-lib/wizz-premium-landing-ui-researcher/scripts/design_sources.py +221 -0
- package/src/skills-lib/wizz-premium-landing-ui-researcher/scripts/tests/test_design_sources.py +130 -0
- package/src/skills-lib/wizz-router/references/routing-table-flat.md +7 -1
- package/src/skills-lib/wizz-seo-audit/SKILL.md +4 -0
- package/src/skills-lib/wizz-seo-audit/references/gsc-regex-perguntas.md +64 -0
- package/src/skills-lib/wizz-signup-flow-cro/SKILL.md +1 -0
- package/src/skills-lib/wizz-signup-flow-cro/references/trial-abuse.md +40 -0
- package/src/skills-lib/wizz-taste-skill/references/anti-slop-tells.md +8 -0
- package/src/skills-lib/wizz-ui-component-curator/SKILL.md +8 -7
- package/src/skills-lib/wizz-ui-ux-pro-max/SKILL.md +1 -1
- package/src/skills-lib/wizz-ui-ux-pro-max/references/common-rules-checklist.md +11 -0
- package/src/skills-lib/wizz-video-effects/LICENSE-motionmaxxing.txt +202 -0
- package/src/skills-lib/wizz-video-effects/SKILL.md +7 -2
- package/src/skills-lib/wizz-video-effects/assets/chrome/collect_layout.js +113 -0
- package/src/skills-lib/wizz-video-effects/assets/effects.config.template.json +45 -0
- package/src/skills-lib/wizz-video-effects/references/data-contract.md +32 -1
- package/src/skills-lib/wizz-video-effects/references/gates.md +4 -1
- package/src/skills-lib/wizz-video-effects/references/one-take.md +41 -0
- package/src/skills-lib/wizz-video-effects/scripts/check_chrome.py +376 -0
- package/src/skills-lib/wizz-video-effects/scripts/check_dead_frames.py +193 -0
- package/src/skills-lib/wizz-video-effects/scripts/check_one_take.py +159 -0
- package/src/skills-lib/wizz-video-effects/scripts/config.py +13 -0
- package/src/skills-lib/wizz-video-effects/scripts/test_chrome.py +181 -0
- package/src/skills-lib/wizz-video-effects/scripts/test_dead_frames.py +171 -0
- package/src/skills-lib/wizz-video-effects/scripts/test_one_take.py +126 -0
- package/src/skills-lib/wizz-video-styles/LICENSE-lemo-opuscar.txt +21 -0
- package/src/skills-lib/wizz-video-styles/SKILL.md +16 -7
- package/src/skills-lib/wizz-video-styles/assets/film-styles/dark-keynote.html +78 -0
- package/src/skills-lib/wizz-video-styles/assets/film-styles/remotion/Riso.tsx +63 -0
- package/src/skills-lib/wizz-video-styles/assets/film-styles/risograph.html +69 -0
- package/src/skills-lib/wizz-video-styles/assets/film-styles/swiss-motion.html +84 -0
- package/src/skills-lib/wizz-video-styles/references/check-ins.md +25 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/DIRECTOR.md +139 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/archive-lighttable.md +109 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/art-deco.md +112 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/ascii-crt.md +115 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/backrooms.md +125 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/blueprint.md +120 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/brick-toy.md +114 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/briefing-board.md +122 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/caption-news.md +137 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/cel-anime-80s.md +119 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/city-symphony.md +142 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/countdown.md +132 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/crayon-book.md +109 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/dark-keynote.md +117 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/data-physical.md +119 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/dataviz.md +123 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/engraving.md +123 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/game-show.md +118 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/glass-product.md +110 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/halftone-dossier.md +122 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/hd-2d.md +111 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/hologram-hud.md +129 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/impasto.md +124 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/ink-wash.md +120 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/iso-infographic.md +117 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/kino-montage.md +151 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/lidar-survey.md +118 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/living-screencast.md +124 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/lowpoly-island.md +112 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/microgame.md +122 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/microscope.md +108 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/midcentury-toon.md +124 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/news-package.md +158 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/newsreel.md +136 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/one-line.md +114 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/paper-lantern.md +116 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/paper-popup.md +119 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/papercut-red.md +110 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/pictogram-motion.md +119 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/pixel-rpg.md +117 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/risograph.md +125 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/rubber-hose.md +112 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/satellite-briefing.md +116 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/scifi-toon.md +121 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/shadow-puppet.md +124 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/silent-film.md +109 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/silkscreen-poster.md +140 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/spy-titles.md +116 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/stained-glass.md +123 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/swiss-motion.md +115 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/teardown.md +127 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/telestrator.md +137 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/tilt-shift.md +107 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/ukiyoe.md +112 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/urban-sketch.md +126 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/visual-investigation.md +140 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/watercolor.md +113 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/white-model.md +115 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/whiteboard.md +118 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles/woodcut.md +122 -0
- package/src/skills-lib/wizz-video-styles/references/film-styles.md +140 -0
- package/src/skills-lib/wizz-video-styles/references/insert-recipes.md +33 -0
- package/src/skills-lib/wizz-video-styles/references/motion-laws.md +62 -0
- package/src/skills-lib/wizz-video-styles/references/styles/desafio-numero.md +1 -0
- package/src/skills-lib/wizz-video-styles/scripts/beat_grid.py +38 -6
- package/src/skills-lib/wizz-video-styles/scripts/blind_review.py +159 -0
- package/src/skills-lib/wizz-video-styles/scripts/taste.mjs +110 -0
- package/src/skills-lib/wizz-video-styles/scripts/test_blind_review.py +82 -0
- package/src/skills-lib/wizz-vsl/LICENSE +49 -0
- package/src/skills-lib/wizz-vsl/SKILL.md +122 -0
- package/src/skills-lib/wizz-vsl/references/assembly-qa.md +123 -0
- package/src/skills-lib/wizz-vsl/references/blocks-and-leads.md +149 -0
- package/src/skills-lib/wizz-vsl/references/diagnosis.md +95 -0
- package/src/skills-lib/wizz-vsl/references/intake.md +200 -0
- package/src/skills-lib/wizz-vsl/references/mechanisms.md +138 -0
- package/src/skills-lib/wizz-vsl/references/niches.md +40 -0
- package/src/skills-lib/wizz-vsl/references/offer.md +91 -0
- package/src/skills-lib/wizz-vsl/references/production.md +75 -0
- package/src/skills-lib/wizz-web-security/SKILL.md +2 -0
- package/tools/hooks/README.md +10 -9
- package/tools/hooks/wizz-arrumar.js +1 -0
- package/tools/hooks/wizz-artifact-guard.js +31 -5
- package/tools/hooks/wizz-cerebro-lib.js +36 -13
- package/tools/hooks/wizz-feature-lib.js +4 -1
- package/tools/hooks/wizz-feature.js +66 -76
- package/tools/hooks/wizz-sprint-archive-lib.js +311 -0
- package/tools/installer/core/hooks-settings.js +1 -0
- package/tools/installer/core/installer.js +6 -0
- package/tools/installer/guided/labels.js +16 -0
- package/tools/installer/install-text.js +2 -0
- package/tools/installer/modules/cli-config.js +3 -0
- package/tools/installer/modules/core-seed.js +72 -0
- package/tools/installer/modules/mcp-config.js +3 -0
- package/tools/installer/ui.js +22 -26
- /package/src/skills-lib/{wizz-pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico}/prompts.md +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Phase 4.1 · The OFFER (written first)
|
|
2
|
+
|
|
3
|
+
> Counterintuitive and non-negotiable: **the offer is the first block written**, even though it is the last the viewer sees. Prerequisite: `manifest.gate2_aprovado_em` filled in. Check it before writing.
|
|
4
|
+
>
|
|
5
|
+
> When an approved `wizz-offer-forge` offer exists, it is the source for steps 2 to 13 (name, stack, bonuses, guarantee, anchor): this block turns it into **spoken** script, it does not redesign it. A change of price, bonus or guarantee goes back to `wizz-offer-forge` and reopens Gate 2.
|
|
6
|
+
|
|
7
|
+
Default position: the pitch enters at the 12 to 14 minute turn and runs 4 to 6 more (full VSL 16 to 20). Under the low ticket ruler, the pitch enters at about two thirds of the runtime the ruler sets.
|
|
8
|
+
|
|
9
|
+
## The 15 steps, in order
|
|
10
|
+
|
|
11
|
+
**1. Transition from the mechanism to the offer.**
|
|
12
|
+
> "Agora que você entendeu <mecanismo da solução>, deixa eu te mostrar como aplicar isso na sua vida a partir de hoje."
|
|
13
|
+
|
|
14
|
+
Never cut dry to "so I created a course".
|
|
15
|
+
|
|
16
|
+
**2. Product name.** Strong and specific. Words that convey a system (*protocolo, método, sistema, programa, desafio*). Avoid *curso* and *formação*: they sound like content, not result.
|
|
17
|
+
|
|
18
|
+
**3. Who it is for, and who it is not for.**
|
|
19
|
+
> "Isso é pra você se <condição 1>, <condição 2> e <condição 3>. E não é pra você se <condição>."
|
|
20
|
+
|
|
21
|
+
The negative qualification raises perceived value and buyer quality (Block 1.9).
|
|
22
|
+
|
|
23
|
+
**4. What the person receives.** Module by module, always **benefit, not feature**:
|
|
24
|
+
> ❌ "Módulo 3: Alimentação"
|
|
25
|
+
> ✅ "Módulo 3: o que comer nos dias em que a agenda vira do avesso, pra você não recomeçar do zero toda segunda"
|
|
26
|
+
|
|
27
|
+
**5. The triad.** Strong products communicate three layers:
|
|
28
|
+
|
|
29
|
+
| Layer | Line | Example |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| **Knowledge** | "Eu te ensino a pescar" | the content, straight to the point |
|
|
32
|
+
| **Recommendation** | "Eu te dou o peixe" | what to do in her specific case (diagnosis, plan, AI) |
|
|
33
|
+
| **Follow-up** | "Eu te acompanho na pescaria" | continuous support: group, WhatsApp, AI, team |
|
|
34
|
+
|
|
35
|
+
Name the layers that exist and never invent the missing ones. If two are missing, flag it to the human: it is an offer weakness, not a copy one.
|
|
36
|
+
|
|
37
|
+
**6. Interleaved testimonials.** `[DEPOIMENTO 1]`, `[DEPOIMENTO 2]`… spread **between** modules, never piled in one block. When there is no proof material, the markers hold the place, or the block is omitted; the rest of the pitch goes on.
|
|
38
|
+
|
|
39
|
+
**7. Price anchoring.** Before revealing the price, compare with the real, expensive alternative:
|
|
40
|
+
> "Uma consultoria individual comigo sai R$ X por sessão. Um acompanhamento de 3 meses passa de R$ Y."
|
|
41
|
+
|
|
42
|
+
Anchor only against a real, supported alternative. A charging history or discount that never existed is never stated as fact.
|
|
43
|
+
|
|
44
|
+
**8. Price with contrast** · `[BOTÃO APARECE AQUI]`
|
|
45
|
+
> "Mas hoje não é nada disso. É R$ <preço>."
|
|
46
|
+
|
|
47
|
+
The button appears **here**, when the price is revealed: that is what the player delay waits for.
|
|
48
|
+
|
|
49
|
+
**9. Bonus with a 60-second timer.** A real urgency bonus with a visual countdown, typically a live workshop seat. It is one of the strongest levers of the format. Honesty requirement: the bonus exists and is delivered. A timer that resets and keeps offering destroys trust in the whole funnel.
|
|
50
|
+
|
|
51
|
+
**10. Guarantee.** Period and condition, in one clear sentence that matches the real terms exactly (an encouraged usage challenge is not automatically a refund condition).
|
|
52
|
+
|
|
53
|
+
**11. Extra bonuses, with a story.** Each bonus gets 2 or 3 lines of origin. A bonus listed without story is noise; with story it is value.
|
|
54
|
+
|
|
55
|
+
**12. Express stack.** Quick recap, in a list, with value per item:
|
|
56
|
+
> "Programa completo: R$ X · Bônus 1: R$ Y · Bônus 2: R$ Z · Total: R$ <soma>. Hoje: R$ <preço>."
|
|
57
|
+
|
|
58
|
+
**13. Surprise gift.**
|
|
59
|
+
> "E tem uma coisa que eu nem coloquei na lista: assim que você entrar, recebe um convite pra <aula/encontro>."
|
|
60
|
+
|
|
61
|
+
It raises the open rate of the first post-purchase message, where the whole backend starts.
|
|
62
|
+
|
|
63
|
+
**14. Emotional close.** Go back to the lead's problem and project the two futures.
|
|
64
|
+
|
|
65
|
+
**15. Final CTA.** One instruction, no ambiguity: "Clica no botão aqui embaixo agora." `[CTA → aponta pro botão que já apareceu no passo 8]`
|
|
66
|
+
|
|
67
|
+
## The value equation (apply to every line of the pitch)
|
|
68
|
+
|
|
69
|
+
Every element of the offer pushes at least one of: **↑ dream outcome · ↑ perceived likelihood · ↓ time to result · ↓ effort and sacrifice**. A bonus or module that moves none of the four weakens the offer by dilution.
|
|
70
|
+
|
|
71
|
+
## Page and player rules (they move conversion more than copy)
|
|
72
|
+
|
|
73
|
+
These belong to the page around the player, written by `wizz-copywriting`; list them in the hand-off.
|
|
74
|
+
|
|
75
|
+
| Rule | Why |
|
|
76
|
+
|---|---|
|
|
77
|
+
| **No headline above the video** | Removing the headline improves play rate |
|
|
78
|
+
| **No email or phone form before the VSL** | Capturing first lowers front-end conversion |
|
|
79
|
+
| **Delayed button**, only after the price | An early button anticipates the pitch and lowers retention |
|
|
80
|
+
| **No discount button or coupon on the page** | Discount buyers take the upsell far less |
|
|
81
|
+
| Traffic goes straight to the open page | No intermediate capture step |
|
|
82
|
+
|
|
83
|
+
Under the low ticket ruler, its page and player shards (`06b-player-vsl.md`, `12c-anatomia-low-ticket.md`) win over this table.
|
|
84
|
+
|
|
85
|
+
## Output of this step
|
|
86
|
+
|
|
87
|
+
The offer block in **spoken language** (it is a script, not page text), with `[DEPOIMENTO]`, `[BOTÃO APARECE AQUI]` and `[A DEFINIR]` markers where information is missing.
|
|
88
|
+
|
|
89
|
+
## OTO and downsell videos
|
|
90
|
+
|
|
91
|
+
A short upsell video after purchase follows a different sequence, in [blocks-and-leads.md](blocks-and-leads.md) §OTO. It never implies that the buyer accepted another optional offer.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Phase 6 · Production plan and hand-off
|
|
2
|
+
|
|
3
|
+
> Animation makes a clear script easier to follow; it cannot rescue vague writing. Production starts only after the script-only audit and the claims ledger are closed ([assembly-qa.md](assembly-qa.md)). This skill **plans** the production and hands it off: drawing, voicing and assembling the video belong to the designer area. The copy agent never invokes another area's agent: offer `wizz-maestro` when the user wants the video in the same delivery.
|
|
4
|
+
|
|
5
|
+
## 1. Beat sheet · `05-beat-sheet.md`
|
|
6
|
+
|
|
7
|
+
One row per beat, in presentation order. It is the contract the production reads.
|
|
8
|
+
|
|
9
|
+
| # | Narration (exact) | On-screen line | Visual evidence | Claim supported | Source |
|
|
10
|
+
|---|---|---|---|---|---|
|
|
11
|
+
| 1 | "…" | ≤ 6 words | product crop / real screen / `[B-ROLL: …]` | ledger #3 | `00-briefing.md` 7.4 |
|
|
12
|
+
|
|
13
|
+
- Capture product states only after confirming the script and the UI agree. Label fixture or example data honestly. Never expose customer data, credentials or live keys.
|
|
14
|
+
- A small visual system: two type roles, a restrained palette, consistent safe margins, a few repeatable scene types (intro, problem, reveal, demo, proof, guarantee, offer, CTA). Patterns, not required branding: the project's brand wins.
|
|
15
|
+
- No durations by hand: every beat's length comes from the final audio (section 3).
|
|
16
|
+
|
|
17
|
+
## 2. Narration
|
|
18
|
+
|
|
19
|
+
- Record or generate the narration from the **exact** final script. Paid voice generation only with the user's explicit authorization; keep the complete inputs (text, voice and model settings, request IDs, returned alignment, audio hashes) so an uncertain job is resumed instead of paid twice.
|
|
20
|
+
- When revising, regenerate only the changed sentences if the voice splices cleanly, with adjacent text as prosody context. Listen to every seam; replace the whole paragraph only when the splice stays audible.
|
|
21
|
+
|
|
22
|
+
## 3. Timing from the final audio
|
|
23
|
+
|
|
24
|
+
Invoke the `wizz-ctc-align` skill on the final narration and the exact spoken text: word-by-word timestamps snapped to real silences become the single timing source. Never estimate timing by hand and never use Whisper timestamps for a known script.
|
|
25
|
+
|
|
26
|
+
- Scene durations are built from that alignment.
|
|
27
|
+
- Big on-screen lines change on **spoken beats**, anchored to the word timings, never to arbitrary seconds.
|
|
28
|
+
- If a section is cut after alignment, update audio, word timings, captions, scene hosts, animation anchors, music ducking and the final fade **together**. Old offsets become invalid after an upstream edit.
|
|
29
|
+
|
|
30
|
+
## 4. Drawing and assembly (designer area)
|
|
31
|
+
|
|
32
|
+
The method's video pipeline rule: **HyperFrames draws the screens (silent visual), Remotion assembles the film (audio, captions, frame timing).**
|
|
33
|
+
|
|
34
|
+
- `hyperframes` draws each silent scene from the beat sheet: kinetic headline cards, animated stack, price reveal, guarantee card, real product mockups.
|
|
35
|
+
- The `wizz-remotion-best-practices` skill assembles the scenes with the narration, captions, SFX and the motion anchored to the `wizz-ctc-align` timings.
|
|
36
|
+
- Effects and sound by semantic event, if wanted: the `wizz-video-effects` skill, after the assembly plan.
|
|
37
|
+
|
|
38
|
+
### Motion rules for the big lines
|
|
39
|
+
|
|
40
|
+
- Split on-screen headlines into one to three short lines, each a complete, concrete thought.
|
|
41
|
+
- Enter lines in sequence with fast, damped motion; use scale, opacity or horizontal movement consistently.
|
|
42
|
+
- Highlight one decisive line or word with the accent colour; never colour every benefit.
|
|
43
|
+
- Keep copy readable after its entrance. Motion marks a beat, reveals evidence or guides focus.
|
|
44
|
+
- During demos the UI dominates: crops and pans focus attention without hiding the state needed to verify the claim. When the finished output is the strongest proof, play it unobstructed and long enough to judge.
|
|
45
|
+
- Respect title-safe areas, captions, mobile scaling and reduced motion.
|
|
46
|
+
|
|
47
|
+
**Illustrated VSL (optional reference).** For a slide-by-slide stickman or illustrated VSL with a timed image manifest and frame verification, `mikefilsaime-groove/stickman-vsl-director` (MIT) is a reference for the storyboard grammar (one image per narration span, a mandatory art-direction choice, a review deck before rendering). It is not installed by the method; its own scripts are not run from here.
|
|
48
|
+
|
|
49
|
+
## 5. Verification of the rendered master
|
|
50
|
+
|
|
51
|
+
- Inspect frames from the rendered master, not only development snapshots.
|
|
52
|
+
- Watch with sound from start to end; listen at every splice and transition.
|
|
53
|
+
- Check captions against the final audio and the visible claims.
|
|
54
|
+
- No black gaps, clipped lines, stale frames, drift, missing fonts or unreadable demo crops.
|
|
55
|
+
- Measure streams, dimensions, runtime, loudness, peaks, file size and hashes when relevant. Keep a high-quality master separate from the web encode.
|
|
56
|
+
|
|
57
|
+
## 6. Player QA (on the real page)
|
|
58
|
+
|
|
59
|
+
Test the actual page player on phone width and desktop: explicit sound start, restart behaviour, native controls, captions, close or escape, pause when out of viewport, poster, reduced motion, and the button delay set to the price moment (`[BOTÃO APARECE AQUI]`). Under the low ticket ruler, `06b-player-vsl.md` of `wizz-lowticket-metodologia` sets what the player must deliver.
|
|
60
|
+
|
|
61
|
+
Report the measured runtime and the remaining limitations. Nothing is published, deployed, or sent to paid generation without the user's authorization.
|
|
62
|
+
|
|
63
|
+
## Hand-off block
|
|
64
|
+
|
|
65
|
+
Close Phase 6 with this block in the reply:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
🎬 HANDOFF DE PRODUÇÃO · <produto>
|
|
69
|
+
Roteiro final: {output_folder}/vsl/<slug>/03-vsl-principal.md (lead default: <qual>)
|
|
70
|
+
Beat sheet: {output_folder}/vsl/<slug>/05-beat-sheet.md (<N> beats)
|
|
71
|
+
Ledger de claims: 04-qa.md (<N> claims, <N> com marcador pendente)
|
|
72
|
+
Régua: <default | lowticket: duração e posição do pitch>
|
|
73
|
+
Ordem: narração final → wizz-ctc-align → hyperframes (cenas mudas) → Remotion (montagem) → QA do player
|
|
74
|
+
Página em volta do player: wizz-copywriting com as regras de página e player
|
|
75
|
+
```
|
|
@@ -32,6 +32,8 @@ The ones that show up most in an audit, with the direct fix. Use as a quick tria
|
|
|
32
32
|
|
|
33
33
|
> The first four (SQLi, IDOR, mass assignment, and permissive RLS) are **stop-the-line**: if it shows up, fix it before anything else.
|
|
34
34
|
>
|
|
35
|
+
> Free trial abuse (the same person opening trial after trial) is not fixed by blocking the IP: NAT puts a whole house or office behind one address. Use the risk score in the `wizz-signup-flow-cro` skill (trial abuse).
|
|
36
|
+
>
|
|
35
37
|
> For a full adversarial audit (hunting with exploit proof + a prioritized plan), use the `wizz-security-audit-pentest` skill. This skill here is for **fixing** a specific finding from the table.
|
|
36
38
|
|
|
37
39
|
## References (load on demand)
|
package/tools/hooks/README.md
CHANGED
|
@@ -12,18 +12,19 @@ ela é substituída no próximo sync.
|
|
|
12
12
|
| `wizz-artifact-guard.js` | PostToolUse (Write/Edit) | Avisa (nunca bloqueia) artefato escrito fora da subpasta da feature ativa, artefato de feature sem feature ativa, frontmatter faltando em `<tipo>-<slug>.md`, e arquivo solto na raiz fora do `layout:`, com o comando que move. Fail-open sem `_wizz/` ou config. |
|
|
13
13
|
| `wizz-close-reminder.js` | Stop | Aviso ao usuário (`systemMessage`, nunca `decision: block`) quando a feature ativa está `implementado`/`no-ar` e o checklist de fechamento do `wizz-feature.js` tem pendência (ou está no ar e segue ativa). Sem subprocesso, sai cedo fora de projeto Wizz e com feature em andamento, não repete o mesmo aviso na sessão, fail-open. |
|
|
14
14
|
| `rtk-rewrite.sh` | PreToolUse (Bash) | Reescreve comandos de shell via `rtk rewrite` (token economy). Fork local do hook gerado pelo instalador upstream do rtk, com 3 patches (auditoria 2026-07-07, C4/RT1; e 2026-09-11, RT-H3): reescrita linha a linha em comando multi-linha (o binário só reescrevia a 1ª linha), timeout interno por linha (`RTK_HOOK_TIMEOUT`, default 3s) que não depende do `timeout`/`gtimeout` do GNU coreutils, e cache do version-guard (`RTK_CACHE_TTL_SECONDS`, default 1h, em `${XDG_CACHE_HOME:-$HOME/.cache}/wizz/rtk-version`) pra não rodar `rtk --version` a cada `Bash` tool call da sessão. Requer `rtk` + `jq` no PATH; fail-open em ambos (e no cache: ilegível/corrompido/expirado recomputa em silêncio). |
|
|
15
|
-
| `rtk-rewrite.js` | PreToolUse (Bash) | Versão Node do `rtk-rewrite.sh`, registrada no lugar dele só no Windows (sem bash e jq garantidos lá), desde 2026-10-15. Mesmo contrato: guard de multi-linha, linha a linha, timeout por linha, piso 0.23.0, fail-open. Sem o cache de versão. `node test/test-rtk-hook-node.js` exige a mesma saída dos dois contra o mesmo `rtk` fake.
|
|
15
|
+
| `rtk-rewrite.js` | PreToolUse (Bash) | Versão Node do `rtk-rewrite.sh`, registrada no lugar dele só no Windows (sem bash e jq garantidos lá), desde 2026-10-15. Mesmo contrato: guard de multi-linha, linha a linha, timeout por linha, piso 0.23.0, fail-open. Sem o cache de versão. `node test/test-rtk-hook-node.js` exige a mesma saída dos dois contra o mesmo `rtk` fake. |
|
|
16
16
|
|
|
17
17
|
Arquivos de apoio (copiados junto, NUNCA registrados em settings.json; o doctor cobra a presença):
|
|
18
18
|
|
|
19
|
-
| Arquivo
|
|
20
|
-
|
|
|
21
|
-
| `wizz-feature-lib.js`
|
|
22
|
-
| `wizz-layout-lib.js`
|
|
23
|
-
| `wizz-cerebro-lib.js`
|
|
24
|
-
| `wizz-debug-lib.js`
|
|
25
|
-
| `wizz-
|
|
26
|
-
| `wizz-
|
|
19
|
+
| Arquivo | O que é |
|
|
20
|
+
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| `wizz-feature-lib.js` | Funções puras de feature, git, config, pastas de artefato e frontmatter. Usada por todos os hooks acima. |
|
|
22
|
+
| `wizz-layout-lib.js` | O que pode ficar na raiz e para onde vai o resto (tipos `codigo`, `scripts`, `dados`, `docs`, `midia`, `arquivo`). |
|
|
23
|
+
| `wizz-cerebro-lib.js` | Grava o fechamento no Cérebro (`encerrar --cerebro`): acha o vault e `projetos/<projeto>.md`, atualiza a linha da feature e a `Última sessão` do bloco Estado e põe 1 linha em Sessões (teto 15; as antigas vão para `_index/cerebro-archive.md`, lido só por grep). Idempotente, fail-open. |
|
|
24
|
+
| `wizz-debug-lib.js` | Diagnóstico opt-in dos hooks: com `WIZZ_DEBUG=1` cada erro engolido vira 1 linha em stderr e é anexada a `~/.claude/wizz-debug.log` (`WIZZ_DEBUG_FILE` troca o caminho). Sem a variável: zero saída, zero disco. Os hooks seguem fail-open (exit 0). O `wizz doctor` roda cada hook com esse modo ligado (check `hooks-run`). |
|
|
25
|
+
| `wizz-sprint-archive-lib.js` | Arquivamento do `encerrar`: planeja antes de tocar no disco (mescla com `_arquivo/<slug>/`, o de mesmo nome vai para o lado como `.anterior-<data>`, dono do `sprint-status.yaml` da raiz, links e referências de fora) e aplica com desfazer. |
|
|
26
|
+
| `wizz-feature.js` | CLI do ciclo de vida da feature: `resolver`, `definir`, `artefato`, `decidir`, `status`, `verificar`, `encerrar [--cerebro]`, `listar`. É o passo verificável que router/maestro rodam antes de despachar. |
|
|
27
|
+
| `wizz-arrumar.js` | CLI de arrumação: `layout [--gravar]`, `planejar`, `aplicar` (manifesto .tsv + referências + testes antes/depois + `desfazer.sh`), `mover`, `desfazer`. |
|
|
27
28
|
|
|
28
29
|
Regra completa em `src/modules/wizz/_shared/organizacao.md` (2026-10-02, caso Spider). `session-rules.js` passou a lembrar "nenhuma feature ativa" e "projeto sem layout" dentro de projeto Wizz, e `wizz-router-enforce.js` acrescenta 1 linha de feature por prompt em projeto Wizz.
|
|
29
30
|
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
// 1. Artefato fora do lugar: escrito na pasta de artefatos mas fora da
|
|
10
10
|
// subpasta da feature ativa, artefato de feature sem feature ativa, ou
|
|
11
11
|
// artefato tipado (`spec-x.md`...) com frontmatter faltando/errado.
|
|
12
|
+
// Gravar num sprint arquivado (`_arquivo/<slug>/`) também avisa, e o
|
|
13
|
+
// arquivo solto direto em `_arquivo/` não tem dono (2026-10-24).
|
|
12
14
|
// 2. Arquivo solto na raiz: fora do `layout:` do config. Sugere o destino e
|
|
13
15
|
// o comando que move atualizando as referências.
|
|
14
16
|
//
|
|
@@ -73,13 +75,37 @@ function artifactWarning(file, content, state, { frontmatterOnly = false } = {})
|
|
|
73
75
|
const slug = state.slug;
|
|
74
76
|
const msgs = [];
|
|
75
77
|
|
|
78
|
+
const archived = parts[0] === lib.ARCHIVE_DIR;
|
|
79
|
+
// Arquivo direto em `_arquivo/` não é de sprint nenhum: o dono não é o nome dele.
|
|
80
|
+
const looseInArchive = archived && parts.length === 2;
|
|
81
|
+
const owner = archived ? (looseInArchive ? '' : parts[1]) : parts[0];
|
|
82
|
+
// O caminho dentro do sprint (com as subpastas), para o "mova para".
|
|
83
|
+
const within = parts.length === 1 ? parts : parts.slice(archived ? 2 : 1);
|
|
84
|
+
const inSprint = (sprint) => path.relative(state.root, path.join(base, sprint, ...within));
|
|
85
|
+
|
|
86
|
+
// Sprint fechado é histórico: avisa em Write e Edit, o sprint não reabre sozinho.
|
|
87
|
+
if (archived && !looseInArchive) {
|
|
88
|
+
msgs.push(
|
|
89
|
+
`SPRINT ARQUIVADO: ${relFile} está no sprint fechado "${owner}". ` +
|
|
90
|
+
(slug
|
|
91
|
+
? `Trabalho novo entra no sprint aberto (${inSprint(slug)}); `
|
|
92
|
+
: 'Trabalho novo entra num sprint aberto (wizz-feature.js definir <slug>); ') +
|
|
93
|
+
'mexa no arquivo morto só para corrigir o histórico, a pedido do dono.',
|
|
94
|
+
);
|
|
95
|
+
} else if (looseInArchive && !frontmatterOnly) {
|
|
96
|
+
msgs.push(
|
|
97
|
+
`ARQUIVO SOLTO NO ARQUIVO MORTO: ${relFile} está direto em ${lib.ARCHIVE_DIR}/, fora de qualquer sprint fechado. ` +
|
|
98
|
+
`Mova para ${lib.ARCHIVE_DIR}/<slug>/ do sprint a que ele pertence.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
76
102
|
// Edit: o lugar já foi avisado quando o arquivo nasceu; só o frontmatter conta.
|
|
77
103
|
if (!frontmatterOnly && parts.length === 1) {
|
|
78
104
|
if (lib.PROJECT_WIDE.test(name)) return '';
|
|
79
105
|
if (slug) {
|
|
80
106
|
msgs.push(
|
|
81
107
|
`ARTEFATO FORA DA FEATURE: ${relFile} está na raiz das pastas de artefato, mas a feature ativa é "${slug}". ` +
|
|
82
|
-
`Mova para ${
|
|
108
|
+
`Mova para ${inSprint(slug)} agora (raiz é só para artefato de projeto inteiro: arquitetura, contexto).`,
|
|
83
109
|
);
|
|
84
110
|
} else if (type) {
|
|
85
111
|
msgs.push(
|
|
@@ -87,16 +113,16 @@ function artifactWarning(file, content, state, { frontmatterOnly = false } = {})
|
|
|
87
113
|
`Grave a feature a partir do pedido (node ${HOOK_CMD}/wizz-feature.js definir <slug>) e mova o arquivo para a subpasta dela.`,
|
|
88
114
|
);
|
|
89
115
|
}
|
|
90
|
-
} else if (!frontmatterOnly && slug &&
|
|
116
|
+
} else if (!frontmatterOnly && slug && !archived && parts[0] !== slug) {
|
|
91
117
|
msgs.push(
|
|
92
118
|
`ARTEFATO EM OUTRO SPRINT: ${relFile} está em "${parts[0]}", mas o sprint aberto é "${slug}". ` +
|
|
93
|
-
`Trabalho novo entra no sprint aberto: mova para ${
|
|
119
|
+
`Trabalho novo entra no sprint aberto: mova para ${inSprint(slug)}. Abrir outro sprint só com a escolha do dono (wizz-feature.js definir <slug> --novo).`,
|
|
94
120
|
);
|
|
95
121
|
}
|
|
96
122
|
|
|
97
123
|
if (type) {
|
|
98
|
-
const
|
|
99
|
-
const problems = lib.frontmatterProblems(content,
|
|
124
|
+
const expected = parts.length > 1 ? owner : slug;
|
|
125
|
+
const problems = lib.frontmatterProblems(content, expected);
|
|
100
126
|
if (problems.length > 0) msgs.push(`FRONTMATTER de ${relFile}: ${problems.join('; ')}.`);
|
|
101
127
|
}
|
|
102
128
|
return msgs.join(' ');
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
// Estado + 1 linha na tabela Sessões" (`_shared/cerebro.md`).
|
|
7
7
|
//
|
|
8
8
|
// Regras do protocolo do Cérebro (skill `wizz-cerebro`, comandos-sessao.md):
|
|
9
|
-
// - vault:
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// - vault: mesma ordem da seção `## Vault` da skill (fonte única):
|
|
10
|
+
// `WIZZ_CEREBRO_VAULT` > `cerebro_vault:` no config > Cérebro por projeto
|
|
11
|
+
// (`<output_folder>/cerebro/`) > `~/Documents/projects/Obsidian Vault/`.
|
|
12
|
+
// O `find ~` do protocolo fica de fora (caro demais para uma CLI).
|
|
12
13
|
// - estado mora em `projetos/<nome>.md`, bloco `## Estado` no topo.
|
|
13
14
|
// - `Sessões` com teto de 15 linhas; nunca apagar histórico: as antigas
|
|
14
15
|
// vão para `_index/cerebro-archive.md`, seção `## Sessões antigas —
|
|
@@ -49,14 +50,31 @@ const hasCerebro = (dir) => {
|
|
|
49
50
|
|
|
50
51
|
const expandHome = (p) => (p.startsWith('~') ? path.join(os.homedir(), p.slice(1)) : p);
|
|
51
52
|
|
|
53
|
+
/** Pasta do Cérebro por projeto: `<output_folder>/cerebro/`, `_wizz-output` sem config. */
|
|
54
|
+
function projectVault(root, configValues = {}) {
|
|
55
|
+
if (!root) return '';
|
|
56
|
+
const raw =
|
|
57
|
+
typeof configValues.output_folder === 'string' && configValues.output_folder.trim()
|
|
58
|
+
? configValues.output_folder.trim()
|
|
59
|
+
: '_wizz-output';
|
|
60
|
+
const out = raw.replace(/^\{project-root\}[/\\]?/, '');
|
|
61
|
+
return path.join(path.isAbsolute(out) ? out : path.join(root, out), 'cerebro');
|
|
62
|
+
}
|
|
63
|
+
|
|
52
64
|
/**
|
|
53
|
-
* Acha o vault: `WIZZ_CEREBRO_VAULT` > `cerebro_vault` do config >
|
|
54
|
-
* protocolo no HOME. Só vale pasta com CEREBRO.md.
|
|
65
|
+
* Acha o vault: `WIZZ_CEREBRO_VAULT` > `cerebro_vault` do config > Cérebro por
|
|
66
|
+
* projeto em `root` > padrão do protocolo no HOME. Só vale pasta com CEREBRO.md.
|
|
55
67
|
* @param {Record<string,string>} configValues
|
|
68
|
+
* @param {string} [root] raiz do projeto (habilita o Cérebro por projeto)
|
|
56
69
|
* @returns {string} caminho do vault ou ''
|
|
57
70
|
*/
|
|
58
|
-
function findVault(configValues = {}) {
|
|
59
|
-
const candidates = [
|
|
71
|
+
function findVault(configValues = {}, root = '') {
|
|
72
|
+
const candidates = [
|
|
73
|
+
process.env.WIZZ_CEREBRO_VAULT,
|
|
74
|
+
configValues.cerebro_vault,
|
|
75
|
+
projectVault(root, configValues),
|
|
76
|
+
path.join(os.homedir(), DEFAULT_VAULT),
|
|
77
|
+
];
|
|
60
78
|
for (const c of candidates) {
|
|
61
79
|
if (typeof c === 'string' && c.trim() && hasCerebro(expandHome(c.trim()))) return expandHome(c.trim());
|
|
62
80
|
}
|
|
@@ -200,24 +218,29 @@ function writeArchive(vault, projeto, overflow, header) {
|
|
|
200
218
|
/**
|
|
201
219
|
* Grava o fechamento no arquivo do projeto no Cérebro. Fail-open: qualquer
|
|
202
220
|
* falha devolve {gravado: false, motivo} sem lançar.
|
|
203
|
-
* @param {{root: string, slug: string, status: string, projeto?: string}} input
|
|
221
|
+
* @param {{root: string, slug: string, status: string, projeto?: string, spec?: string}} input
|
|
222
|
+
* `spec`: caminho da spec que o `encerrar` acabou de arquivar (sem ele, adivinha)
|
|
204
223
|
* @returns {{gravado: boolean, arquivo?: string, motivo?: string}}
|
|
205
224
|
*/
|
|
206
|
-
function recordClose({ root, slug, status, projeto }) {
|
|
225
|
+
function recordClose({ root, slug, status, projeto, spec: knownSpec }) {
|
|
207
226
|
try {
|
|
208
227
|
const { values } = lib.readConfig(root);
|
|
209
|
-
const vault = findVault(values);
|
|
228
|
+
const vault = findVault(values, root);
|
|
210
229
|
if (!vault) return { gravado: false, motivo: 'vault do Cérebro não encontrado (WIZZ_CEREBRO_VAULT)' };
|
|
211
230
|
const file = findProjectFile(vault, root, values, projeto);
|
|
212
231
|
if (!file) return { gravado: false, motivo: `sem projetos/<projeto>.md no vault (use --projeto <nome>)` };
|
|
213
232
|
|
|
214
233
|
const date = lib.today();
|
|
215
234
|
const dirs = lib.artifactDirs(root);
|
|
216
|
-
// O `encerrar`
|
|
235
|
+
// O `encerrar` passa a spec que arquivou. Sem ela, a pasta aberta vem
|
|
236
|
+
// primeiro: num sprint reaberto a cópia de `_arquivo/` é o fechamento
|
|
237
|
+
// ANTERIOR, e o Cérebro gravava o resumo velho.
|
|
217
238
|
const spec =
|
|
218
|
-
|
|
239
|
+
(knownSpec && fs.existsSync(knownSpec) && knownSpec) ||
|
|
240
|
+
[path.join(dirs.planning, slug), path.join(dirs.planning, lib.ARCHIVE_DIR, slug)]
|
|
219
241
|
.map((d) => path.join(d, `spec-${slug}.md`))
|
|
220
|
-
.find((f) => fs.existsSync(f)) ||
|
|
242
|
+
.find((f) => fs.existsSync(f)) ||
|
|
243
|
+
path.join(dirs.planning, slug, `spec-${slug}.md`);
|
|
221
244
|
const specText = fs.existsSync(spec) ? fs.readFileSync(spec, 'utf8') : '';
|
|
222
245
|
const specRel = path.relative(root, spec);
|
|
223
246
|
const resumo = implementationSummary(specText);
|
|
@@ -399,9 +399,11 @@ const MD_LINK = /\]\((?!(?:[a-z][\w+.-]*:|#|\/))([^)\s#?]+)([^)\s]*)\)/gi;
|
|
|
399
399
|
* @param {string} oldDir - pasta antiga do arquivo (absoluta)
|
|
400
400
|
* @param {string} newDir - pasta nova do arquivo (absoluta)
|
|
401
401
|
* @param {(abs: string) => string} [mapAbs] - caminho atual de um alvo movido
|
|
402
|
+
* @param {{onlyMoved?: boolean}} [opts] - só toca link cujo alvo `mapAbs` moveu
|
|
403
|
+
* (arquivo que não sai do lugar: o resto dos links fica como o autor escreveu)
|
|
402
404
|
* @returns {string}
|
|
403
405
|
*/
|
|
404
|
-
function rebaseMarkdownLinks(text, oldDir, newDir, mapAbs = (p) => p) {
|
|
406
|
+
function rebaseMarkdownLinks(text, oldDir, newDir, mapAbs = (p) => p, { onlyMoved = false } = {}) {
|
|
405
407
|
return String(text).replaceAll(MD_LINK, (whole, target, rest) => {
|
|
406
408
|
let decoded;
|
|
407
409
|
try {
|
|
@@ -411,6 +413,7 @@ function rebaseMarkdownLinks(text, oldDir, newDir, mapAbs = (p) => p) {
|
|
|
411
413
|
}
|
|
412
414
|
const original = path.resolve(oldDir, decoded);
|
|
413
415
|
const abs = mapAbs(original);
|
|
416
|
+
if (onlyMoved && abs === original) return whole;
|
|
414
417
|
// Link que já não apontava para nada fica como está: reescrever inventaria outro caminho.
|
|
415
418
|
if (!fs.existsSync(original) && !fs.existsSync(abs)) return whole;
|
|
416
419
|
const next = path.relative(newDir, abs).split(path.sep).join('/') || '.';
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
// status <status> rascunho|aprovado|implementado|no-ar na spec
|
|
17
17
|
// verificar checklist de fechamento (exit 1 se houver pendência)
|
|
18
18
|
// encerrar status final na spec, arquiva o sprint em _arquivo/
|
|
19
|
-
// (com o sprint-status.yaml da raiz
|
|
19
|
+
// (com o sprint-status.yaml da raiz quando é dele; o de
|
|
20
|
+
// mesmo nome de um fechamento anterior vai para o lado;
|
|
21
|
+
// falha no meio desfaz tudo; referências de fora corrigidas
|
|
22
|
+
// ou listadas), limpa/troca active_feature
|
|
20
23
|
// (recusa com pendência do checklist, salvo --forcar);
|
|
21
24
|
// com --cerebro grava Estado + linha de Sessões no
|
|
22
25
|
// projetos/<projeto>.md do Cérebro (--projeto <nome>)
|
|
@@ -29,6 +32,7 @@ const fs = require('node:fs');
|
|
|
29
32
|
const path = require('node:path');
|
|
30
33
|
const lib = require('./wizz-feature-lib.js');
|
|
31
34
|
const cerebro = require('./wizz-cerebro-lib.js');
|
|
35
|
+
const archive = require('./wizz-sprint-archive-lib.js');
|
|
32
36
|
|
|
33
37
|
const ALIASES = {
|
|
34
38
|
resolve: 'resolver',
|
|
@@ -61,6 +65,8 @@ function rel(root, p) {
|
|
|
61
65
|
return path.relative(root, p) || '.';
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
const toPosixPath = (p) => p.split(path.sep).join('/');
|
|
69
|
+
|
|
64
70
|
/** Pasta de cada tipo de artefato, para uma feature. */
|
|
65
71
|
function artifactPath(root, type, slug, dirs = lib.artifactDirs(root)) {
|
|
66
72
|
const where = lib.ARTIFACT_TYPES[type];
|
|
@@ -146,7 +152,10 @@ function cmdDefinir(state, opts) {
|
|
|
146
152
|
const slug = lib.toSlug(raw);
|
|
147
153
|
if (!slug) return { ok: false, code: 2, line: 'Uso: definir <slug ou nome da feature>. Nada foi gravado.' };
|
|
148
154
|
const { root } = state;
|
|
149
|
-
|
|
155
|
+
// O sprint aberto é o resolvido (branch > config), não só o do config: com o
|
|
156
|
+
// branch apontando outra feature o config velho recusava a certa, e o vazio
|
|
157
|
+
// deixava abrir uma que o branch esconderia (adendo de 2026-10-19).
|
|
158
|
+
const previous = state.slug;
|
|
150
159
|
// O sprint aberto manda: assunto novo entra nele. Abrir outro sprint com um
|
|
151
160
|
// aberto é escolha do dono, nunca do agente (pastas demais na Lucrare, 2026-10-15).
|
|
152
161
|
if (previous && previous !== slug && !opts.novo) {
|
|
@@ -261,72 +270,6 @@ function looseArtifacts(root, dirs) {
|
|
|
261
270
|
return out;
|
|
262
271
|
}
|
|
263
272
|
|
|
264
|
-
/** Move `src` para `dest`, mesclando quando `dest` já existe (sprint reaberto e fechado de novo). */
|
|
265
|
-
function moveInto(src, dest) {
|
|
266
|
-
if (!fs.existsSync(dest)) {
|
|
267
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
268
|
-
fs.renameSync(src, dest);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
for (const name of fs.readdirSync(src)) moveInto(path.join(src, name), path.join(dest, name));
|
|
272
|
-
fs.rmdirSync(src);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function markdownFiles(dir) {
|
|
276
|
-
let entries = [];
|
|
277
|
-
try {
|
|
278
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
279
|
-
} catch {
|
|
280
|
-
return [];
|
|
281
|
-
}
|
|
282
|
-
return entries.flatMap((e) => {
|
|
283
|
-
const abs = path.join(dir, e.name);
|
|
284
|
-
if (e.isDirectory()) return markdownFiles(abs);
|
|
285
|
-
return e.isFile() && e.name.endsWith('.md') ? [abs] : [];
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Sprint fechado vai para o arquivo morto: as duas pastas da feature descem
|
|
291
|
-
* para `_arquivo/<slug>/`, e o `sprint-status.yaml` da raiz de implementação
|
|
292
|
-
* (o do sprint aberto) vai junto, para o próximo sprint começar limpo.
|
|
293
|
-
* @returns {{planning: string, implementation: string, sprintStatus: string}}
|
|
294
|
-
*/
|
|
295
|
-
function archiveSprint(root, slug) {
|
|
296
|
-
const dirs = lib.artifactDirs(root);
|
|
297
|
-
const out = {};
|
|
298
|
-
const moves = [dirs.planning, dirs.implementation].map((base) => ({
|
|
299
|
-
src: path.join(base, slug),
|
|
300
|
-
dest: path.join(base, lib.ARCHIVE_DIR, slug),
|
|
301
|
-
}));
|
|
302
|
-
const mapAbs = (abs) => {
|
|
303
|
-
const hit = moves.find((m) => abs === m.src || abs.startsWith(m.src + path.sep));
|
|
304
|
-
return hit ? hit.dest + abs.slice(hit.src.length) : abs;
|
|
305
|
-
};
|
|
306
|
-
// Links relativos (`../`) descem um nível junto com o arquivo: reescreve antes de mover.
|
|
307
|
-
for (const { src, dest } of moves) {
|
|
308
|
-
for (const file of markdownFiles(src)) {
|
|
309
|
-
const text = fs.readFileSync(file, 'utf8');
|
|
310
|
-
const next = lib.rebaseMarkdownLinks(text, path.dirname(file), path.dirname(mapAbs(file)), mapAbs);
|
|
311
|
-
if (next !== text) fs.writeFileSync(file, next);
|
|
312
|
-
}
|
|
313
|
-
if (fs.existsSync(src)) moveInto(src, dest);
|
|
314
|
-
}
|
|
315
|
-
out.planning = moves[0].dest;
|
|
316
|
-
out.implementation = moves[1].dest;
|
|
317
|
-
const status = path.join(dirs.implementation, 'sprint-status.yaml');
|
|
318
|
-
out.sprintStatus = '';
|
|
319
|
-
if (fs.existsSync(status)) {
|
|
320
|
-
const dest = path.join(out.implementation, 'sprint-status.yaml');
|
|
321
|
-
fs.mkdirSync(out.implementation, { recursive: true });
|
|
322
|
-
if (!fs.existsSync(dest)) {
|
|
323
|
-
fs.renameSync(status, dest);
|
|
324
|
-
out.sprintStatus = dest;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return out;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
273
|
/**
|
|
331
274
|
* Checklist de fechamento. Itens mecânicos são checados; os de julgamento
|
|
332
275
|
* (Cérebro, memória) voltam como "confirmar" para o agente responder.
|
|
@@ -390,9 +333,30 @@ function cmdVerificar(state, opts) {
|
|
|
390
333
|
}
|
|
391
334
|
|
|
392
335
|
/** `--cerebro`: grava no vault; fail-open, nunca desfaz o encerramento. */
|
|
393
|
-
function cerebroClose(state, slug, status, opts) {
|
|
336
|
+
function cerebroClose(state, slug, status, opts, spec) {
|
|
394
337
|
const projeto = typeof opts.projeto === 'string' ? opts.projeto : '';
|
|
395
|
-
return cerebro.recordClose({ root: state.root, slug, status, projeto });
|
|
338
|
+
return cerebro.recordClose({ root: state.root, slug, status, projeto, spec });
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Sprint fechado vai para o arquivo morto: as duas pastas da feature descem
|
|
343
|
+
* para `_arquivo/<slug>/` (mesclando com um fechamento anterior), com a spec
|
|
344
|
+
* já no status final e as referências corrigidas. Planeja antes, aplica com
|
|
345
|
+
* desfazer: falha no meio deixa o projeto como estava.
|
|
346
|
+
*/
|
|
347
|
+
function archiveSprint(state, slug, status) {
|
|
348
|
+
const spec = artifactPath(state.root, 'spec', slug);
|
|
349
|
+
const created = !fs.existsSync(spec) && ensureArtifact(state.root, 'spec', slug).created;
|
|
350
|
+
try {
|
|
351
|
+
const text = touch(lib.setFrontmatterField(fs.readFileSync(spec, 'utf8'), 'status', status));
|
|
352
|
+
const plan = archive.planArchive(state.root, slug, { edits: new Map([[spec, text]]) });
|
|
353
|
+
const { asides } = archive.applyArchive(plan);
|
|
354
|
+
return { plan, asides, spec: plan.moves[0].dest + spec.slice(plan.moves[0].src.length) };
|
|
355
|
+
} catch (error) {
|
|
356
|
+
// A spec criada aqui some junto: nada do encerrar fica para trás.
|
|
357
|
+
if (created && fs.existsSync(spec)) fs.unlinkSync(spec);
|
|
358
|
+
throw error;
|
|
359
|
+
}
|
|
396
360
|
}
|
|
397
361
|
|
|
398
362
|
function cmdEncerrar(state, opts) {
|
|
@@ -400,6 +364,15 @@ function cmdEncerrar(state, opts) {
|
|
|
400
364
|
if (!slug) return { ok: false, code: 2, line: 'Nenhuma feature ativa para encerrar.' };
|
|
401
365
|
const status = typeof opts.status === 'string' ? opts.status.toLowerCase() : 'no-ar';
|
|
402
366
|
if (!lib.STATUS.includes(status)) return { ok: false, code: 2, line: `Status inválido. Use: ${lib.STATUS.join('|')}.` };
|
|
367
|
+
// Rodar de novo num sprint já fechado não recria a spec nem mexe no arquivo morto.
|
|
368
|
+
if (archive.isArchived(state.root, slug)) {
|
|
369
|
+
if (state.fromConfig === slug) lib.writeConfigValue(state.root, 'active_feature', '');
|
|
370
|
+
return {
|
|
371
|
+
ok: true,
|
|
372
|
+
data: { feature: slug, status, ja_encerrado: true },
|
|
373
|
+
line: `Sprint ${slug} já estava encerrado em ${lib.ARCHIVE_DIR}/${slug}/: nada a fazer.`,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
403
376
|
// Não fecha com pendência: a checagem roda ANTES de gravar, já com o status final.
|
|
404
377
|
const pend = checklist(state, slug, { statusOverride: status }).pendentes;
|
|
405
378
|
if (pend.length > 0 && !opts.forcar) {
|
|
@@ -410,9 +383,20 @@ function cmdEncerrar(state, opts) {
|
|
|
410
383
|
line: `Não encerrei ${slug}: ${pend.map((p) => p.item + (p.detalhe ? ` (${p.detalhe})` : '')).join('; ')}. Resolva e rode de novo (ou --forcar se o dono aceitar a pendência).`,
|
|
411
384
|
};
|
|
412
385
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
386
|
+
let archived;
|
|
387
|
+
try {
|
|
388
|
+
archived = archiveSprint(state, slug, status);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
const desfeito = error.desfeito === false ? 'O desfazer não fechou: confira o projeto.' : 'Desfiz o que tinha feito: nada mudou.';
|
|
391
|
+
return {
|
|
392
|
+
ok: false,
|
|
393
|
+
code: 1,
|
|
394
|
+
data: { feature: slug, erro: error.message },
|
|
395
|
+
line: `Não encerrei ${slug}: ${error.message}. ${desfeito}`,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const { plan } = archived;
|
|
399
|
+
const file = archived.spec;
|
|
416
400
|
const next = lib.toSlug(typeof opts.proxima === 'string' ? opts.proxima : '');
|
|
417
401
|
if (state.fromConfig === slug || !state.fromConfig) lib.writeConfigValue(state.root, 'active_feature', next);
|
|
418
402
|
if (next) {
|
|
@@ -420,10 +404,14 @@ function cmdEncerrar(state, opts) {
|
|
|
420
404
|
for (const d of [dirs.planning, dirs.implementation]) fs.mkdirSync(path.join(d, next), { recursive: true });
|
|
421
405
|
}
|
|
422
406
|
const parts = [`Sprint ${slug} encerrado (${status}) em ${lib.today()} e arquivado. Spec: ${rel(state.root, file)}.`];
|
|
423
|
-
if (
|
|
407
|
+
if (plan.status?.owned) parts.push(`sprint-status.yaml arquivado junto: o próximo sprint começa limpo.`);
|
|
408
|
+
else if (plan.status) parts.push(`O sprint-status.yaml da raiz ficou na raiz (${plan.status.motivo}): mova à mão se for deste sprint.`);
|
|
409
|
+
if (archived.asides.length > 0)
|
|
410
|
+
parts.push(`Fechamento anterior preservado ao lado: ${archived.asides.map((a) => rel(state.root, a)).join(', ')}.`);
|
|
411
|
+
if (plan.externas.length > 0) parts.push(`Ainda citam a pasta antiga do sprint (corrija): ${plan.externas.map(toPosixPath).join(', ')}.`);
|
|
424
412
|
parts.push(next ? `Sprint aberto agora: ${next}.` : 'Nenhum sprint aberto agora: o próximo pedido de trabalho abre um.');
|
|
425
413
|
if (state.fromBranch === slug) parts.push(`O branch ${state.git.branch} ainda aponta para ${slug}: volte ao tronco ou troque de branch.`);
|
|
426
|
-
const memo = opts.cerebro ? cerebroClose(state, slug, status, opts) : null;
|
|
414
|
+
const memo = opts.cerebro ? cerebroClose(state, slug, status, opts, file) : null;
|
|
427
415
|
if (memo && memo.gravado) parts.push(`Cérebro atualizado: ${memo.arquivo} (Estado + linha em Sessões).`);
|
|
428
416
|
else {
|
|
429
417
|
if (memo) parts.push(`Cérebro não gravado (${memo.motivo}).`);
|
|
@@ -435,7 +423,9 @@ function cmdEncerrar(state, opts) {
|
|
|
435
423
|
status,
|
|
436
424
|
proxima: next,
|
|
437
425
|
pendencias: pend.map((p) => p.item),
|
|
438
|
-
arquivo: rel(state.root,
|
|
426
|
+
arquivo: rel(state.root, plan.moves[0].dest),
|
|
427
|
+
referencias_externas: plan.externas,
|
|
428
|
+
preservados: archived.asides.map((a) => rel(state.root, a)),
|
|
439
429
|
};
|
|
440
430
|
if (memo) data.cerebro = memo;
|
|
441
431
|
return { ok: true, data, line: parts.join(' ') };
|