wizz-method 2.18.0 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -3
- package/removals.txt +10 -0
- package/skills-registry.yaml +50 -38
- package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +9 -9
- package/src/bmm-skills/4-implementation/wizz-create-story/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-dev-story/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-qa-generate-e2e-tests/checklist.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-quick-dev/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/wizz-sprint-status/SKILL.md +5 -5
- package/src/bmm-skills/module-help.csv +1 -1
- package/src/bmm-skills/module.yaml +1 -1
- package/src/core-skills/_shared/handoff-protocol.md +2 -2
- package/src/modules/lowticket/skills/wizz-lowticket-funil/SKILL.md +1 -1
- package/src/modules/lowticket/skills/wizz-lowticket-guia/SKILL.md +1 -1
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/SKILL.md +1 -1
- package/src/modules/lowticket/skills/wizz-lowticket-minerador/SKILL.md +1 -1
- package/src/modules/lowticket/skills/wizz-lowticket-pagina/SKILL.md +1 -1
- package/src/modules/lowticket/skills/wizz-lowticket-trafego/SKILL.md +1 -1
- package/src/modules/wizz/_shared/cerebro.md +7 -7
- package/src/modules/wizz/_shared/communication-rules.md +1 -1
- package/src/modules/wizz/_shared/encerramento.md +4 -3
- package/src/modules/wizz/_shared/organizacao.md +8 -4
- package/src/modules/wizz/_shared/token-economy.md +1 -1
- package/src/modules/wizz/_shared/video-pipeline.md +4 -4
- package/src/modules/wizz/agents/wizz-ads/SKILL.md +4 -1
- package/src/modules/wizz/agents/wizz-ads/customize.toml +12 -3
- package/src/modules/wizz/agents/wizz-ads/knowledge/playbook.md +36 -0
- package/src/modules/wizz/agents/wizz-copy/SKILL.md +4 -1
- package/src/modules/wizz/agents/wizz-copy/customize.toml +2 -3
- package/src/modules/wizz/agents/wizz-copy/knowledge/playbook.md +34 -0
- package/src/modules/wizz/agents/wizz-designer/SKILL.md +2 -2
- package/src/modules/wizz/agents/wizz-designer/customize.toml +2 -3
- package/src/modules/wizz/agents/wizz-growth/SKILL.md +4 -1
- package/src/modules/wizz/agents/wizz-growth/customize.toml +33 -4
- package/src/modules/wizz/agents/wizz-growth/knowledge/playbook.md +39 -0
- package/src/modules/wizz/agents/wizz-maestro/SKILL.md +6 -8
- package/src/modules/wizz/agents/wizz-maestro/customize.toml +2 -3
- package/src/modules/wizz/agents/wizz-memoria/SKILL.md +7 -7
- package/src/modules/wizz/agents/wizz-memoria/customize.toml +5 -6
- package/src/modules/wizz/agents/wizz-qa/SKILL.md +5 -6
- package/src/modules/wizz/agents/wizz-qa/customize.toml +3 -4
- package/src/modules/wizz/agents/wizz-seo/SKILL.md +4 -1
- package/src/modules/wizz/agents/wizz-seo/customize.toml +2 -3
- package/src/modules/wizz/agents/wizz-seo/knowledge/playbook.md +31 -0
- package/src/modules/wizz/agents/wizz-social/SKILL.md +3 -3
- package/src/modules/wizz/agents/wizz-social/customize.toml +2 -3
- package/src/modules/wizz/overrides/wizz-agent-analyst.toml +2 -2
- package/src/modules/wizz/overrides/wizz-agent-architect.toml +3 -3
- package/src/modules/wizz/overrides/wizz-agent-dev.toml +3 -3
- package/src/modules/wizz/overrides/wizz-agent-pm.toml +2 -2
- package/src/modules/wizz/overrides/wizz-agent-tech-writer.toml +2 -2
- package/src/modules/wizz/overrides/wizz-agent-ux-designer.toml +2 -2
- package/src/skills-lib/_shared/complexity-signal.md +8 -9
- package/src/skills-lib/_shared/dispatch-rule.yaml +21 -0
- package/src/skills-lib/{cerebro → wizz-cerebro}/SKILL.md +1 -1
- package/src/skills-lib/{ctc-align → wizz-ctc-align}/SKILL.md +1 -1
- package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/SKILL.md +1 -1
- package/src/skills-lib/{find-skills → wizz-find-skills}/SKILL.md +1 -1
- package/src/skills-lib/wizz-huashu-design/references/sfx-library.md +1 -1
- package/src/skills-lib/wizz-huashu-design/scripts/add-music.sh +1 -1
- package/src/skills-lib/wizz-huashu-design/scripts/html2pptx.js +45 -37
- package/src/skills-lib/wizz-huashu-design/scripts/mix-voiceover.sh +1 -1
- package/src/skills-lib/wizz-huashu-design/scripts/narrate-pipeline.mjs +11 -5
- package/src/skills-lib/wizz-huashu-design/scripts/test_scripts.py +209 -0
- package/src/skills-lib/wizz-huashu-design/scripts/tts-doubao.mjs +42 -28
- package/src/skills-lib/wizz-ig-profile-optimizer/SKILL.md +1 -1
- package/src/skills-lib/wizz-inicio-de-projeto/SKILL.md +1 -1
- package/src/skills-lib/wizz-launch-readiness/SKILL.md +7 -7
- package/src/skills-lib/wizz-launch-readiness/references/02-seguranca.md +3 -3
- package/src/skills-lib/wizz-launch-readiness/references/03-seo-descoberta.md +5 -5
- package/src/skills-lib/wizz-launch-readiness/references/04-analytics-medicao.md +1 -1
- package/src/skills-lib/wizz-launch-readiness/references/05-conteudo-prova-social.md +6 -6
- package/src/skills-lib/wizz-launch-readiness/references/06-legal-lgpd.md +1 -1
- package/src/skills-lib/wizz-launch-readiness/references/08-fusao-priorizacao.md +1 -1
- package/src/skills-lib/wizz-offer-forge/SKILL.md +3 -3
- package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/README.md +1 -1
- package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/SKILL.md +1 -1
- package/src/skills-lib/wizz-router/SKILL.md +3 -3
- package/src/skills-lib/wizz-router/references/routing-table-flat.md +9 -9
- package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/SKILL.md +1 -1
- package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/SKILL.md +1 -1
- package/src/skills-lib/wizz-video-effects/SKILL.md +3 -3
- package/src/skills-lib/wizz-video-effects/references/data-contract.md +3 -3
- package/src/skills-lib/wizz-video-effects/scripts/fill_breaths.py +15 -1
- package/src/skills-lib/wizz-video-effects/scripts/test_fill_breaths.py +20 -0
- package/src/skills-lib/wizz-video-styles/SKILL.md +2 -2
- package/src/skills-lib/wizz-video-styles/data/recipes.json +32 -1
- package/src/skills-lib/wizz-video-styles/data/references.json +211 -1
- package/src/skills-lib/wizz-video-styles/data/styles.json +54 -0
- package/src/skills-lib/wizz-video-styles/references/generated-prompts.md +13 -3
- package/src/skills-lib/wizz-video-styles/references/style-picker.md +4 -2
- package/src/skills-lib/wizz-video-styles/references/styles/ui-morph.md +68 -0
- package/src/skills-lib/wizz-video-styles/scripts/analyze.sh +21 -9
- package/src/skills-lib/wizz-video-styles/scripts/references.mjs +1 -0
- package/src/skills-lib/wizz-web-security/SKILL.md +1 -1
- package/tools/fetch-assets.mjs +1 -1
- package/tools/hooks/README.md +18 -14
- package/tools/hooks/rtk-rewrite.js +95 -0
- package/tools/hooks/security-defensive-context.js +17 -1
- package/tools/hooks/session-rules.js +25 -5
- package/tools/hooks/wizz-arrumar.js +126 -12
- package/tools/hooks/wizz-artifact-guard.js +41 -7
- package/tools/hooks/wizz-cerebro-lib.js +239 -0
- package/tools/hooks/wizz-close-reminder.js +124 -0
- package/tools/hooks/wizz-debug-lib.js +55 -0
- package/tools/hooks/wizz-decision-trace.js +19 -2
- package/tools/hooks/wizz-feature.js +20 -4
- package/tools/hooks/wizz-router-enforce.js +30 -13
- package/tools/installer/command-text.js +243 -0
- package/tools/installer/commands/catalog.js +2 -0
- package/tools/installer/commands/doctor.js +10 -12
- package/tools/installer/commands/fetch-assets.js +5 -5
- package/tools/installer/commands/install.js +6 -7
- package/tools/installer/commands/resolve.js +17 -17
- package/tools/installer/commands/trace-report.js +40 -37
- package/tools/installer/core/doctor.js +60 -55
- package/tools/installer/core/hooks-settings.js +88 -14
- package/tools/installer/core/hooks-smoke.js +134 -0
- package/tools/installer/core/installer.js +61 -52
- package/tools/installer/core/quick-update.js +2 -3
- package/tools/installer/install-text.js +201 -0
- package/tools/installer/modules/channel-plan.js +2 -2
- package/tools/installer/modules/env-vars.js +5 -6
- package/tools/installer/modules/skills-lib.js +1 -1
- package/tools/installer/ui.js +17 -9
- /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-pesquisa-e-memoria.md +0 -0
- /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-rotina-e-captura.md +0 -0
- /package/src/skills-lib/{cerebro → wizz-cerebro}/references/comandos-sessao.md +0 -0
- /package/src/skills-lib/{ctc-align → wizz-ctc-align}/scripts/align.py +0 -0
- /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/README.md +0 -0
- /package/src/skills-lib/{enhance-prompt → wizz-enhance-prompt}/references/KEYWORDS.md +0 -0
- /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/assets/template.html +0 -0
- /package/src/skills-lib/{pesquisa-de-publico-do-piva → wizz-pesquisa-de-publico-do-piva}/prompts.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-1-validacao-entrada.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-2-autorizacao.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-3-abuso-volume.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-4-vazamento-resposta.md +0 -0
- /package/src/skills-lib/{security-audit-pentest → wizz-security-audit-pentest}/references/stage-5-fusao-plano.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/01-cta-principal.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/02-barra-fixa-mobile.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/03-tempo-de-resposta.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/04-prova-social.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/05-faq-decisao.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/06-imagens-reais.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/07-endereco-como-chegar.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/08-titles.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/09-open-graph.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/10-breadcrumbs.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/11-alt-text.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/12-schema-local.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/13-robots-sitemap.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/14-privacidade-termos.md +0 -0
- /package/src/skills-lib/{site-launch-kit → wizz-site-launch-kit}/references/15-medicao.md +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# UI morph: one shape, many UI states (twoclipping, verbove, AnnaCher)
|
|
2
|
+
|
|
3
|
+
Measured on 6 shorts (en) in: dev / AI / build in public (EN), product / SaaS / marketing / business / law.
|
|
4
|
+
|
|
5
|
+
## Measured
|
|
6
|
+
|
|
7
|
+
| Metric | Value |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Reach (views) | max 1,164,424 · median 2,786 |
|
|
10
|
+
| Cuts per 10 s | min 0.0 · median 0.0 · max 0.0 |
|
|
11
|
+
| Median scene length | 17.05 s |
|
|
12
|
+
| Music bed (continuous / faint / none) | 4 / 0 / 2 |
|
|
13
|
+
| Cuts with a sound effect on them | 0/0 |
|
|
14
|
+
|
|
15
|
+
Cuts come from scene detection (it undercounts overlays and cards); music and SFX from an audio heuristic that classifies type, never names the sound ([../research-method.md](../research-method.md)).
|
|
16
|
+
|
|
17
|
+
## Material it needs
|
|
18
|
+
|
|
19
|
+
The product's real screens or a list of 8 to 12 UI states that tell its story (button, loader, player, slider, toggle, tabs, chart, ⌘K, toast), one accent colour and a track around 120 BPM. Nobody on camera, no footage: everything is drawn in code (HyperFrames).
|
|
20
|
+
|
|
21
|
+
## When it fits
|
|
22
|
+
|
|
23
|
+
App and SaaS launches, a feature announcement, a product promo for the site or the store page, a portfolio piece. Strong on X and LinkedIn with a product audience; it shows the product working instead of describing it. Weak for teaching step by step or for anything that needs a face.
|
|
24
|
+
|
|
25
|
+
## Recipe
|
|
26
|
+
|
|
27
|
+
**Look**
|
|
28
|
+
|
|
29
|
+
- One shape on screen the whole time, never cut: it becomes button, loader, player, slider, toggle, tabs, chart, ⌘K and toast, changing size, corners and colour (the same language as the iPhone Dynamic Island).
|
|
30
|
+
- Warm light grey canvas (#EDEBE6), components in black and white, at most one accent colour; a dark variant (navy with one yellow) for a product film.
|
|
31
|
+
- Real UI and real data: the product's own labels and numbers, never lorem ipsum or generic components.
|
|
32
|
+
- A cursor drives every change with real clicks and drags; the camera zooms so each state fills the frame.
|
|
33
|
+
|
|
34
|
+
**Palette:** Warm grey #EDEBE6 or beige #E6E2DA, black #111, white, plus ONE accent (orange, peach or yellow) used only where the eye must go. Dark variant: #0B1020 base, one yellow accent.
|
|
35
|
+
|
|
36
|
+
**Typography:** One UI sans (Inter, Geist or the product's own font) in regular and semibold; mono only for a URL or a code line. No display fonts.
|
|
37
|
+
|
|
38
|
+
**Motion**
|
|
39
|
+
|
|
40
|
+
- Every state grows out of the previous one by a spring (size, corner radius, colour), with at most one small overshoot; content swaps with a short blur.
|
|
41
|
+
- Something happens on every beat; the state changes land on the downbeats of the beat grid (scripts/beat_grid.py), not on a hand-counted 120 BPM.
|
|
42
|
+
- Build it as a pure function of time (seek(t)): closed-form springs, no CSS transitions or timers, so every frame renders the same. HyperFrames does this natively.
|
|
43
|
+
- The last frame equals the first, so the piece loops.
|
|
44
|
+
|
|
45
|
+
**Captions:** None. The text is the interface's own (button labels, track title, chart numbers). A single short line at most, in the UI font, when the product needs one sentence.
|
|
46
|
+
|
|
47
|
+
**Hook (0 to 3 s):** The first state already on screen at 0 s (a button or the brand pill) and the cursor arriving on the first beat. No logo intro, no title card.
|
|
48
|
+
|
|
49
|
+
**CTA:** The shape returns to the first state or closes on the brand pill with the URL in small mono under it, held 1 to 2 s.
|
|
50
|
+
|
|
51
|
+
**Music:** The track is the whole soundtrack: a continuous bed in 4 of 6 measured pieces, around -14 LUFS. Pick an electronic or lo-fi track with a clear kick so the beat grid is reliable.
|
|
52
|
+
|
|
53
|
+
**SFX:** Sparse and tied to UI events: a soft click on a press, a low tick on a snap. The measured pieces range from none to a hit on most state changes; never whooshes on every move.
|
|
54
|
+
|
|
55
|
+
## Do not
|
|
56
|
+
|
|
57
|
+
- Cutting between screens: the moment it cuts it becomes a screen recording, not this style.
|
|
58
|
+
- Bouncy easing, particles, glow, gradients on the UI chrome, icons with uneven stroke, dead time, template look.
|
|
59
|
+
- Generic components instead of the product's real UI and numbers.
|
|
60
|
+
- Shipping square or 16:9: every measured piece is 1:1 or 16:9; recompose for 9:16 with the shape in the upper-middle and nothing in the bottom 20 %.
|
|
61
|
+
|
|
62
|
+
## Watch before editing
|
|
63
|
+
|
|
64
|
+
- [zero](https://x.com/twoclipping/status/2103273003555402193) · 1,164,424 views
|
|
65
|
+
- [Martijn Verbove](https://x.com/verbove/status/2103483957266268381) · 17,438 views
|
|
66
|
+
- [Daniel Haida](https://x.com/daniel_haida/status/2104139720829636937) · 3,968 views
|
|
67
|
+
- [Anna Chernikova](https://x.com/AnnaCher___/status/2103571096549433425) · 1,605 views
|
|
68
|
+
- [Nez](https://x.com/nezbuilds/status/2103487427365294391) · 519 views
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Measures one public
|
|
2
|
+
# Measures one public short: download (720p), cuts (scene detection),
|
|
3
3
|
# audio (music bed, SFX type heuristic, loudness) and a 16-frame contact sheet.
|
|
4
|
-
# Usage: analyze.sh <youtube_id> <out_dir> → <out_dir>/<id>.json + <id>-sheet.png
|
|
4
|
+
# Usage: analyze.sh <youtube_id|url> <out_dir> → <out_dir>/<id>.json + <id>-sheet.png
|
|
5
|
+
# A URL (an X post, any page yt-dlp reads) is measured the same way; its id is
|
|
6
|
+
# x-<author>-<status> for X and a sanitised slug otherwise (2026-10-15: the
|
|
7
|
+
# code-generated UI pieces of the ui-morph style live on X, not on YouTube).
|
|
5
8
|
# The .mp4 is deleted at the end; read the contact sheet before describing the look.
|
|
6
9
|
set -euo pipefail
|
|
7
|
-
[ $# -eq 2 ] || { echo "uso: analyze.sh <youtube_id> <out_dir>" >&2; exit 2; }
|
|
10
|
+
[ $# -eq 2 ] || { echo "uso: analyze.sh <youtube_id|url> <out_dir>" >&2; exit 2; }
|
|
8
11
|
id="$1"; out="$2"
|
|
9
|
-
[[ "$id" =~ ^
|
|
12
|
+
if [[ "$id" =~ ^https?:// ]]; then
|
|
13
|
+
url="$id"
|
|
14
|
+
if [[ "$url" =~ (x|twitter)\.com/([A-Za-z0-9_]+)/status/([0-9]+) ]]; then
|
|
15
|
+
id="x-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}"
|
|
16
|
+
else
|
|
17
|
+
id=$(printf '%s' "$url" | sed -E 's#^https?://##; s#[^A-Za-z0-9_-]+#-#g' | cut -c1-60)
|
|
18
|
+
fi
|
|
19
|
+
else
|
|
20
|
+
[[ "$id" =~ ^[A-Za-z0-9_-]{11}$ ]] || { echo "id inválido: $id" >&2; exit 2; }
|
|
21
|
+
url="https://www.youtube.com/shorts/$id"
|
|
22
|
+
fi
|
|
10
23
|
mkdir -p "$out"; cd "$out"
|
|
11
24
|
f="./$id" # prefixo ./ : ID que começa com hífen não vira opção do ffmpeg
|
|
12
25
|
# .part: a first download that fails (HTTP 403) leaves it behind before the android fallback runs.
|
|
@@ -15,12 +28,11 @@ trap 'rm -f "$f.wav" "$f.mp4" "$f".*.part' EXIT
|
|
|
15
28
|
PY="${VIDEO_STYLES_PY:-python3}"
|
|
16
29
|
"$PY" -c "import librosa, numpy" 2>/dev/null || { echo "precisa de librosa e numpy: pip install librosa numpy (ou VIDEO_STYLES_PY=/caminho/do/python)" >&2; exit 2; }
|
|
17
30
|
for bin in yt-dlp ffmpeg ffprobe; do command -v "$bin" >/dev/null || { echo "falta $bin no PATH" >&2; exit 2; }; done
|
|
18
|
-
url="https://www.youtube.com/shorts/$id"
|
|
19
31
|
fmt="bv*[height<=1280][ext=mp4]+ba[ext=m4a]/b[ext=mp4]/b"
|
|
20
32
|
# When YouTube answers "confirm you're not a bot", the android player client usually still works (no cookies used).
|
|
21
33
|
[ -f "$f.mp4" ] || yt-dlp -q --no-warnings -f "$fmt" --merge-output-format mp4 -o "$f.mp4" "$url" \
|
|
22
34
|
|| yt-dlp -q --no-warnings --extractor-args "youtube:player_client=android" -f "$fmt" --merge-output-format mp4 -o "$f.mp4" "$url"
|
|
23
|
-
meta_fmt="%(view_count)s|%(like_count)s|%(channel)s|%(upload_date)s|%(title)s"
|
|
35
|
+
meta_fmt="%(view_count)s|%(like_count)s|%(channel,uploader)s|%(upload_date)s|%(title)s"
|
|
24
36
|
yt-dlp -q --no-warnings --skip-download --print "$meta_fmt" "$url" > "$f.meta" \
|
|
25
37
|
|| yt-dlp -q --no-warnings --skip-download --extractor-args "youtube:player_client=android" --print "$meta_fmt" "$url" > "$f.meta" \
|
|
26
38
|
|| echo "NA|NA|NA|NA|NA" > "$f.meta"
|
|
@@ -32,9 +44,9 @@ lufs=$(ffmpeg -i "$f.mp4" -af ebur128 -f null - 2>&1 | grep -E "^\s+I:" | tail -
|
|
|
32
44
|
d=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$f.mp4")
|
|
33
45
|
step=$(python3 -c "print(max(1.0, $d/16))")
|
|
34
46
|
ffmpeg -loglevel error -y -i "$f.mp4" -vf "fps=1/$step,scale=200:-1,tile=8x2" -frames:v 1 "$f-sheet.png"
|
|
35
|
-
"$PY" - "$id" "$lufs" <<'PY'
|
|
47
|
+
"$PY" - "$id" "$lufs" "$url" <<'PY'
|
|
36
48
|
import librosa, numpy as np, json, sys
|
|
37
|
-
id, lufs = sys.argv[1], sys.argv[2]
|
|
49
|
+
id, lufs, url = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
38
50
|
y, sr = librosa.load(f"{id}.wav", sr=22050); dur = len(y)/sr
|
|
39
51
|
cuts = [float(x) for x in open(f"{id}.cuts").read().split()]
|
|
40
52
|
H, P = librosa.effects.hpss(y, margin=2.0)
|
|
@@ -58,7 +70,7 @@ on_cut = sum(1 for c in cuts if any(abs(t-c) < 0.15 for t,_ in events))
|
|
|
58
70
|
shots = np.diff([0]+cuts+[dur]) if dur else np.array([0])
|
|
59
71
|
bed = float(np.percentile(rh,15)/(np.percentile(rh,95)+1e-9))
|
|
60
72
|
meta = open(f"{id}.meta").read().strip().split("|", 4)
|
|
61
|
-
json.dump({"id": id, "url":
|
|
73
|
+
json.dump({"id": id, "url": url, "views": meta[0], "likes": meta[1], "channel": meta[2], "upload_date": meta[3], "title": meta[4],
|
|
62
74
|
"duration_s": round(dur,1), "cuts": len(cuts), "cuts_per_10s": round(len(cuts)/dur*10,2), "shot_median_s": round(float(np.median(shots)),2), "shot_mean_s": round(float(shots.mean()),2),
|
|
63
75
|
"music_bed_ratio": round(bed,3), "music_bed_guess": "continua" if bed > 0.13 else ("fraca" if bed > 0.07 else "sem"),
|
|
64
76
|
"sfx_counts": kinds, "sfx_per_10s": round(sum(kinds.values())/dur*10,2), "sfx_on_cuts": f"{on_cut}/{len(cuts)}", "sfx_events": events[:40],
|
|
@@ -31,6 +31,7 @@ const HISTORY_REL = path.join('_wizz-output', 'video-styles', 'historico.json');
|
|
|
31
31
|
// tour, macro...) have no entry: a code-generated video is not a reference for them.
|
|
32
32
|
const GENERATED = {
|
|
33
33
|
'tipografia-cinetica': { categories: ['motion', 'explainer', '3d'], match: /lyric|music video|kinetic typograph|\bMV\b/i },
|
|
34
|
+
'ui-morph': { categories: ['motion', 'explainer', '3d', 'interactive'], match: /UI states|one shape|morph|UI moments|product film|inside the app/i },
|
|
34
35
|
'motion-editorial': { categories: ['motion', 'explainer'], match: /\bUI\b|product|launch|saas|\bapp\b|logo|promo|brand/i },
|
|
35
36
|
'explicador-animado': { categories: ['explainer'], match: /./ },
|
|
36
37
|
'quadro-branco': { categories: ['explainer', 'motion'], match: /whiteboard|hand-drawn|sketch|doodle/i },
|
|
@@ -32,7 +32,7 @@ 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
|
-
> For a full adversarial audit (hunting with exploit proof + a prioritized plan), use the `security-audit-pentest` skill. This skill here is for **fixing** a specific finding from the table.
|
|
35
|
+
> 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
36
|
|
|
37
37
|
## References (load on demand)
|
|
38
38
|
|
package/tools/fetch-assets.mjs
CHANGED
|
@@ -39,7 +39,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
39
39
|
const __dirname = path.dirname(__filename);
|
|
40
40
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
41
41
|
|
|
42
|
-
export const ASSETS_BASE = 'https://
|
|
42
|
+
export const ASSETS_BASE = 'https://assets.wizzcomms.com';
|
|
43
43
|
|
|
44
44
|
// Fonte única dos bundles conhecidos. `dest` é relativo ao root do pacote
|
|
45
45
|
// (repo em dev, node_modules/wizz-method quando instalado via npm — a mesma
|
package/tools/hooks/README.md
CHANGED
|
@@ -4,29 +4,33 @@ Os hooks do Claude Code usados pelo Wizz Method vivem AQUI e são instalados em
|
|
|
4
4
|
`~/.claude/hooks/` via `npm run sync:global`. Nunca edite a cópia global direto:
|
|
5
5
|
ela é substituída no próximo sync.
|
|
6
6
|
|
|
7
|
-
| Hook | Evento
|
|
8
|
-
| ------------------------------- |
|
|
9
|
-
| `wizz-router-enforce.js` | UserPromptSubmit
|
|
10
|
-
| `session-rules.js` | SessionStart
|
|
11
|
-
| `security-defensive-context.js` | PreToolUse (Skill)
|
|
12
|
-
| `wizz-artifact-guard.js` | PostToolUse (Write) | 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
|
-
| `
|
|
7
|
+
| Hook | Evento | O que faz |
|
|
8
|
+
| ------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `wizz-router-enforce.js` | UserPromptSubmit | Gate `isTrivial()` + injeção curta: em projeto Wizz (`_wizz/`) manda delegar direto ao maestro/agente da área (sem router); fora, manda pro `wizz-router`. |
|
|
10
|
+
| `session-rules.js` | SessionStart | Duas coisas num processo só: injeta as regras de comunicação 1x por sessão (antes era por prompt) e resolve a FEATURE ATIVA sozinho (branch git > `_wizz/bmm/config.yaml` > nada), para ninguém precisar invocar `wizz-set-feature`. Branch de tronco e de release não viram feature. Fora de projeto Wizz e sem branch de feature a linha não é emitida (custo zero); dentro de projeto Wizz sem feature sai o lembrete curto "o primeiro pedido nomeado define uma", e sem `layout:` o lembrete de propor um. A feature também é o `tema` da memória. |
|
|
11
|
+
| `security-defensive-context.js` | PreToolUse (Skill) | Metodologia defensiva/escopada ao invocar skills de segurança. Genérico por projeto (v2). |
|
|
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
|
+
| `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
|
+
| `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. |
|
|
14
16
|
|
|
15
17
|
Arquivos de apoio (copiados junto, NUNCA registrados em settings.json; o doctor cobra a presença):
|
|
16
18
|
|
|
17
|
-
| Arquivo
|
|
18
|
-
|
|
|
19
|
-
| `wizz-feature-lib.js` | Funções puras de feature, git, config, pastas de artefato e frontmatter. Usada por todos os hooks acima.
|
|
20
|
-
| `wizz-layout-lib.js`
|
|
21
|
-
| `wizz-
|
|
22
|
-
| `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-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. |
|
|
26
|
+
| `wizz-arrumar.js` | CLI de arrumação: `layout [--gravar]`, `planejar`, `aplicar` (manifesto .tsv + referências + testes antes/depois + `desfazer.sh`), `mover`, `desfazer`. |
|
|
23
27
|
|
|
24
28
|
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.
|
|
25
29
|
|
|
26
30
|
Removidos em 2026-07 (arquivados em `~/.claude/hooks-archive/`):
|
|
27
31
|
`no-narration-enforce.js` (absorvido pelo session-rules) e `vault-reminder.js`.
|
|
28
32
|
|
|
29
|
-
Testes: `npm run test:organizacao` (CLIs de feature e arrumação, guard, lembretes nos cenários sem git / main / branch), `npm run eval:feature` (hooks reais + CLI + estado final; `--llm` mede a obediência do modelo), `npm run test:hooks` (isTrivial + contexto por modo), `npm run test:feature-context` (resolução da feature ativa: tronco não vira feature, branch ganha do config, silêncio quando não há feature) e `node test/test-rtk-hook.js` (rewrite básico + multi-linha do `rtk-rewrite.sh`, smoke test que exercita o binário `rtk` real quando disponível, e o cache do version-guard: criado na 1ª chamada e reusado na 2ª, TTL expirado força recompute, path do binário mudou invalida, cache corrompido não quebra e se auto-cura).
|
|
33
|
+
Testes: `node test/test-feature-close.js` (`encerrar --cerebro` e o lembrete do Stop), `npm run test:organizacao` (CLIs de feature e arrumação, guard, lembretes nos cenários sem git / main / branch), `npm run eval:feature` (hooks reais + CLI + estado final; `--llm` mede a obediência do modelo), `npm run test:hooks` (isTrivial + contexto por modo), `npm run test:feature-context` (resolução da feature ativa: tronco não vira feature, branch ganha do config, silêncio quando não há feature) e `node test/test-rtk-hook.js` (rewrite básico + multi-linha do `rtk-rewrite.sh`, smoke test que exercita o binário `rtk` real quando disponível, e o cache do version-guard: criado na 1ª chamada e reusado na 2ª, TTL expirado força recompute, path do binário mudou invalida, cache corrompido não quebra e se auto-cura).
|
|
30
34
|
Evals de roteamento: `node evals/routing/run-routing-eval.mjs [--llm]`.
|
|
31
35
|
|
|
32
36
|
Drift repo ↔ global: `npm run sync:check` compara hash de cada hook (e skill) entre o repo e `~/.claude`, sem escrever nada — é lembrete (fail-open), não gate; roda no fim do `npm test`. Achou drift? `npm run sync:global` atualiza `~/.claude`.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// rtk-rewrite.js — PreToolUse (Bash): reescreve o comando via `rtk rewrite`.
|
|
2
|
+
//
|
|
3
|
+
// Versão Node do rtk-rewrite.sh para o Windows (2026-10-15), onde não há bash
|
|
4
|
+
// nem jq garantidos. O installer registra este arquivo em win32 e o .sh nos
|
|
5
|
+
// outros sistemas (hooks-settings.js), então o comportamento tem de ser o
|
|
6
|
+
// mesmo; test/test-rtk-hook-node.js roda os dois contra o mesmo rtk fake.
|
|
7
|
+
//
|
|
8
|
+
// Mesmo contrato do .sh:
|
|
9
|
+
// - sem rtk no PATH, ou rtk abaixo de 0.23.0: sai 0 sem tocar o comando;
|
|
10
|
+
// - comando multi-linha com heredoc, continuação `\` ou aspas abertas numa
|
|
11
|
+
// linha passa intacto (reescrever corromperia o corpo do heredoc);
|
|
12
|
+
// - cada linha vai sozinha para `rtk rewrite`, com timeout (RTK_HOOK_TIMEOUT,
|
|
13
|
+
// 3 s); exit 0 ou 3 com saída = linha nova, qualquer outra coisa = original;
|
|
14
|
+
// - nada mudou: stdout vazio. Mudou: hookSpecificOutput com updatedInput.
|
|
15
|
+
// Fail-open em tudo: erro aqui nunca bloqueia o Bash.
|
|
16
|
+
//
|
|
17
|
+
// Sem o cache de versão do .sh: `rtk --version` de um binário nativo custa
|
|
18
|
+
// poucos ms, e no Windows não há `command -v` para chavear o cache pelo path.
|
|
19
|
+
|
|
20
|
+
const { spawnSync } = require('node:child_process');
|
|
21
|
+
|
|
22
|
+
const TIMEOUT_MS = (Number(process.env.RTK_HOOK_TIMEOUT) || 3) * 1000;
|
|
23
|
+
const FLOOR = [0, 23];
|
|
24
|
+
|
|
25
|
+
function rtkVersionOk() {
|
|
26
|
+
const res = spawnSync('rtk', ['--version'], { encoding: 'utf8', timeout: TIMEOUT_MS, windowsHide: true });
|
|
27
|
+
if (res.error || res.status !== 0) {
|
|
28
|
+
process.stderr.write('[rtk] WARNING: rtk is not installed or not in PATH. Hook cannot rewrite commands.\n');
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const match = /v?(\d+)\.(\d+)\.(\d+)/.exec(res.stdout || '');
|
|
32
|
+
if (!match) return true;
|
|
33
|
+
const [major, minor] = [Number(match[1]), Number(match[2])];
|
|
34
|
+
if (major === FLOOR[0] && minor < FLOOR[1]) {
|
|
35
|
+
process.stderr.write(
|
|
36
|
+
`[rtk] WARNING: rtk ${match[1]}.${match[2]}.${match[3]} is too old (need >= 0.23.0). Upgrade: cargo install rtk\n`,
|
|
37
|
+
);
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Heredoc, continuação ou aspas desbalanceadas numa linha: não dá para
|
|
44
|
+
// reescrever linha a linha sem quebrar o comando.
|
|
45
|
+
function multilineUnsafe(cmd) {
|
|
46
|
+
if (cmd.includes('<<')) return true;
|
|
47
|
+
return cmd.split('\n').some((line) => {
|
|
48
|
+
if (line.endsWith('\\')) return true;
|
|
49
|
+
const count = (ch) => line.split(ch).length - 1;
|
|
50
|
+
return count('"') % 2 !== 0 || count("'") % 2 !== 0;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function rewriteOne(line) {
|
|
55
|
+
if (!line) return line;
|
|
56
|
+
const res = spawnSync('rtk', ['rewrite', line], { encoding: 'utf8', timeout: TIMEOUT_MS, windowsHide: true });
|
|
57
|
+
// Como o $(...) do .sh: a saída perde só as quebras de linha do fim.
|
|
58
|
+
const out = (res.stdout || '').replace(/\n+$/, '');
|
|
59
|
+
return !res.error && (res.status === 0 || res.status === 3) && out ? out : line;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function main(raw) {
|
|
63
|
+
const input = JSON.parse(raw);
|
|
64
|
+
const toolInput = input && typeof input.tool_input === 'object' && input.tool_input ? input.tool_input : {};
|
|
65
|
+
const cmd = typeof toolInput.command === 'string' ? toolInput.command : '';
|
|
66
|
+
if (!cmd) return;
|
|
67
|
+
if (cmd.includes('\n') && multilineUnsafe(cmd)) return;
|
|
68
|
+
if (!rtkVersionOk()) return;
|
|
69
|
+
|
|
70
|
+
const rewritten = cmd.includes('\n') ? cmd.split('\n').map(rewriteOne).join('\n') : rewriteOne(cmd);
|
|
71
|
+
if (rewritten === cmd) return;
|
|
72
|
+
|
|
73
|
+
process.stdout.write(
|
|
74
|
+
JSON.stringify({
|
|
75
|
+
hookSpecificOutput: {
|
|
76
|
+
hookEventName: 'PreToolUse',
|
|
77
|
+
permissionDecision: 'allow',
|
|
78
|
+
permissionDecisionReason: 'RTK auto-rewrite',
|
|
79
|
+
updatedInput: { ...toolInput, command: rewritten },
|
|
80
|
+
},
|
|
81
|
+
}) + '\n',
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let raw = '';
|
|
86
|
+
process.stdin.setEncoding('utf8');
|
|
87
|
+
process.stdin.on('data', (chunk) => (raw += chunk));
|
|
88
|
+
process.stdin.on('end', () => {
|
|
89
|
+
try {
|
|
90
|
+
main(raw);
|
|
91
|
+
} catch {
|
|
92
|
+
// fail-open: JSON quebrado ou erro inesperado deixa o comando como veio
|
|
93
|
+
}
|
|
94
|
+
process.exit(0);
|
|
95
|
+
});
|
|
@@ -28,6 +28,21 @@ const SECURITY_SKILLS = new Set([
|
|
|
28
28
|
'code-review:code-review',
|
|
29
29
|
]);
|
|
30
30
|
|
|
31
|
+
// Diagnóstico opt-in (WIZZ_DEBUG=1). Lib ausente numa cópia parcial: ainda avisa em stderr.
|
|
32
|
+
let debug = (hook, phase, err) => {
|
|
33
|
+
if (process.env.WIZZ_DEBUG !== '1') return;
|
|
34
|
+
try {
|
|
35
|
+
process.stderr.write(`[wizz-debug] ${hook} ${phase}: ${(err && err.message) || err}\n`);
|
|
36
|
+
} catch {
|
|
37
|
+
// stderr fechado
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
({ debug } = require('./wizz-debug-lib.js'));
|
|
42
|
+
} catch (error) {
|
|
43
|
+
debug('security-defensive-context', 'require wizz-debug-lib', error);
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
const DEFENSIVE_CONTEXT =
|
|
32
47
|
'Metodologia de revisao de seguranca (defensiva, escopada):\n\n' +
|
|
33
48
|
'1. ESCOPO: trabalhe por arquivo ou dimensao concreta de cada vez (ex: validacao ' +
|
|
@@ -63,8 +78,9 @@ process.stdin.on('end', () => {
|
|
|
63
78
|
);
|
|
64
79
|
}
|
|
65
80
|
// Para qualquer outra skill/tool: sai silenciosamente sem output
|
|
66
|
-
} catch {
|
|
81
|
+
} catch (error) {
|
|
67
82
|
// Parse error: sai silenciosamente, nao bloqueia nada
|
|
83
|
+
debug('security-defensive-context', 'parse payload', error);
|
|
68
84
|
}
|
|
69
85
|
process.exit(0);
|
|
70
86
|
});
|
|
@@ -45,11 +45,27 @@ const RULES =
|
|
|
45
45
|
// Feature, git e config moram em wizz-feature-lib.js (mesma pasta: o installer
|
|
46
46
|
// e o sync:global copiam tools/hooks/ inteiro). Se a lib faltar numa cópia
|
|
47
47
|
// manual parcial, a regra de comunicação continua saindo e a feature cala.
|
|
48
|
+
// Diagnóstico opt-in (WIZZ_DEBUG=1). Lib ausente numa cópia parcial: ainda avisa em stderr.
|
|
49
|
+
let debug = (hook, phase, err) => {
|
|
50
|
+
if (process.env.WIZZ_DEBUG !== '1') return;
|
|
51
|
+
try {
|
|
52
|
+
process.stderr.write(`[wizz-debug] ${hook} ${phase}: ${(err && err.message) || err}\n`);
|
|
53
|
+
} catch {
|
|
54
|
+
// stderr fechado
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
({ debug } = require('./wizz-debug-lib.js'));
|
|
59
|
+
} catch (error) {
|
|
60
|
+
debug('session-rules', 'require wizz-debug-lib', error);
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
let lib = null;
|
|
49
64
|
try {
|
|
50
65
|
lib = require('./wizz-feature-lib.js');
|
|
51
|
-
} catch {
|
|
66
|
+
} catch (error) {
|
|
52
67
|
lib = null;
|
|
68
|
+
debug('session-rules', 'require wizz-feature-lib', error);
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
const HOOK_CMD = 'node _wizz/hooks/wizz-feature.js';
|
|
@@ -246,22 +262,25 @@ function finish(raw) {
|
|
|
246
262
|
try {
|
|
247
263
|
const payload = JSON.parse(raw || '{}');
|
|
248
264
|
if (payload && typeof payload.cwd === 'string' && payload.cwd) cwd = payload.cwd;
|
|
249
|
-
} catch {
|
|
265
|
+
} catch (error) {
|
|
250
266
|
// payload ausente ou inválido: cai no cwd do processo
|
|
267
|
+
debug('session-rules', 'parse payload', error);
|
|
251
268
|
}
|
|
252
269
|
|
|
253
270
|
try {
|
|
254
271
|
const feature = buildFeatureContext(cwd);
|
|
255
272
|
if (feature) context += `\n\n${feature}`;
|
|
256
|
-
} catch {
|
|
273
|
+
} catch (error) {
|
|
257
274
|
// resolução de feature NUNCA derruba as regras de comunicação
|
|
275
|
+
debug('session-rules', 'buildFeatureContext', error);
|
|
258
276
|
}
|
|
259
277
|
|
|
260
278
|
try {
|
|
261
279
|
const ceiling = buildMemoryCeilingContext(cwd);
|
|
262
280
|
if (ceiling) context += `\n\n${ceiling}`;
|
|
263
|
-
} catch {
|
|
281
|
+
} catch (error) {
|
|
264
282
|
// teto da memória é aviso, não gate: erro aqui não afeta o resto
|
|
283
|
+
debug('session-rules', 'buildMemoryCeilingContext', error);
|
|
265
284
|
}
|
|
266
285
|
|
|
267
286
|
try {
|
|
@@ -273,8 +292,9 @@ function finish(raw) {
|
|
|
273
292
|
},
|
|
274
293
|
}),
|
|
275
294
|
);
|
|
276
|
-
} catch {
|
|
295
|
+
} catch (error) {
|
|
277
296
|
// nunca bloquear
|
|
297
|
+
debug('session-rules', 'write stdout', error);
|
|
278
298
|
}
|
|
279
299
|
process.exit(0);
|
|
280
300
|
}
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
// mover <origem> <destino> atalho de um arquivo (manifesto de uma linha + aplicar)
|
|
15
15
|
// desfazer <pasta-da-arrumacao>
|
|
16
16
|
//
|
|
17
|
-
// Opções de aplicar/mover: --testes "<comando>"
|
|
18
|
-
//
|
|
17
|
+
// Opções de aplicar/mover: --testes "<comando>" (só runner conhecido: npm/pnpm/
|
|
18
|
+
// yarn/bun test, npx vitest|jest, pytest, go/cargo/make test; sem valor = script
|
|
19
|
+
// test do package.json; outro comando exige --testes-livre), --refs <dir,dir>
|
|
20
|
+
// (ex.: pasta da auto-memória), --manter (não desfaz sozinho se os testes quebrarem).
|
|
19
21
|
// Cada execução fica em `<arquivo>/arrumacao/<data>-<hora>/`.
|
|
20
22
|
|
|
21
23
|
const fs = require('node:fs');
|
|
@@ -76,7 +78,7 @@ function parseArgs(argv) {
|
|
|
76
78
|
if (a.startsWith('--')) {
|
|
77
79
|
const key = a.slice(2);
|
|
78
80
|
const next = argv[i + 1];
|
|
79
|
-
if (next !== undefined && !next.startsWith('--')) {
|
|
81
|
+
if (key !== 'testes-livre' && next !== undefined && !next.startsWith('--')) {
|
|
80
82
|
opts[key] = next;
|
|
81
83
|
i++;
|
|
82
84
|
} else opts[key] = true;
|
|
@@ -237,17 +239,116 @@ function rewriteRefs(text, moves, fileDir) {
|
|
|
237
239
|
|
|
238
240
|
// ── aplicar / desfazer ──────────────────────────────────────────────────
|
|
239
241
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
// ── comando de testes: allowlist, sem shell ─────────────────────────────
|
|
243
|
+
//
|
|
244
|
+
// `--testes` não executa string arbitrária: o comando é cortado em argv (sem
|
|
245
|
+
// shell) e só roda se for de um runner conhecido. `--testes` sem valor usa o
|
|
246
|
+
// script `test` do package.json. Qualquer outra coisa exige `--testes-livre`,
|
|
247
|
+
// que imprime o que vai rodar e usa o shell (o dono assumiu o comando).
|
|
248
|
+
|
|
249
|
+
const SHELL_META = /[;&|<>$`()\n\r\\]/;
|
|
250
|
+
const PM = new Set(['npm', 'pnpm', 'yarn', 'bun']);
|
|
251
|
+
const TEST_SCRIPT = /^test(?:[:\w.-]*)$/;
|
|
252
|
+
|
|
253
|
+
/** Corta em argv respeitando aspas simples/duplas. null = não dá para cortar com segurança. */
|
|
254
|
+
function splitArgv(text) {
|
|
255
|
+
if (SHELL_META.test(text)) return null;
|
|
256
|
+
const argv = [];
|
|
257
|
+
let cur = '';
|
|
258
|
+
let quote = '';
|
|
259
|
+
let has = false;
|
|
260
|
+
for (const ch of text) {
|
|
261
|
+
if (quote) {
|
|
262
|
+
if (ch === quote) quote = '';
|
|
263
|
+
else cur += ch;
|
|
264
|
+
} else if (ch === '"' || ch === "'") {
|
|
265
|
+
quote = ch;
|
|
266
|
+
has = true;
|
|
267
|
+
} else if (/\s/.test(ch)) {
|
|
268
|
+
if (has || cur) argv.push(cur);
|
|
269
|
+
cur = '';
|
|
270
|
+
has = false;
|
|
271
|
+
} else {
|
|
272
|
+
cur += ch;
|
|
273
|
+
has = true;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (quote) return null;
|
|
277
|
+
if (has || cur) argv.push(cur);
|
|
278
|
+
return argv;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** O argv é de um runner de testes conhecido? */
|
|
282
|
+
function isAllowedTestArgv(argv) {
|
|
283
|
+
const [bin, a, b] = argv;
|
|
284
|
+
if (!bin) return false;
|
|
285
|
+
if (PM.has(bin)) return a === 'test' || (a === 'run' && TEST_SCRIPT.test(b || ''));
|
|
286
|
+
if (bin === 'npx') return a === 'vitest' || a === 'jest';
|
|
287
|
+
if (bin === 'pytest') return true;
|
|
288
|
+
if (bin === 'python' || bin === 'python3') return a === '-m' && (b === 'pytest' || b === 'unittest');
|
|
289
|
+
if (bin === 'go' || bin === 'cargo' || bin === 'make') return a === 'test';
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Resolve o que `--testes` vai rodar.
|
|
295
|
+
* @returns {{none: true}|{argv: string[], shell: false, display: string}|{shellCmd: string, shell: true, display: string}|{error: string}}
|
|
296
|
+
*/
|
|
297
|
+
function resolveTestCommand(root, opts) {
|
|
298
|
+
const raw = opts.testes;
|
|
299
|
+
if (!raw) return { none: true };
|
|
300
|
+
let text = raw;
|
|
301
|
+
if (raw === true) {
|
|
302
|
+
try {
|
|
303
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
304
|
+
if (!pkg.scripts || typeof pkg.scripts.test !== 'string') throw new Error('sem script test');
|
|
305
|
+
} catch {
|
|
306
|
+
return {
|
|
307
|
+
error: '--testes sem comando pede o script "test" do package.json, e ele não existe aqui. Passe o comando ou use --testes-livre.',
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
text = 'npm test';
|
|
311
|
+
}
|
|
312
|
+
const argv = splitArgv(text);
|
|
313
|
+
if (argv && isAllowedTestArgv(argv)) return { argv, shell: false, display: argv.join(' ') };
|
|
314
|
+
if (opts['testes-livre']) return { shellCmd: text, shell: true, display: text };
|
|
315
|
+
return {
|
|
316
|
+
error:
|
|
317
|
+
`Comando de testes fora da lista: "${text}". Aceitos: npm/pnpm/yarn/bun test ou run test*, npx vitest|jest, pytest, ` +
|
|
318
|
+
'python -m pytest|unittest, go test, cargo test, make test (sem ; & | < > $ ` ou parênteses). ' +
|
|
319
|
+
'Para rodar outro comando, repita com --testes-livre.',
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function runTests(root, resolved) {
|
|
324
|
+
if (!resolved || resolved.none) return null;
|
|
325
|
+
process.stderr.write(`Rodando testes${resolved.shell ? ' (livre, via shell)' : ''}: ${resolved.display}\n`);
|
|
326
|
+
const timeout = 10 * 60 * 1000;
|
|
327
|
+
const r = resolved.shell
|
|
328
|
+
? spawnSync(resolved.shellCmd, { cwd: root, shell: true, encoding: 'utf8', timeout })
|
|
329
|
+
: spawnSync(resolved.argv[0], resolved.argv.slice(1), { cwd: root, shell: false, encoding: 'utf8', timeout });
|
|
330
|
+
const out = `${r.stdout || ''}${r.stderr || ''}${r.error ? r.error.message : ''}`;
|
|
331
|
+
return { ok: r.status === 0, code: r.status, tail: out.trim().split('\n').slice(-5).join('\n') };
|
|
244
332
|
}
|
|
245
333
|
|
|
246
334
|
function shQuote(s) {
|
|
247
335
|
return `'${String(s).replaceAll("'", String.raw`'\''`)}'`;
|
|
248
336
|
}
|
|
249
337
|
|
|
250
|
-
|
|
338
|
+
// Desfazer em qualquer sistema: `node wizz-arrumar.js desfazer <pasta>` (Node
|
|
339
|
+
// puro, função undo abaixo). O script ao lado é atalho: desfazer.sh fora do
|
|
340
|
+
// Windows; no Windows, sem bash garantido, um desfazer.cmd que chama o Node.
|
|
341
|
+
function undoCommand(root, runDir) {
|
|
342
|
+
const q = (p) => `"${p}"`;
|
|
343
|
+
return `node ${q(__filename)} desfazer ${q(path.relative(root, runDir))}`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function writeUndoScript(runDir, root, moves, edits, platform = process.platform) {
|
|
347
|
+
if (platform === 'win32') {
|
|
348
|
+
const file = path.join(runDir, 'desfazer.cmd');
|
|
349
|
+
fs.writeFileSync(file, ['@echo off', `cd /d "${root}"`, undoCommand(root, runDir), ''].join('\r\n'));
|
|
350
|
+
return file;
|
|
351
|
+
}
|
|
251
352
|
const lines = [
|
|
252
353
|
'#!/usr/bin/env bash',
|
|
253
354
|
`# Desfaz a arrumação de ${path.basename(runDir)}: restaura os arquivos editados e devolve os moves.`,
|
|
@@ -305,12 +406,14 @@ function applyMoves(root, moves, opts) {
|
|
|
305
406
|
const config = lib.readConfig(root);
|
|
306
407
|
const problems = validateMoves(root, moves);
|
|
307
408
|
if (problems.length > 0) return { ok: false, code: 2, line: `Nada foi movido: ${problems.join('; ')}.` };
|
|
409
|
+
const testCommand = resolveTestCommand(root, opts);
|
|
410
|
+
if (testCommand.error) return { ok: false, code: 2, line: `Nada foi movido: ${testCommand.error}` };
|
|
308
411
|
|
|
309
412
|
const runDir = path.join(archiveDir(root, config.layout), stamp());
|
|
310
413
|
fs.mkdirSync(runDir, { recursive: true });
|
|
311
414
|
writeManifest(path.join(runDir, 'manifesto.tsv'), moves);
|
|
312
415
|
|
|
313
|
-
const before = runTests(root,
|
|
416
|
+
const before = runTests(root, testCommand);
|
|
314
417
|
|
|
315
418
|
// Referências: projeto inteiro + pastas extras (auto-memória, Cérebro).
|
|
316
419
|
const extra = typeof opts.refs === 'string' ? opts.refs.split(',').map((d) => path.resolve(root, d.trim())) : [];
|
|
@@ -348,7 +451,7 @@ function applyMoves(root, moves, opts) {
|
|
|
348
451
|
fs.renameSync(path.join(root, m.origem), to);
|
|
349
452
|
}
|
|
350
453
|
const undoFile = writeUndoScript(runDir, root, moves, edits);
|
|
351
|
-
const after = runTests(root,
|
|
454
|
+
const after = runTests(root, testCommand);
|
|
352
455
|
const relRun = path.relative(root, runDir);
|
|
353
456
|
|
|
354
457
|
if (before && after && before.ok && !after.ok && !opts.manter) {
|
|
@@ -365,7 +468,7 @@ function applyMoves(root, moves, opts) {
|
|
|
365
468
|
return {
|
|
366
469
|
ok: !after || after.ok || !before?.ok,
|
|
367
470
|
code: 0,
|
|
368
|
-
line: `Movi ${moves.length} arquivo(s) e atualizei referências em ${edits.length}.${testes} Desfazer: ${path.relative(root, undoFile)}.`,
|
|
471
|
+
line: `Movi ${moves.length} arquivo(s) e atualizei referências em ${edits.length}.${testes} Desfazer: ${undoCommand(root, runDir)} (ou ${path.relative(root, undoFile)}).`,
|
|
369
472
|
};
|
|
370
473
|
}
|
|
371
474
|
|
|
@@ -415,4 +518,15 @@ if (require.main === module) {
|
|
|
415
518
|
process.exit(result.code ?? (result.ok ? 0 : 1));
|
|
416
519
|
}
|
|
417
520
|
|
|
418
|
-
module.exports = {
|
|
521
|
+
module.exports = {
|
|
522
|
+
run,
|
|
523
|
+
splitArgv,
|
|
524
|
+
isAllowedTestArgv,
|
|
525
|
+
resolveTestCommand,
|
|
526
|
+
rewriteRefs,
|
|
527
|
+
readManifest,
|
|
528
|
+
planMoves,
|
|
529
|
+
undo,
|
|
530
|
+
writeUndoScript,
|
|
531
|
+
undoCommand,
|
|
532
|
+
};
|