wizz-method 2.8.0 → 2.8.1
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 +10 -15
- package/skills-registry.yaml +5 -5
- package/src/bmm-skills/4-implementation/wizz-code-review/references/review-economy.md +5 -5
- package/src/bmm-skills/4-implementation/wizz-quick-dev/SKILL.md +2 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/references/review-economy.md +5 -5
- package/src/modules/wizz/agents/wizz-maestro/SKILL.md +2 -0
- package/src/skills-lib/_shared/complexity-signal.md +2 -0
- package/src/skills-lib/wizz-ad-creative/SKILL.md +3 -1
- package/src/skills-lib/wizz-copywriting/SKILL.md +5 -1
- package/src/skills-lib/wizz-offer-forge/SKILL.md +3 -1
- package/src/skills-lib/wizz-page-cro/SKILL.md +5 -1
- package/src/skills-lib/wizz-referral-program/SKILL.md +2 -0
- package/src/skills-lib/wizz-router/SKILL.md +2 -0
- package/tools/hooks/wizz-router-enforce.js +3 -3
- package/tools/installer/core/installer.js +1 -0
- package/tools/installer/ide/_config-driven.js +223 -45
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "wizz-method",
|
|
4
|
-
"version": "2.8.
|
|
4
|
+
"version": "2.8.1",
|
|
5
5
|
"description": "Wizz Method: método de agência orientado por IA em PT-BR",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agile",
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"lint:fix": "eslint . --ext .js,.cjs,.mjs,.yaml --fix",
|
|
60
60
|
"lint:md": "markdownlint-cli2 \"**/*.md\"",
|
|
61
61
|
"prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0",
|
|
62
|
-
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run
|
|
62
|
+
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run quality:validate",
|
|
63
|
+
"quality:validate": "npm test && npm run validate:refs && npm run validate:registry-schema && npm run validate:method-refs && npm run validate:skills && npm run docs:validate-sidebar && npm run validate:generated && npm run eval:routing && npm run eval:output",
|
|
63
64
|
"rebundle": "node tools/bundle-web-bundles.js",
|
|
64
65
|
"sync:check": "node tools/sync-check.mjs",
|
|
65
66
|
"sync:global": "node tools/sync-global.mjs",
|
|
@@ -136,29 +137,24 @@
|
|
|
136
137
|
"markdownlint-cli2"
|
|
137
138
|
]
|
|
138
139
|
},
|
|
139
|
-
"overrides": {
|
|
140
|
-
"markdownlint-cli2": {
|
|
141
|
-
"js-yaml": "^4.3.0",
|
|
142
|
-
"markdown-it": "^14.2.0"
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
140
|
"dependencies": {
|
|
146
141
|
"@clack/core": "^1.3.1",
|
|
147
142
|
"@clack/prompts": "^1.4.0",
|
|
148
143
|
"@kayvan/markdown-tree-parser": "^1.6.1",
|
|
149
144
|
"chalk": "^4.1.2",
|
|
150
145
|
"commander": "^14.0.0",
|
|
151
|
-
"csv-parse": "^
|
|
146
|
+
"csv-parse": "^7.0.2",
|
|
152
147
|
"picocolors": "^1.1.1",
|
|
153
148
|
"semver": "^7.6.3",
|
|
154
149
|
"smol-toml": "^1.8.0",
|
|
155
150
|
"yaml": "^2.7.0"
|
|
156
151
|
},
|
|
157
152
|
"devDependencies": {
|
|
153
|
+
"@astrojs/markdown-remark": "^7.3.1",
|
|
158
154
|
"@astrojs/sitemap": "^3.6.0",
|
|
159
|
-
"@astrojs/starlight": "^0.
|
|
155
|
+
"@astrojs/starlight": "^0.42.2",
|
|
160
156
|
"@eslint/js": "^9.33.0",
|
|
161
|
-
"astro": "^
|
|
157
|
+
"astro": "^7.3.3",
|
|
162
158
|
"eslint": "^9.33.0",
|
|
163
159
|
"eslint-config-prettier": "^10.1.8",
|
|
164
160
|
"eslint-plugin-n": "^17.21.3",
|
|
@@ -166,13 +162,12 @@
|
|
|
166
162
|
"eslint-plugin-yml": "^1.18.0",
|
|
167
163
|
"husky": "^9.1.7",
|
|
168
164
|
"lint-staged": "^16.1.1",
|
|
169
|
-
"markdownlint-cli2": "^0.
|
|
165
|
+
"markdownlint-cli2": "^0.23.3",
|
|
170
166
|
"prettier": "^3.7.4",
|
|
171
167
|
"prettier-plugin-packagejson": "^2.5.19",
|
|
172
|
-
"sharp": "^0.
|
|
168
|
+
"sharp": "^0.35.4",
|
|
173
169
|
"unist-util-visit": "^5.1.0",
|
|
174
|
-
"yaml-eslint-parser": "^1.2.3"
|
|
175
|
-
"yaml-lint": "^1.7.0"
|
|
170
|
+
"yaml-eslint-parser": "^1.2.3"
|
|
176
171
|
},
|
|
177
172
|
"engines": {
|
|
178
173
|
"node": ">=20.12.0"
|
package/skills-registry.yaml
CHANGED
|
@@ -302,7 +302,7 @@ areas:
|
|
|
302
302
|
# eval mostrava o custo: "qual prazo de garantia?" respondia copy,
|
|
303
303
|
# growth ou ads em amostras diferentes, porque as três estavam certas.
|
|
304
304
|
- id: wizz-copywriting
|
|
305
|
-
when: "Escrever copy de venda/página/headline do zero ou reescrever."
|
|
305
|
+
when: "Escrever copy de venda/página/headline do zero ou reescrever. Na página de vendas completa, incluir wizz-offer-forge se faltar oferta aprovada e wizz-page-cro se faltar estrutura de conversão aprovada; headline/CTA isolado ou texto sobre oferta e estrutura já aprovadas não exige refazê-las."
|
|
306
306
|
rel:
|
|
307
307
|
after: [pesquisa-de-publico-do-piva, wizz-offer-forge]
|
|
308
308
|
before: [wizz-copy-editing]
|
|
@@ -415,7 +415,7 @@ areas:
|
|
|
415
415
|
rel:
|
|
416
416
|
pairs_with: [wizz-churn-prevention]
|
|
417
417
|
- id: wizz-page-cro
|
|
418
|
-
when: "
|
|
418
|
+
when: "Estruturar ou otimizar conversão de página (home, landing, página de vendas, pricing, feature): hierarquia, CTA, prova e objeções. Inclui página nova/pré-lançamento junto da copy, sem exigir métricas existentes; não é apenas otimização pós-lançamento."
|
|
419
419
|
rel:
|
|
420
420
|
after: [wizz-tracking-audit]
|
|
421
421
|
pairs_with: [wizz-copywriting, wizz-form-cro, wizz-popup-cro]
|
|
@@ -440,7 +440,7 @@ areas:
|
|
|
440
440
|
rel:
|
|
441
441
|
pairs_with: [scrapling, wizz-sales-enablement]
|
|
442
442
|
- id: wizz-referral-program
|
|
443
|
-
when: "
|
|
443
|
+
when: "Desenhar ou otimizar programa de indicação/afiliados: um plano de mecânica, incentivos, regras e atribuição. Suas etapas internas não são entregas de outras áreas. Produzir uma campanha de emails, uma nova página ou código de integração são entregas adicionais, somente quando solicitadas; não são implícitas em montar o programa."
|
|
444
444
|
rel:
|
|
445
445
|
pairs_with: [wizz-analytics-tracking, wizz-email-sequence]
|
|
446
446
|
- id: wizz-free-tool-strategy
|
|
@@ -468,7 +468,7 @@ areas:
|
|
|
468
468
|
rel:
|
|
469
469
|
after: [pesquisa-de-publico-do-piva, wizz-lowticket-minerador]
|
|
470
470
|
before: [wizz-copywriting]
|
|
471
|
-
pairs_with: [wizz-pricing-strategy]
|
|
471
|
+
pairs_with: [wizz-pricing-strategy, wizz-lowticket-metodologia]
|
|
472
472
|
# Módulo lowticket (src/modules/lowticket) — ver nota em ads.skills. Desde
|
|
473
473
|
# 2026-09-18 o módulo inteiro mora aqui (um dono por skill): copy e ads
|
|
474
474
|
# chegam nele pelo `rel`. Isto é comentário de propósito — escrever "vale
|
|
@@ -490,7 +490,7 @@ areas:
|
|
|
490
490
|
- "SaaS, B2B, site de cliente, high ticket ou mentoria: os números não transferem."
|
|
491
491
|
rel:
|
|
492
492
|
after: [wizz-lowticket-guia]
|
|
493
|
-
pairs_with: [wizz-copywriting, wizz-lowticket-pagina, wizz-lowticket-trafego]
|
|
493
|
+
pairs_with: [wizz-copywriting, wizz-lowticket-pagina, wizz-lowticket-trafego, wizz-offer-forge]
|
|
494
494
|
- id: wizz-lowticket-funil
|
|
495
495
|
module: lowticket
|
|
496
496
|
when: "Escada pós-checkout de low ticket: upsell 1, upsell 2, downsell, oferta final, régua de preço por degrau e copy das páginas."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Review Economy
|
|
2
2
|
|
|
3
|
-
Shared rules that keep the review layers on the
|
|
3
|
+
Shared rules that keep the review layers on the Strong tier (`opus` in Claude Code) while cutting what they read and how many of them run. Two byte-identical copies live in `wizz-code-review/references/review-economy.md` and `wizz-quick-dev/references/review-economy.md`. Diffs are built with plain git commands; the tier is picked by session judgment.
|
|
4
4
|
|
|
5
5
|
## 1. Build the diff
|
|
6
6
|
|
|
@@ -27,7 +27,7 @@ Every diff command here runs against the repository root, never a bare `.`: comp
|
|
|
27
27
|
|
|
28
28
|
## 2. Pick the tier (session judgment)
|
|
29
29
|
|
|
30
|
-
| Tier | Rule | Active layers |
|
|
30
|
+
| Tier | Rule | Active layers | Strong subagents |
|
|
31
31
|
|---|---|---|---|
|
|
32
32
|
| `small` | at most 3 files and 80 changed lines (after exclusion), no critical hit | `blind-verification` | 1 |
|
|
33
33
|
| `normal` | anything above `small`, no critical hit | `blind-verification`, `edge-case-hunter` | 2 |
|
|
@@ -59,10 +59,10 @@ When the brief names a `review-prompts/` file, that file owns the method and the
|
|
|
59
59
|
|
|
60
60
|
## 4. Decide who triages
|
|
61
61
|
|
|
62
|
-
The
|
|
62
|
+
The Strong tier is defined in `_shared/model-ladder.md`.
|
|
63
63
|
|
|
64
|
-
- **Session runs on the
|
|
65
|
-
- **Any session model other than the
|
|
64
|
+
- **Session runs on the Strong tier:** the session triages, as the triage instructions say. It already holds the context, so a subagent would only re-read it.
|
|
65
|
+
- **Any session model other than the Strong tier:** write the collected findings, each with its originating layer `id`, to a uniquely-named temp file and launch one `wizz-exec-strong` subagent. Its brief is: the triage instructions (the path of the step file that holds them), the findings file, `{diff_file}`, `{spec_file}` when present, the paths of any loaded context docs, `{tier}`, `{failed_layers}`, `{layer_status}` (the active/dropped layer ids with drop reasons, so the triager knows which layers actually ran), and — only in workflows that set it (`wizz-code-review`) — `{review_mode}`. The subagent runs the whole pipeline itself: verdicts, the acceptance audit, grouping, and routing into categories. It returns every entry with severity, category and evidence, plus every dismissal with its reason — it never writes to any file; the session writes every log. The session then re-checks the subagent's own verdicts and routing against the evidence it returned — this is the session validating a delegated triager's work, not a new prompt to the user — and carries on per each workflow's own step. In `wizz-code-review`, `decision_needed` entries still get a genuine human confirmation before continuing, exactly as the presentation step already does; a confirmed entry the user rejects there is logged as dismissed with the user's own reason, never forced through. In `wizz-quick-dev`, `intent_gap` still loops back to the human as part of resolving it, and `bad_spec` still loops back automatically — the session's re-check adds no new user prompt for either.
|
|
66
66
|
|
|
67
67
|
`{failed_layers}` stores layer `id`s (append ` (partial)` for a partial layer); every check against it compares ids, not names.
|
|
68
68
|
|
|
@@ -39,6 +39,8 @@ You are the leaf: the maestro demotes here for pointed work (bug, tweak, single-
|
|
|
39
39
|
|
|
40
40
|
**Dispatch rule:** IF 2+ areas → `wizz-maestro`, always. ELSE count the 3 remaining factors (multi-step, needs planning, produces a memorable artifact); 2+ factors → `wizz-maestro`. The area clause decides on its own, without summing with anything else.
|
|
41
41
|
|
|
42
|
+
**Decision order — capabilities → required relations → areas → shortcuts.** Match the catalogue entries, inspect their `rel`, and include only capabilities needed for the requested deliverable and not already supplied in the context. Resolve their owning areas before applying ANY one-area shortcut. One artifact can require multiple areas; the word "copy" does not prove that only copy is needed. For full sales-page copy, if either the offer or the conversion structure is missing from the supplied context, include the missing capability: `wizz-offer-forge` for the offer and/or `wizz-page-cro` for conversion structure (growth), alongside `wizz-copywriting` (copy); route to maestro. Do not assume those inputs are ready merely because the user did not mention them. If both are already approved and the request is limited to text, keep copy-only work in copy. An isolated headline or CTA does not require redesigning the whole page. Optional neighbours and separate deliverables do not add areas; `pairs_with` alone never escalates.
|
|
43
|
+
|
|
42
44
|
- If clarification reveals the request is actually **multi-goal / multi-area** (2+ top-level shippable deliverables, or it spans several areas), do not silently absorb it. That is clause 1: escalate regardless of how light each individual part looks. Surface it and propose escalating to `wizz-maestro` to orchestrate the sequence. Proceed solo only if the user confirms a narrowed single goal.
|
|
43
45
|
- If it is genuinely single-area but hits 2+ of the remaining factors (multi-step, needs planning, produces a memorable artifact), also escalate. Do not silently absorb heavy single-area work either.
|
|
44
46
|
- **Count those factors with the objective test** (canonical source: `_shared/complexity-signal.md`; same test the Diretor and the maestro apply). **When in doubt, the factor does NOT count.** *Multi-step* = the delivery is 2+ artifacts of different kinds (a batch of the same kind is ONE delivery). *Needs planning* = a scope or ordering decision is missing that the user has not made; "work that has internal steps" does not count, every task has internal steps. *Memorable artifact* = a reference document someone reopens later (PRD, story, spec, written plan or audit); a fix, a component or a refactor of one module is not. A **single delivery** and the **diagnosis of one specific mechanism** stay with you, however important they look; what escalates is the request of **open scope that the request itself does not delimit** ("how do I improve / reduce X" is that, a question with a single definable answer is not), and what escalates regardless of size is a **sensitive subsystem**: changing auth/sessions, payments/billing, permissions, or migrating data that already exists in production goes to the maestro even as a single delivery, because getting it wrong breaks something other than the delivery itself (people locked out, money charged twice, data lost). Running a test against one of those is not changing it. **Changing the product's code is yours even when the subject is visual** — a button colour, mobile spacing: the delivery is a commit, and `designer` decides the look without shipping the change.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Review Economy
|
|
2
2
|
|
|
3
|
-
Shared rules that keep the review layers on the
|
|
3
|
+
Shared rules that keep the review layers on the Strong tier (`opus` in Claude Code) while cutting what they read and how many of them run. Two byte-identical copies live in `wizz-code-review/references/review-economy.md` and `wizz-quick-dev/references/review-economy.md`. Diffs are built with plain git commands; the tier is picked by session judgment.
|
|
4
4
|
|
|
5
5
|
## 1. Build the diff
|
|
6
6
|
|
|
@@ -27,7 +27,7 @@ Every diff command here runs against the repository root, never a bare `.`: comp
|
|
|
27
27
|
|
|
28
28
|
## 2. Pick the tier (session judgment)
|
|
29
29
|
|
|
30
|
-
| Tier | Rule | Active layers |
|
|
30
|
+
| Tier | Rule | Active layers | Strong subagents |
|
|
31
31
|
|---|---|---|---|
|
|
32
32
|
| `small` | at most 3 files and 80 changed lines (after exclusion), no critical hit | `blind-verification` | 1 |
|
|
33
33
|
| `normal` | anything above `small`, no critical hit | `blind-verification`, `edge-case-hunter` | 2 |
|
|
@@ -59,10 +59,10 @@ When the brief names a `review-prompts/` file, that file owns the method and the
|
|
|
59
59
|
|
|
60
60
|
## 4. Decide who triages
|
|
61
61
|
|
|
62
|
-
The
|
|
62
|
+
The Strong tier is defined in `_shared/model-ladder.md`.
|
|
63
63
|
|
|
64
|
-
- **Session runs on the
|
|
65
|
-
- **Any session model other than the
|
|
64
|
+
- **Session runs on the Strong tier:** the session triages, as the triage instructions say. It already holds the context, so a subagent would only re-read it.
|
|
65
|
+
- **Any session model other than the Strong tier:** write the collected findings, each with its originating layer `id`, to a uniquely-named temp file and launch one `wizz-exec-strong` subagent. Its brief is: the triage instructions (the path of the step file that holds them), the findings file, `{diff_file}`, `{spec_file}` when present, the paths of any loaded context docs, `{tier}`, `{failed_layers}`, `{layer_status}` (the active/dropped layer ids with drop reasons, so the triager knows which layers actually ran), and — only in workflows that set it (`wizz-code-review`) — `{review_mode}`. The subagent runs the whole pipeline itself: verdicts, the acceptance audit, grouping, and routing into categories. It returns every entry with severity, category and evidence, plus every dismissal with its reason — it never writes to any file; the session writes every log. The session then re-checks the subagent's own verdicts and routing against the evidence it returned — this is the session validating a delegated triager's work, not a new prompt to the user — and carries on per each workflow's own step. In `wizz-code-review`, `decision_needed` entries still get a genuine human confirmation before continuing, exactly as the presentation step already does; a confirmed entry the user rejects there is logged as dismissed with the user's own reason, never forced through. In `wizz-quick-dev`, `intent_gap` still loops back to the human as part of resolving it, and `bad_spec` still loops back automatically — the session's re-check adds no new user prompt for either.
|
|
66
66
|
|
|
67
67
|
`{failed_layers}` stores layer `id`s (append ` (partial)` for a partial layer); every check against it compares ids, not names.
|
|
68
68
|
|
|
@@ -86,6 +86,8 @@ In short: **the menu chooses the agent, the registry chooses the skill(s) and th
|
|
|
86
86
|
|
|
87
87
|
**Dispatch rule:** IF 2+ areas → maestro (you), always. OTHERWISE count the 3 remaining factors (multi-step, needs planning, generates a memorable artifact); 2+ factors → maestro (you). The area clause decides on its own, without adding up with anything else.
|
|
88
88
|
|
|
89
|
+
**Decision order — capabilities → required relations → areas → shortcuts.** Match the catalogue entries, inspect their `rel`, and include only capabilities needed for the requested deliverable and not already supplied in the context. Resolve their owning areas before applying ANY one-area shortcut. One artifact can require multiple areas; the word "copy" does not prove that only copy is needed. For full sales-page copy, if either the offer or the conversion structure is missing from the supplied context, include the missing capability: `wizz-offer-forge` for the offer and/or `wizz-page-cro` for conversion structure (growth), alongside `wizz-copywriting` (copy); route to maestro. Do not assume those inputs are ready merely because the user did not mention them. If both are already approved and the request is limited to text, keep copy-only work in copy. An isolated headline or CTA does not require redesigning the whole page. Optional neighbours and separate deliverables do not add areas; `pairs_with` alone never escalates.
|
|
90
|
+
|
|
89
91
|
1. **Areas**: just 1, or several? (clause 1: 2+ decides on its own, without adding up with anything else)
|
|
90
92
|
2. **Steps** — a one-off tweak, or multi-step?
|
|
91
93
|
3. **Planning** — can it go straight ahead, or does it need planning first?
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
Dispatch rule: IF 2+ areas → maestro, always. OTHERWISE count the 3 remaining factors (multi-step, needs planning, produces a memorable artifact); 2+ factors → maestro. The area clause decides on its own, without summing with anything else.
|
|
14
14
|
|
|
15
|
+
**Decision order — capabilities → required relations → areas → shortcuts.** Match the catalogue entries, inspect their `rel`, and include only capabilities needed for the requested deliverable and not already supplied in the context. Resolve their owning areas before applying ANY one-area shortcut. One artifact can require multiple areas; the word "copy" does not prove that only copy is needed. For full sales-page copy, if either the offer or the conversion structure is missing from the supplied context, include the missing capability: `wizz-offer-forge` for the offer and/or `wizz-page-cro` for conversion structure (growth), alongside `wizz-copywriting` (copy); route to maestro. Do not assume those inputs are ready merely because the user did not mention them. If both are already approved and the request is limited to text, keep copy-only work in copy. An isolated headline or CTA does not require redesigning the whole page. Optional neighbours and separate deliverables do not add areas; `pairs_with` alone never escalates.
|
|
16
|
+
|
|
15
17
|
What you count are the areas of the catalogue ENTRIES the request matches — never the topics named in the sentence. "Full audit of the site: performance, SEO, security, accessibility" names four topics and matches one entry (`wizz-launch-readiness`, qa), whose four legs are that single audit's own scope, not four deliverables: one area, so `agent:qa`. Counting nouns instead of entries is the most common way to over-escalate. (Owner's decision, 2026-09-19.)
|
|
16
18
|
|
|
17
19
|
## How to count (added 2026-09-18)
|
|
@@ -15,7 +15,9 @@ You are an expert performance creative strategist. Your goal is to generate high
|
|
|
15
15
|
If `.agents/product-marketing-context.md` exists (or `.claude/product-marketing-context.md` in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
|
|
16
16
|
|
|
17
17
|
**Check for the low ticket base:**
|
|
18
|
-
|
|
18
|
+
The low ticket base applies only when BOTH are true: the `wizz-lowticket-metodologia` skill is installed in this project, AND the project context or the request itself (including what the user answers while you gather context) establishes that the operation is low ticket (price range, cheap product, direct traffic). When both hold, read its `knowledge/INDEX.md` and `knowledge/guardrails.md` first and load the one shard the INDEX names for this task; the usual ones here are `04` (creative structure and rules), `04c` (video creative script) and `04b` (formats and swipe file). Where that shard contradicts this skill, **the shard wins**, and say so in your reply.
|
|
19
|
+
|
|
20
|
+
If neither the project context nor the request says so, this skill's defaults win: do not presume low ticket because the page is a sales page, the product is a course, or a campaign is running. SaaS, B2B, client sites, high ticket and mentorship are outside the base: do not load it, its numbers do not transfer. Without that skill installed, nothing changes.
|
|
19
21
|
|
|
20
22
|
Gather this context (ask if not provided):
|
|
21
23
|
|
|
@@ -11,11 +11,15 @@ You are an expert conversion copywriter. Your goal is to write marketing copy th
|
|
|
11
11
|
|
|
12
12
|
## Before Writing
|
|
13
13
|
|
|
14
|
+
**Check the scope of a full sales page.** Reuse an approved offer and conversion structure when supplied. If either is missing, invoke the `wizz-offer-forge` skill for the offer and the `wizz-page-cro` skill for conversion structure as needed; inside a Wizz project the maestro coordinates this copy + growth work. CRO structure also applies before launch, not only after metrics exist. A single headline/CTA or text-only work within an approved structure stays a copy task; do not reopen decisions the user has already made.
|
|
15
|
+
|
|
14
16
|
**Check for product marketing context first:**
|
|
15
17
|
If `.agents/product-marketing-context.md` exists (or `.claude/product-marketing-context.md` in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
|
|
16
18
|
|
|
17
19
|
**Check for the low ticket base:**
|
|
18
|
-
|
|
20
|
+
The low ticket base applies only when BOTH are true: the `wizz-lowticket-metodologia` skill is installed in this project, AND the project context or the request itself (including what the user answers while you gather context) establishes that the operation is low ticket (price range, cheap product, direct traffic). When both hold, read its `knowledge/INDEX.md` and `knowledge/guardrails.md` first and load the one shard the INDEX names for this task; the usual ones here are `12c` (anatomy of the low ticket page), `18` (copy from desire to offer, the 7 blocks) and, for a VSL, `04b` §4.7 and §4.7-A. Where that shard contradicts this skill, **the shard wins**, and say so in your reply.
|
|
21
|
+
|
|
22
|
+
If neither the project context nor the request says so, this skill's defaults win: do not presume low ticket because the page is a sales page, the product is a course, or a campaign is running. SaaS, B2B, client sites, high ticket and mentorship are outside the base: do not load it, its numbers do not transfer. Without that skill installed, nothing changes.
|
|
19
23
|
|
|
20
24
|
Gather this context (ask if not provided):
|
|
21
25
|
|
|
@@ -40,7 +40,9 @@ Then, consult existing context or ask the user:
|
|
|
40
40
|
|
|
41
41
|
### Step 0.5 — The Local Ruler Takes Precedence
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
The low ticket base applies only when BOTH are true: the `wizz-lowticket-metodologia` skill is installed in this project, AND the project context or the request itself (including what the user answers while you gather context) establishes that the operation is low ticket (price range, cheap product, direct traffic). The business model answered in Step 0 counts: Low-ticket establishes it, High-ticket / Mentorship rules it out. When both hold, read its INDEX entries for **offer, price, and guarantee** and use those values as the ruler above this skill's defaults (price ranges, upsell multiplier, guarantee period). State in your reply which ruler is in effect. Where the local ruler contradicts this skill, **the local ruler wins**.
|
|
44
|
+
|
|
45
|
+
If neither the project context nor the request says so, this skill's defaults win: do not presume low ticket because the page is a sales page, the product is a course, or a campaign is running. SaaS, B2B, client sites, high ticket and mentorship are outside the base: do not load it, its numbers do not transfer.
|
|
44
46
|
|
|
45
47
|
Without that skill installed, follow this skill's defaults. Nothing changes.
|
|
46
48
|
|
|
@@ -11,11 +11,15 @@ You are a conversion rate optimization expert. Your goal is to analyze marketing
|
|
|
11
11
|
|
|
12
12
|
## Initial Assessment
|
|
13
13
|
|
|
14
|
+
**New pages and drafts are in scope.** Use this framework to define or review conversion structure before launch as well as to optimize an existing page. For a new sales page, work with the copy on hierarchy, CTA placement, proof and objections; existing traffic or conversion metrics are not prerequisites. If metrics do not exist, state that limitation and propose testable hypotheses, not measured uplift. An isolated text edit with an approved structure does not require a new CRO engagement. Invoke the `wizz-copywriting` skill for the copy itself.
|
|
15
|
+
|
|
14
16
|
**Check for product marketing context first:**
|
|
15
17
|
If `.agents/product-marketing-context.md` exists (or `.claude/product-marketing-context.md` in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
|
|
16
18
|
|
|
17
19
|
**Check for the low ticket base:**
|
|
18
|
-
|
|
20
|
+
The low ticket base applies only when BOTH are true: the `wizz-lowticket-metodologia` skill is installed in this project, AND the project context or the request itself (including what the user answers while you gather context) establishes that the operation is low ticket (price range, cheap product, direct traffic). When both hold, read its `knowledge/INDEX.md` and `knowledge/guardrails.md` first and load the one shard the INDEX names for this task; the usual ones here are `12c` (anatomy of the low ticket page), `06` (sales page) and `02` (funnel diagnosis: which transition is leaking). Where that shard contradicts this skill, **the shard wins**, and say so in your reply.
|
|
21
|
+
|
|
22
|
+
If neither the project context nor the request says so, this skill's defaults win: do not presume low ticket because the page is a sales page, the product is a course, or a campaign is running. SaaS, B2B, client sites, high ticket and mentorship are outside the base: do not load it, its numbers do not transfer. Without that skill installed, nothing changes.
|
|
19
23
|
|
|
20
24
|
Before providing recommendations, identify:
|
|
21
25
|
|
|
@@ -11,6 +11,8 @@ You are an expert in viral growth and referral marketing. Your goal is to help d
|
|
|
11
11
|
|
|
12
12
|
## Before Starting
|
|
13
13
|
|
|
14
|
+
**Bound the requested deliverable.** A request to set up a referral program starts with one program plan: mechanics, incentives, rules and attribution. The design steps and launch checklist below are parts of that plan, not permission to implement every listed item. Consider related email and tracking capabilities, but do not turn example invitations into a separate production campaign. Writing and sending a campaign, building a new page, or changing integration/payment code are additional deliverables only when requested; use the normal multi-area and sensitive-subsystem rules for those requests. For a program plan alone, stay with its growth owner.
|
|
15
|
+
|
|
14
16
|
**Check for product marketing context first:**
|
|
15
17
|
If `.agents/product-marketing-context.md` exists (or `.claude/product-marketing-context.md` in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
|
|
16
18
|
|
|
@@ -40,6 +40,8 @@ Assess the **Area**: how many areas does the request touch? The canonical area l
|
|
|
40
40
|
|
|
41
41
|
**Dispatch rule:** IF 2+ areas → `wizz-maestro`, always. OTHERWISE count the 3 remaining factors (multi-step, needs planning, produces a memorable artifact); 2+ factors → `wizz-maestro`. The area clause decides on its own, without combining with anything else.
|
|
42
42
|
|
|
43
|
+
**Decision order — capabilities → required relations → areas → shortcuts.** Match the catalogue entries, inspect their `rel`, and include only capabilities needed for the requested deliverable and not already supplied in the context. Resolve their owning areas before applying ANY one-area shortcut. One artifact can require multiple areas; the word "copy" does not prove that only copy is needed. For full sales-page copy, if either the offer or the conversion structure is missing from the supplied context, include the missing capability: `wizz-offer-forge` for the offer and/or `wizz-page-cro` for conversion structure (growth), alongside `wizz-copywriting` (copy); route to maestro. Do not assume those inputs are ready merely because the user did not mention them. If both are already approved and the request is limited to text, keep copy-only work in copy. An isolated headline or CTA does not require redesigning the whole page. Optional neighbours and separate deliverables do not add areas; `pairs_with` alone never escalates.
|
|
44
|
+
|
|
43
45
|
**What you count are the areas of the catalogue ENTRIES the request matches — never the topics named in the sentence.** A request can name four subjects and still match one entry, and then it is one area. "Full audit of the site: performance, SEO, security, accessibility" names four topics and matches `wizz-launch-readiness` (qa), whose `when:` already spans build, security, SEO, analytics, content, legal and infra: those four legs are that single audit's own scope, not four deliverables, so the answer is `agent:qa`. Counting nouns instead of entries is the most common way to over-escalate. (Owner's decision, 2026-09-19.)
|
|
44
46
|
|
|
45
47
|
- **2+ areas → `wizz-maestro`, always.** It's the Manager: coordinates agents across areas, sets up the sequence, upholds the memory duty. Invoke via `Skill` and **stop here**.
|
|
@@ -123,12 +123,12 @@ function findWizzRoot(startDir) {
|
|
|
123
123
|
function buildContext(isWizzProject) {
|
|
124
124
|
if (isWizzProject) {
|
|
125
125
|
return (
|
|
126
|
-
'🎛️
|
|
126
|
+
'🎛️ Wizz: NÃO invoque wizz-router. Capacidades → rel necessário à entrega → áreas → atalhos. 2+ áreas → `wizz-maestro`. ' +
|
|
127
127
|
'1 área: 2+ fatores (multi-passo, planejar, artefato memorável) → `wizz-maestro`; ' +
|
|
128
|
-
'senão → Skill da área
|
|
128
|
+
'senão → Skill da área. ' +
|
|
129
129
|
'Auth/pagamento/permissão/migração → maestro. ' +
|
|
130
130
|
'Personas são skills, não subagent_type; Agent usa tipos disponíveis wizz-exec-*' +
|
|
131
|
-
'; difícil → wizz-exec-strong (ESCALAR → ultra).
|
|
131
|
+
'; difícil → wizz-exec-strong (ESCALAR → ultra).'
|
|
132
132
|
);
|
|
133
133
|
}
|
|
134
134
|
return (
|
|
@@ -1502,6 +1502,7 @@ class Installer {
|
|
|
1502
1502
|
' Get started:',
|
|
1503
1503
|
` 1. Launch your AI agent from your project folder`,
|
|
1504
1504
|
` 2. Not sure what to do? Invoke the ${color.cyan('wizz-help')} skill and ask it what to do!`,
|
|
1505
|
+
` Recommended session: use the Standard tier; consult the model ladder for platform-specific mappings.`,
|
|
1505
1506
|
'',
|
|
1506
1507
|
` Docs and Guides: ${color.blue('https://method.wizzcomms.com/')}`,
|
|
1507
1508
|
` Support: ${color.blue('support@wizzcomms.com')}`,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const path = require('node:path');
|
|
2
|
+
const nativeFsp = require('node:fs/promises');
|
|
2
3
|
const fs = require('../fs-native');
|
|
3
4
|
const yaml = require('yaml');
|
|
4
5
|
const prompts = require('../prompts');
|
|
@@ -58,6 +59,22 @@ function isSafeCanonicalId(value) {
|
|
|
58
59
|
// OpenCode's native `@skills/<id>` skill-reference syntax.
|
|
59
60
|
const DEFAULT_COMMANDS_BODY_TEMPLATE = '@skills/{canonicalId}';
|
|
60
61
|
|
|
62
|
+
// Native Wizz execution tiers have a deliberately small, known vocabulary.
|
|
63
|
+
// Do not infer ownership from the `wizz-exec-` prefix alone: users can (and do)
|
|
64
|
+
// create their own agents with that prefix.
|
|
65
|
+
const CURRENT_SUBAGENT_TIERS = new Set(['mechanical', 'standard', 'strong', 'ultra', 'review']);
|
|
66
|
+
const LEGACY_SUBAGENT_TIERS = new Set(['haiku', 'sonnet', 'opus']);
|
|
67
|
+
|
|
68
|
+
function subagentTierFromName(fileName) {
|
|
69
|
+
const match = fileName.match(/^wizz-exec-([a-z0-9][a-z0-9-]*)\.(?:md|toml)$/i);
|
|
70
|
+
return match ? match[1].toLowerCase() : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isRecognizedSubagentName(fileName, sourceNames = new Set()) {
|
|
74
|
+
const tier = subagentTierFromName(fileName);
|
|
75
|
+
return tier && (CURRENT_SUBAGENT_TIERS.has(tier) || LEGACY_SUBAGENT_TIERS.has(tier) || sourceNames.has(fileName));
|
|
76
|
+
}
|
|
77
|
+
|
|
61
78
|
// Is this skill a persona agent (vs. a workflow/tool/standalone skill)?
|
|
62
79
|
// Used by platforms that surface only persona agents (e.g. Copilot's Custom
|
|
63
80
|
// Agents picker). Signal: the skill's source `customize.toml` has an
|
|
@@ -207,7 +224,9 @@ class ConfigDrivenIdeSetup {
|
|
|
207
224
|
results.commands = await this.installCommandPointers(projectDir, wizzDir, this.installerConfig, options);
|
|
208
225
|
}
|
|
209
226
|
if (this.installerConfig.agents_target_dir) {
|
|
210
|
-
results.subagents = await this.installSubagents(projectDir, wizzDir, this.installerConfig);
|
|
227
|
+
results.subagents = await this.installSubagents(projectDir, wizzDir, this.installerConfig, options);
|
|
228
|
+
results.subagentReport = this.lastSubagentReport;
|
|
229
|
+
results.subagentCleanupReport = this.lastSubagentCleanupReport;
|
|
211
230
|
}
|
|
212
231
|
return { success: true, results };
|
|
213
232
|
}
|
|
@@ -243,7 +262,9 @@ class ConfigDrivenIdeSetup {
|
|
|
243
262
|
}
|
|
244
263
|
|
|
245
264
|
if (config.agents_target_dir) {
|
|
246
|
-
results.subagents = await this.installSubagents(projectDir, wizzDir, config);
|
|
265
|
+
results.subagents = await this.installSubagents(projectDir, wizzDir, config, options);
|
|
266
|
+
results.subagentReport = this.lastSubagentReport;
|
|
267
|
+
results.subagentCleanupReport = this.lastSubagentCleanupReport;
|
|
247
268
|
}
|
|
248
269
|
|
|
249
270
|
await this.printSummary(results, target_dir, options);
|
|
@@ -466,21 +487,19 @@ class ConfigDrivenIdeSetup {
|
|
|
466
487
|
* format that field means anything to. This mirrors how
|
|
467
488
|
* `installVerbatimSkills` resolves module-relative source paths.
|
|
468
489
|
*
|
|
469
|
-
* Ownership is scoped to
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
* explicitly rather than relying on the extension filter to exclude them.
|
|
490
|
+
* Ownership is scoped to recognized Wizz tier names, not merely the
|
|
491
|
+
* `wizz-exec-*` naming convention (`.md` or `.toml` extension). A user's
|
|
492
|
+
* own hand-authored `wizz-exec-*` subagents are left untouched. Directory
|
|
493
|
+
* entries (including the per-platform subdirs that live alongside Claude
|
|
494
|
+
* Code's root-level files) are skipped explicitly.
|
|
475
495
|
* Silently no-ops when the source directory doesn't exist (e.g. install
|
|
476
496
|
* without the wizz module).
|
|
477
497
|
*
|
|
478
498
|
* Tier names in the target dir are current (mechanical, standard, strong,
|
|
479
|
-
* ultra, review);
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
* already removed every wizz-exec-*.{md,toml} file from the target dir.
|
|
499
|
+
* ultra, review); legacy ladder-v1 tiers are recognized for migration. A
|
|
500
|
+
* stale tier a platform's source no longer provides is removed only after
|
|
501
|
+
* being backed up, because its old source may no longer be available for
|
|
502
|
+
* comparison.
|
|
484
503
|
*
|
|
485
504
|
* @param {string} projectDir - Project directory
|
|
486
505
|
* @param {string} wizzDir - WIZZ installation directory
|
|
@@ -488,7 +507,15 @@ class ConfigDrivenIdeSetup {
|
|
|
488
507
|
* and the optional agents_source_subdir
|
|
489
508
|
* @returns {Promise<number>} Count of subagent files installed
|
|
490
509
|
*/
|
|
491
|
-
async installSubagents(projectDir, wizzDir, config) {
|
|
510
|
+
async installSubagents(projectDir, wizzDir, config, options = {}) {
|
|
511
|
+
const report = {
|
|
512
|
+
installed: 0,
|
|
513
|
+
preserved: 0,
|
|
514
|
+
backups: 0,
|
|
515
|
+
failures: [],
|
|
516
|
+
};
|
|
517
|
+
this.lastSubagentReport = report;
|
|
518
|
+
|
|
492
519
|
const sourceDir = config.agents_source_subdir
|
|
493
520
|
? path.join(wizzDir, 'wizz', 'subagents', config.agents_source_subdir)
|
|
494
521
|
: path.join(wizzDir, 'wizz', 'subagents');
|
|
@@ -497,7 +524,8 @@ class ConfigDrivenIdeSetup {
|
|
|
497
524
|
let entries;
|
|
498
525
|
try {
|
|
499
526
|
entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
500
|
-
} catch {
|
|
527
|
+
} catch (error) {
|
|
528
|
+
await this._reportSubagentFailure('read source directory', sourceDir, error, options, report);
|
|
501
529
|
return 0;
|
|
502
530
|
}
|
|
503
531
|
|
|
@@ -507,12 +535,53 @@ class ConfigDrivenIdeSetup {
|
|
|
507
535
|
if (subagentFiles.length === 0) return 0;
|
|
508
536
|
|
|
509
537
|
const targetPath = path.join(projectDir, config.agents_target_dir);
|
|
510
|
-
|
|
538
|
+
try {
|
|
539
|
+
await fs.ensureDir(targetPath);
|
|
540
|
+
} catch (error) {
|
|
541
|
+
await this._reportSubagentFailure('create target directory', targetPath, error, options, report);
|
|
542
|
+
return 0;
|
|
543
|
+
}
|
|
511
544
|
|
|
512
545
|
let count = 0;
|
|
513
546
|
for (const entry of subagentFiles) {
|
|
514
|
-
|
|
515
|
-
|
|
547
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
548
|
+
const targetFile = path.join(targetPath, entry.name);
|
|
549
|
+
try {
|
|
550
|
+
let targetStat;
|
|
551
|
+
try {
|
|
552
|
+
targetStat = await nativeFsp.lstat(targetFile);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
if (error.code !== 'ENOENT') throw error;
|
|
555
|
+
}
|
|
556
|
+
if (targetStat) {
|
|
557
|
+
if (!targetStat.isFile()) {
|
|
558
|
+
report.preserved++;
|
|
559
|
+
await this._reportSubagentFailure(
|
|
560
|
+
'skip non-file target',
|
|
561
|
+
targetFile,
|
|
562
|
+
new Error('target is not a regular file'),
|
|
563
|
+
options,
|
|
564
|
+
report,
|
|
565
|
+
);
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
const existing = await fs.readFile(targetFile, 'utf8');
|
|
569
|
+
const source = await fs.readFile(sourcePath, 'utf8');
|
|
570
|
+
if (existing !== source) {
|
|
571
|
+
const backupPath = await this._backupSubagentFile(targetFile, options, report);
|
|
572
|
+
if (!backupPath) {
|
|
573
|
+
report.preserved++;
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
report.backups++;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
await fs.copy(sourcePath, targetFile, { overwrite: true });
|
|
580
|
+
count++;
|
|
581
|
+
report.installed++;
|
|
582
|
+
} catch (error) {
|
|
583
|
+
await this._reportSubagentFailure('install', targetFile, error, options, report);
|
|
584
|
+
}
|
|
516
585
|
}
|
|
517
586
|
|
|
518
587
|
return count;
|
|
@@ -544,6 +613,15 @@ class ConfigDrivenIdeSetup {
|
|
|
544
613
|
if (results.subagents > 0 && this.installerConfig?.agents_target_dir) {
|
|
545
614
|
await prompts.log.success(`${this.name} subagents: ${results.subagents} → ${this.installerConfig.agents_target_dir}`);
|
|
546
615
|
}
|
|
616
|
+
const subagentReport = results.subagentReport;
|
|
617
|
+
const cleanupReport = results.subagentCleanupReport;
|
|
618
|
+
const operationFailures = (subagentReport?.failures?.length || 0) + (cleanupReport?.failures?.length || 0);
|
|
619
|
+
if (operationFailures > 0) {
|
|
620
|
+
await prompts.log.warn(` (${operationFailures} subagent file operation(s) failed — see warnings above)`);
|
|
621
|
+
}
|
|
622
|
+
if (subagentReport?.preserved > 0 && !options.silent) {
|
|
623
|
+
await prompts.log.message(` (${subagentReport.preserved} custom subagent file(s) preserved)`);
|
|
624
|
+
}
|
|
547
625
|
}
|
|
548
626
|
|
|
549
627
|
/**
|
|
@@ -614,14 +692,12 @@ class ConfigDrivenIdeSetup {
|
|
|
614
692
|
);
|
|
615
693
|
}
|
|
616
694
|
|
|
617
|
-
// Clean
|
|
695
|
+
// Clean recognized Wizz subagent files (.md or .toml). Like command
|
|
618
696
|
// pointers, these live in a per-IDE directory (e.g. .claude/agents,
|
|
619
|
-
// .codex/agents) that isn't deduped across peers, so this runs
|
|
620
|
-
//
|
|
621
|
-
// current source content, so unconditional removal here is safe — a
|
|
622
|
-
// routine reinstall just recreates the files moments later.
|
|
697
|
+
// .codex/agents) that isn't deduped across peers, so this runs regardless
|
|
698
|
+
// of skipTarget. Customized files are backed up before replacement.
|
|
623
699
|
if (this.installerConfig?.agents_target_dir) {
|
|
624
|
-
await this.cleanupSubagents(projectDir, this.installerConfig
|
|
700
|
+
await this.cleanupSubagents(projectDir, this.installerConfig, options, resolvedWizzDir);
|
|
625
701
|
}
|
|
626
702
|
|
|
627
703
|
// Skip target_dir cleanup when a peer platform owns this directory
|
|
@@ -787,45 +863,147 @@ class ConfigDrivenIdeSetup {
|
|
|
787
863
|
}
|
|
788
864
|
|
|
789
865
|
/**
|
|
790
|
-
* Remove
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
866
|
+
* Remove only recognized Wizz subagents. A matching filename is not enough
|
|
867
|
+
* to establish ownership: custom `wizz-exec-*` agents remain in place.
|
|
868
|
+
* When a recognized file is customized, move it to a recoverable backup
|
|
869
|
+
* before cleanup/replacement so an update never destroys user work.
|
|
870
|
+
*
|
|
871
|
+
* The second argument accepts the historical target-dir string as well as
|
|
872
|
+
* the installer config. Keeping the string form makes this method useful to
|
|
873
|
+
* callers and older integrations that invoke it directly.
|
|
874
|
+
*
|
|
796
875
|
* @param {string} projectDir
|
|
797
|
-
* @param {string}
|
|
876
|
+
* @param {string|Object} agentsTargetOrConfig
|
|
877
|
+
* @param {Object} [options]
|
|
878
|
+
* @param {string|null} [wizzDir]
|
|
879
|
+
* @returns {Promise<Object>} cleanup report
|
|
798
880
|
*/
|
|
799
|
-
async cleanupSubagents(projectDir,
|
|
881
|
+
async cleanupSubagents(projectDir, agentsTargetOrConfig, options = {}, wizzDir = null) {
|
|
882
|
+
const config = typeof agentsTargetOrConfig === 'string' ? { agents_target_dir: agentsTargetOrConfig } : agentsTargetOrConfig || {};
|
|
883
|
+
const agentsTargetDir = config.agents_target_dir;
|
|
884
|
+
const report = { removed: 0, preserved: 0, backups: 0, failures: [] };
|
|
885
|
+
this.lastSubagentCleanupReport = report;
|
|
886
|
+
if (!agentsTargetDir) return report;
|
|
887
|
+
|
|
800
888
|
const agentsPath = path.join(projectDir, agentsTargetDir);
|
|
801
|
-
if (!(await fs.pathExists(agentsPath))) return;
|
|
889
|
+
if (!(await fs.pathExists(agentsPath))) return report;
|
|
802
890
|
|
|
803
891
|
let entries;
|
|
804
892
|
try {
|
|
805
893
|
entries = await fs.readdir(agentsPath, { withFileTypes: true });
|
|
806
|
-
} catch {
|
|
807
|
-
|
|
894
|
+
} catch (error) {
|
|
895
|
+
await this._reportSubagentFailure('read target directory', agentsPath, error, options, report);
|
|
896
|
+
return report;
|
|
808
897
|
}
|
|
809
898
|
|
|
899
|
+
const sourceNames = await this._subagentSourceNames(wizzDir, config, options, report);
|
|
810
900
|
for (const entry of entries) {
|
|
811
|
-
if (!entry.
|
|
812
|
-
if (!entry.
|
|
901
|
+
if (!subagentTierFromName(entry.name)) continue;
|
|
902
|
+
if (!entry.isFile()) {
|
|
903
|
+
report.preserved++;
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
if (!isRecognizedSubagentName(entry.name, sourceNames)) {
|
|
907
|
+
report.preserved++;
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const filePath = path.join(agentsPath, entry.name);
|
|
912
|
+
let content;
|
|
813
913
|
try {
|
|
814
|
-
await fs.
|
|
815
|
-
} catch {
|
|
816
|
-
|
|
914
|
+
content = await fs.readFile(filePath, 'utf8');
|
|
915
|
+
} catch (error) {
|
|
916
|
+
report.preserved++;
|
|
917
|
+
await this._reportSubagentFailure('read target file', filePath, error, options, report);
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const sourcePath = sourceNames.get(entry.name);
|
|
922
|
+
let sourceContent;
|
|
923
|
+
if (sourcePath) {
|
|
924
|
+
try {
|
|
925
|
+
sourceContent = await fs.readFile(sourcePath, 'utf8');
|
|
926
|
+
} catch (error) {
|
|
927
|
+
await this._reportSubagentFailure('read source file', sourcePath, error, options, report);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
// Exact source equality is the only proof that a source-backed file is
|
|
931
|
+
// unchanged. Without a matching source, both current and legacy tiers
|
|
932
|
+
// may contain customizations and must be backed up before removal.
|
|
933
|
+
const generated = sourceContent !== undefined && content === sourceContent;
|
|
934
|
+
if (!generated) {
|
|
935
|
+
const backupPath = await this._backupSubagentFile(filePath, options, report);
|
|
936
|
+
if (!backupPath) {
|
|
937
|
+
report.preserved++;
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
report.backups++;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
try {
|
|
944
|
+
await fs.remove(filePath);
|
|
945
|
+
report.removed++;
|
|
946
|
+
} catch (error) {
|
|
947
|
+
await this._reportSubagentFailure('remove', filePath, error, options, report);
|
|
817
948
|
}
|
|
818
949
|
}
|
|
819
950
|
|
|
820
|
-
// Remove the agents directory if we emptied it.
|
|
821
951
|
try {
|
|
822
952
|
const remaining = await fs.readdir(agentsPath);
|
|
823
|
-
if (remaining.length === 0)
|
|
824
|
-
|
|
953
|
+
if (remaining.length === 0) await fs.remove(agentsPath);
|
|
954
|
+
} catch (error) {
|
|
955
|
+
// A non-empty directory is normal when user-owned agents remain. A
|
|
956
|
+
// failure to inspect/remove it is still actionable and must be visible.
|
|
957
|
+
if (error.code !== 'ENOENT') await this._reportSubagentFailure('remove empty target directory', agentsPath, error, options, report);
|
|
958
|
+
}
|
|
959
|
+
return report;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
async _subagentSourceNames(wizzDir, config, options = {}, report = null) {
|
|
963
|
+
const sourceNames = new Map();
|
|
964
|
+
if (!wizzDir) return sourceNames;
|
|
965
|
+
const sourceDir = config.agents_source_subdir
|
|
966
|
+
? path.join(wizzDir, 'wizz', 'subagents', config.agents_source_subdir)
|
|
967
|
+
: path.join(wizzDir, 'wizz', 'subagents');
|
|
968
|
+
if (!(await fs.pathExists(sourceDir))) return sourceNames;
|
|
969
|
+
try {
|
|
970
|
+
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
971
|
+
for (const entry of entries) {
|
|
972
|
+
if (entry.isFile() && subagentTierFromName(entry.name)) sourceNames.set(entry.name, path.join(sourceDir, entry.name));
|
|
825
973
|
}
|
|
826
|
-
} catch {
|
|
827
|
-
|
|
974
|
+
} catch (error) {
|
|
975
|
+
await this._reportSubagentFailure('read source directory', sourceDir, error, options, report);
|
|
828
976
|
}
|
|
977
|
+
return sourceNames;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
async _backupSubagentFile(filePath, options = {}, report = null) {
|
|
981
|
+
let backupPath = `${filePath}.wizz-backup`;
|
|
982
|
+
let suffix = 0;
|
|
983
|
+
try {
|
|
984
|
+
const sourceStat = await nativeFsp.lstat(filePath);
|
|
985
|
+
if (!sourceStat.isFile()) throw new Error('source is not a regular file');
|
|
986
|
+
while (true) {
|
|
987
|
+
try {
|
|
988
|
+
await nativeFsp.copyFile(filePath, backupPath, nativeFsp.constants.COPYFILE_EXCL);
|
|
989
|
+
if (!options.silent) await prompts.log.warn(`Preserved existing subagent at ${backupPath}`);
|
|
990
|
+
return backupPath;
|
|
991
|
+
} catch (error) {
|
|
992
|
+
if (error.code !== 'EEXIST') throw error;
|
|
993
|
+
suffix++;
|
|
994
|
+
backupPath = `${filePath}.wizz-backup.${suffix}`;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
} catch (error) {
|
|
998
|
+
await this._reportSubagentFailure('backup', filePath, error, options, report);
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
async _reportSubagentFailure(action, filePath, error, options = {}, report = null) {
|
|
1004
|
+
const failure = { action, file: filePath, error: error instanceof Error ? error.message : String(error) };
|
|
1005
|
+
if (report) report.failures.push(failure);
|
|
1006
|
+
if (!options.silent) await prompts.log.warn(`Subagent ${action} failed for ${filePath}: ${failure.error}`);
|
|
829
1007
|
}
|
|
830
1008
|
|
|
831
1009
|
/**
|