wizz-method 2.19.1 → 2.20.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "wizz-method",
4
- "version": "2.19.1",
4
+ "version": "2.20.0",
5
5
  "description": "Wizz Method: método de agência orientado por IA em PT-BR",
6
6
  "keywords": [
7
7
  "agile",
@@ -39,7 +39,7 @@ activation_steps_append = []
39
39
  # (the Wizz block imports project-context.md there once per session). Add
40
40
  # `file:{project-root}/**/project-context.md` here only to load it on every activation.
41
41
  persistent_facts = [
42
- "FEATURE: the hooks already inject the active feature ('FEATURE ATIVA: <slug>' or 'Feature: <slug>') or 'nenhuma ativa'. Named work with no active feature: derive the slug from the request's subject and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE writing anything, announcing it in ONE line (never ask for the slug); a different subject switches (definir <new>), a continuation keeps it; a brief's `feature:` field wins. Create artifacts with `wizz-feature.js artefato <tipo>` so they land as <tipo>-<slug>.md in {planning_artifacts}/{active_feature}/ or {implementation_artifacts}/{active_feature}/ with valid frontmatter; project-wide artifacts (architecture, context) stay at the root. Owner decisions go in with `wizz-feature.js decidir`. Memory saved in this session carries the same slug as its theme. Ask only when the request fits two open features. Full rule: wizz module _shared/organizacao.md.",
42
+ "FEATURE: the hooks already inject the active feature ('FEATURE ATIVA: <slug>' or 'Feature: <slug>') or 'nenhuma ativa'. Named work with no active feature: derive the slug from the request's subject and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE writing anything, announcing it in ONE line (never ask for the slug); a new subject goes into the open sprint (the active feature), sized by the work (a small tweak is just done, no story; a multi-step change becomes a story; a new objective becomes an epic), while it fits the sprint's goal; only if it does not fit, ask ONCE: add to this sprint or open another (`definir <new> --novo` only after the owner chooses); a continuation keeps it; a brief's `feature:` field wins. Create artifacts with `wizz-feature.js artefato <tipo>` so they land as <tipo>-<slug>.md in {planning_artifacts}/{active_feature}/ or {implementation_artifacts}/{active_feature}/ with valid frontmatter; project-wide artifacts (architecture, context) stay at the root. Owner decisions go in with `wizz-feature.js decidir`. Memory saved in this session carries the same slug as its theme. A question or conversation records nothing. Full rule: wizz module _shared/organizacao.md.",
43
43
  ]
44
44
 
45
45
  role = "Help the user ideate research and analyze before committing to a project in the Wizz Method analysis phase."
@@ -27,7 +27,7 @@ Invoke this skill only for the explicit cases in "When this skill runs" below.
27
27
  3. **The request itself.** When 1 and 2 are both empty and the request is named work (a feature, a batch, a screen, a page, a campaign, a bug with a scope: "vamos fazer o painel de assinaturas"). Derive the slug from the subject, 2 to 4 words, and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE dispatching; say so in **one line**. Do not ask. This step is mandatory for the router and the maestro, not a nice-to-have: skipping it is how a project spends its whole life with an empty `active_feature`.
28
28
  4. **Nothing.** Only for work that is not named (a question, a conversation, a one-line tweak with no artifact). Artifacts go to the roots and memory carries no feature tag.
29
29
 
30
- **Switch or keep.** With a feature active, a request about a different subject switches (`definir <new>`; the previous one stays open until closed) and a continuation keeps it. **Close** when the delivery closes (deploy or acceptance): `wizz-feature.js verificar` must pass, then `wizz-feature.js encerrar --cerebro [--proxima <slug>]` marks the spec `no-ar`, clears or switches `active_feature`, and writes the Cérebro (state + one Sessions line; without a vault it prints the instruction instead).
30
+ **The open sprint rules.** The active feature is the open sprint: every request goes into it until it is closed, with no new folder. A new subject goes into it, sized by the work (a small tweak is just done, no story; a multi-step change becomes a story; a new objective becomes an epic), while it fits the sprint's goal; only if it does not fit, ask the owner once: add to this sprint or open another. `definir <other>` with a sprint open is refused unless `--novo` (the owner's choice). **Close** when the delivery closes (deploy or acceptance): `wizz-feature.js verificar` must pass, then `wizz-feature.js encerrar --cerebro [--proxima <slug>]` marks the spec `no-ar`, moves the sprint's folders (and the root `sprint-status.yaml`) to `_arquivo/<slug>/`, clears or switches `active_feature`, and writes the Cérebro (state + one Sessions line; without a vault it prints the instruction instead).
31
31
 
32
32
  **Optional branch.** `feature_branches: auto` in `_wizz/bmm/config.yaml` makes `definir` (and the SessionStart hook) propose `git switch -c feat/<slug>` when you are on a trunk branch of a git repo. The default is `off`, for whoever works directly on `main`.
33
33
 
@@ -36,7 +36,7 @@ Invoke this skill only for the explicit cases in "When this skill runs" below.
36
36
  | Command | What it does |
37
37
  |---|---|
38
38
  | `resolver` | Active feature and its source (branch, config, none). |
39
- | `definir <slug or name>` | Normalizes, writes `active_feature`, creates both subfolders, prints one line. |
39
+ | `definir <slug or name> [--novo]` | Normalizes, writes `active_feature`, creates both subfolders, prints one line. With another sprint open it refuses unless `--novo` (owner's choice). |
40
40
  | `artefato <tipo>` | Creates `<tipo>-<slug>.md` (spec, plano, auditoria, story, review, roteiro) in the right folder with valid frontmatter. |
41
41
  | `decidir "<texto>"` | Appends `D-NN` with the date to the feature's spec and to its `decisoes:` frontmatter. |
42
42
  | `status <status>` | Sets the spec's status: `rascunho`, `aprovado`, `implementado` or `no-ar` (it writes; to only read, use `resolver` or `listar`). |
@@ -39,7 +39,7 @@ activation_steps_append = []
39
39
  # (the Wizz block imports project-context.md there once per session). Add
40
40
  # `file:{project-root}/**/project-context.md` here only to load it on every activation.
41
41
  persistent_facts = [
42
- "FEATURE: the hooks already inject the active feature ('FEATURE ATIVA: <slug>' or 'Feature: <slug>') or 'nenhuma ativa'. Named work with no active feature: derive the slug from the request's subject and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE writing anything, announcing it in ONE line (never ask for the slug); a different subject switches (definir <new>), a continuation keeps it; a brief's `feature:` field wins. Create artifacts with `wizz-feature.js artefato <tipo>` so they land as <tipo>-<slug>.md in {planning_artifacts}/{active_feature}/ or {implementation_artifacts}/{active_feature}/ with valid frontmatter; project-wide artifacts (architecture, context) stay at the root. Owner decisions go in with `wizz-feature.js decidir`. Memory saved in this session carries the same slug as its theme. Ask only when the request fits two open features. Full rule: wizz module _shared/organizacao.md.",
42
+ "FEATURE: the hooks already inject the active feature ('FEATURE ATIVA: <slug>' or 'Feature: <slug>') or 'nenhuma ativa'. Named work with no active feature: derive the slug from the request's subject and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE writing anything, announcing it in ONE line (never ask for the slug); a new subject goes into the open sprint (the active feature), sized by the work (a small tweak is just done, no story; a multi-step change becomes a story; a new objective becomes an epic), while it fits the sprint's goal; only if it does not fit, ask ONCE: add to this sprint or open another (`definir <new> --novo` only after the owner chooses); a continuation keeps it; a brief's `feature:` field wins. Create artifacts with `wizz-feature.js artefato <tipo>` so they land as <tipo>-<slug>.md in {planning_artifacts}/{active_feature}/ or {implementation_artifacts}/{active_feature}/ with valid frontmatter; project-wide artifacts (architecture, context) stay at the root. Owner decisions go in with `wizz-feature.js decidir`. Memory saved in this session carries the same slug as its theme. A question or conversation records nothing. Full rule: wizz module _shared/organizacao.md.",
43
43
  ]
44
44
 
45
45
  role = "Translate product vision into a validated PRD, epics, and stories that development can execute during the Wizz Method planning phase."
@@ -39,7 +39,7 @@ activation_steps_append = []
39
39
  # (the Wizz block imports project-context.md there once per session). Add
40
40
  # `file:{project-root}/**/project-context.md` here only to load it on every activation.
41
41
  persistent_facts = [
42
- "FEATURE: the hooks already inject the active feature ('FEATURE ATIVA: <slug>' or 'Feature: <slug>') or 'nenhuma ativa'. Named work with no active feature: derive the slug from the request's subject and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE writing anything, announcing it in ONE line (never ask for the slug); a different subject switches (definir <new>), a continuation keeps it; a brief's `feature:` field wins. Create artifacts with `wizz-feature.js artefato <tipo>` so they land as <tipo>-<slug>.md in {planning_artifacts}/{active_feature}/ or {implementation_artifacts}/{active_feature}/ with valid frontmatter; project-wide artifacts (architecture, context) stay at the root. Owner decisions go in with `wizz-feature.js decidir`. Memory saved in this session carries the same slug as its theme. Ask only when the request fits two open features. Full rule: wizz module _shared/organizacao.md.",
42
+ "FEATURE: the hooks already inject the active feature ('FEATURE ATIVA: <slug>' or 'Feature: <slug>') or 'nenhuma ativa'. Named work with no active feature: derive the slug from the request's subject and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE writing anything, announcing it in ONE line (never ask for the slug); a new subject goes into the open sprint (the active feature), sized by the work (a small tweak is just done, no story; a multi-step change becomes a story; a new objective becomes an epic), while it fits the sprint's goal; only if it does not fit, ask ONCE: add to this sprint or open another (`definir <new> --novo` only after the owner chooses); a continuation keeps it; a brief's `feature:` field wins. Create artifacts with `wizz-feature.js artefato <tipo>` so they land as <tipo>-<slug>.md in {planning_artifacts}/{active_feature}/ or {implementation_artifacts}/{active_feature}/ with valid frontmatter; project-wide artifacts (architecture, context) stay at the root. Owner decisions go in with `wizz-feature.js decidir`. Memory saved in this session carries the same slug as its theme. A question or conversation records nothing. Full rule: wizz module _shared/organizacao.md.",
43
43
  ]
44
44
 
45
45
  role = "Turn user needs and the PRD into UX design specifications that inform architecture and implementation during the Wizz Method planning phase."
@@ -39,7 +39,7 @@ activation_steps_append = []
39
39
  # (the Wizz block imports project-context.md there once per session). Add
40
40
  # `file:{project-root}/**/project-context.md` here only to load it on every activation.
41
41
  persistent_facts = [
42
- "FEATURE: the hooks already inject the active feature ('FEATURE ATIVA: <slug>' or 'Feature: <slug>') or 'nenhuma ativa'. Named work with no active feature: derive the slug from the request's subject and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE writing anything, announcing it in ONE line (never ask for the slug); a different subject switches (definir <new>), a continuation keeps it; a brief's `feature:` field wins. Create artifacts with `wizz-feature.js artefato <tipo>` so they land as <tipo>-<slug>.md in {planning_artifacts}/{active_feature}/ or {implementation_artifacts}/{active_feature}/ with valid frontmatter; project-wide artifacts (architecture, context) stay at the root. Owner decisions go in with `wizz-feature.js decidir`. Memory saved in this session carries the same slug as its theme. Ask only when the request fits two open features. Full rule: wizz module _shared/organizacao.md.",
42
+ "FEATURE: the hooks already inject the active feature ('FEATURE ATIVA: <slug>' or 'Feature: <slug>') or 'nenhuma ativa'. Named work with no active feature: derive the slug from the request's subject and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE writing anything, announcing it in ONE line (never ask for the slug); a new subject goes into the open sprint (the active feature), sized by the work (a small tweak is just done, no story; a multi-step change becomes a story; a new objective becomes an epic), while it fits the sprint's goal; only if it does not fit, ask ONCE: add to this sprint or open another (`definir <new> --novo` only after the owner chooses); a continuation keeps it; a brief's `feature:` field wins. Create artifacts with `wizz-feature.js artefato <tipo>` so they land as <tipo>-<slug>.md in {planning_artifacts}/{active_feature}/ or {implementation_artifacts}/{active_feature}/ with valid frontmatter; project-wide artifacts (architecture, context) stay at the root. Owner decisions go in with `wizz-feature.js decidir`. Memory saved in this session carries the same slug as its theme. A question or conversation records nothing. Full rule: wizz module _shared/organizacao.md.",
43
43
  ]
44
44
 
45
45
  role = "Convert the PRD and UX into technical architecture decisions that keep implementation on track during the Wizz Method solutioning phase."
@@ -39,7 +39,7 @@ activation_steps_append = []
39
39
  # (the Wizz block imports project-context.md there once per session). Add
40
40
  # `file:{project-root}/**/project-context.md` here only to load it on every activation.
41
41
  persistent_facts = [
42
- "FEATURE: the hooks already inject the active feature ('FEATURE ATIVA: <slug>' or 'Feature: <slug>') or 'nenhuma ativa'. Named work with no active feature: derive the slug from the request's subject and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE writing anything, announcing it in ONE line (never ask for the slug); a different subject switches (definir <new>), a continuation keeps it; a brief's `feature:` field wins. Create artifacts with `wizz-feature.js artefato <tipo>` so they land as <tipo>-<slug>.md in {planning_artifacts}/{active_feature}/ or {implementation_artifacts}/{active_feature}/ with valid frontmatter; project-wide artifacts (architecture, context) stay at the root. Owner decisions go in with `wizz-feature.js decidir`. Memory saved in this session carries the same slug as its theme. Ask only when the request fits two open features. Full rule: wizz module _shared/organizacao.md.",
42
+ "FEATURE: the hooks already inject the active feature ('FEATURE ATIVA: <slug>' or 'Feature: <slug>') or 'nenhuma ativa'. Named work with no active feature: derive the slug from the request's subject and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE writing anything, announcing it in ONE line (never ask for the slug); a new subject goes into the open sprint (the active feature), sized by the work (a small tweak is just done, no story; a multi-step change becomes a story; a new objective becomes an epic), while it fits the sprint's goal; only if it does not fit, ask ONCE: add to this sprint or open another (`definir <new> --novo` only after the owner chooses); a continuation keeps it; a brief's `feature:` field wins. Create artifacts with `wizz-feature.js artefato <tipo>` so they land as <tipo>-<slug>.md in {planning_artifacts}/{active_feature}/ or {implementation_artifacts}/{active_feature}/ with valid frontmatter; project-wide artifacts (architecture, context) stay at the root. Owner decisions go in with `wizz-feature.js decidir`. Memory saved in this session carries the same slug as its theme. A question or conversation records nothing. Full rule: wizz module _shared/organizacao.md.",
43
43
  "When implementing against a library or framework, consult Context7 for current docs, APIs, and version-specific syntax before relying on training data or web search: call resolve-library-id with the library name, then query-docs with the returned /org/project id and a specific question. If Context7 is unavailable, tell the user once that they can enable it with `npx ctx7 setup`, then fall back to web research.",
44
44
  ]
45
45
 
@@ -12,7 +12,8 @@ and pass along only the skill's entrypoint and the references actually needed
12
12
  - area skill: canonical name of the persona to invoke via `Skill` (e.g. `wizz-growth`). Not a `subagent_type`: to isolate the task, pick an available native executor (`wizz-exec-*`) and instruct it to load that skill before working. The absence of an executor doesn't block invoking the skill in the current session.
13
13
  - feature: the active feature slug, resolved BEFORE the handoff (in a Wizz
14
14
  project: `node _wizz/hooks/wizz-feature.js resolver`; named work with no
15
- feature gets one with `definir <slug from the request>`, without asking).
15
+ feature gets one with `definir <slug from the request>`, without asking;
16
+ with a feature active, it is the open sprint and new work goes into it).
16
17
  `feature: nenhuma` is valid only for work that is not named (a question, a
17
18
  conversation). Receiver rule: a brief WITHOUT this field is resolved by the
18
19
  receiver itself with the same steps before it writes anything; it never
@@ -7,27 +7,34 @@ Every step below is a **command**, not a memory exercise. The commands live in t
7
7
  - `F` = `node _wizz/hooks/wizz-feature.js`
8
8
  - `A` = `node _wizz/hooks/wizz-arrumar.js`
9
9
 
10
- `F` subcommands: `resolver` (active feature and its source), `definir <slug>`, `artefato <tipo>`, `decidir "<texto>"`, `status <status>` (sets the spec status), `verificar`, `encerrar`, `listar` (features with a folder and each spec's status). `resolver` and `listar` only read. The `wizz-set-feature` skill is just the manual override over `F definir` (rename, switch or clear on the owner's explicit request); nobody invokes it for the normal flow.
10
+ `F` subcommands: `resolver` (active feature and its source), `definir <slug> [--novo]`, `artefato <tipo>`, `decidir "<texto>"`, `status <status>` (sets the spec status), `verificar`, `encerrar` (closes and archives the sprint), `listar` (open sprints, plus a count of the archived ones). `resolver` and `listar` only read. The `wizz-set-feature` skill is just the manual override over `F definir` (rename, switch or clear on the owner's explicit request); nobody invokes it for the normal flow.
11
11
 
12
- ## 1. Feature before dispatch
12
+ ## 1. The open sprint rules
13
13
 
14
- The SessionStart and UserPromptSubmit hooks already tell you the state (`FEATURE ATIVA: <slug>`, `Feature: <slug>` or `nenhuma ativa`). Before you dispatch anything or write any artifact:
14
+ **The active feature is the open sprint.** One sprint is open at a time, and every piece of work goes into it until it is closed. A new subject does **not** create a new feature or a new folder on its own: dozens of half-used folders is how the Lucrare ended up with 13 features (owner's decision, 2026-10-15).
15
15
 
16
- 1. **Named work and no active feature** (a feature, a batch, a screen, a page, a campaign, a bug with a scope) → derive the slug from the **subject** of the request, 2 to 4 words in kebab-case, never from the verb ("vamos fazer o painel de assinaturas" → painel-assinaturas). Run `F definir <slug>` and tell the owner in **one line**. Never ask for a slug that comes out of the request.
17
- 2. **Active feature, same subject or continuation** ("agora ajusta o filtro do painel") → keep it. Write nothing.
18
- 3. **Active feature, different subject** → `F definir <new-slug>`. The previous one stays open until it is closed; say the switch in the same single line.
19
- 4. **Not named work** (a question, conversation, a trivial one-line tweak that produces no artifact) → do nothing.
20
- 5. **Real ambiguity** (the request fits two open features, see `F listar`) → the only case where you ask, once, offering the open features as options.
21
- 6. **Feature branch** (`feat/<slug>`) → the branch already is the feature; when the config disagrees, `F definir <branch-slug>`.
16
+ The SessionStart and UserPromptSubmit hooks already tell you the state (`FEATURE ATIVA: <slug>`, `Sprint aberto: <slug>` or `nenhuma ativa`). Before you dispatch anything or write any artifact:
17
+
18
+ 1. **Work and no open sprint** (a feature, a batch, a screen, a page, a campaign, a bug with a scope) → derive the slug from the **subject** of the request, 2 to 4 words in kebab-case, never from the verb ("vamos fazer o painel de assinaturas" → painel-assinaturas). Run `F definir <slug>` and tell the owner in **one line**. Never ask for a slug that comes out of the request.
19
+ 2. **Open sprint, same subject or continuation** ("agora ajusta o filtro do painel") → keep it. Write nothing.
20
+ 3. **Open sprint, new subject that fits its goal** (same product, same objective) → it goes into the open sprint, sized by the work. No `definir`, no new folder, one line to the owner:
21
+ - **small** (a colour, a text, one adjustment, no planning) → just do it; no story;
22
+ - **medium** (several steps, needs testing and review) → a story in the open sprint;
23
+ - **large** (a new objective inside the same product) → an epic with its stories.
24
+ 4. **Open sprint, new subject that clearly does not fit** (a separate objective, another product) → the only case where you ask, **once**: "add to `<slug>` or open a new sprint?". Only after the owner chooses a new one: `F definir <new-slug> --novo`. Without `--novo`, `definir` refuses while a sprint is open.
25
+ 5. **Not work** (a question, conversation, a trivial one-line tweak that produces no artifact) → do nothing.
26
+ 6. **Feature branch** (`feat/<slug>`) → the branch already is the sprint; when the config disagrees, `F definir <branch-slug> --novo`.
22
27
  7. **`feature_branches: auto`** in the config and you are on a trunk branch → propose `git switch -c feat/<slug>` in one line. With `off` (the default, for whoever works on `main`) say nothing about branches.
23
28
 
29
+ **Closing archives.** `F encerrar` moves the sprint's two folders to `_arquivo/<slug>/` inside each artifact folder, together with the root `{implementation_artifacts}/sprint-status.yaml`, so the next sprint starts clean. `F listar` shows the open sprints and one line with the number of archived ones. The archive is history, not trash: when a request needs a past decision, spec or retro ("how did we solve the Meta sync?"), search `_arquivo/` of both artifact folders (and the Cérebro, which points to it) before saying it does not exist.
30
+
24
31
  **Handoff:** every brief carries `feature: <slug>` (or `feature: nenhuma` for work that is not named). A receiver without that field resolves it itself with the steps above before writing anything; it never writes an artifact without a resolved feature.
25
32
 
26
33
  ## 2. Artifacts in the right place
27
34
 
28
35
  - Create planning and implementation artifacts with `F artefato <tipo>` (`spec`, `plano`, `auditoria`, `story`, `review`, `roteiro`). It writes `<tipo>-<slug>.md` under `{planning_artifacts}/{feature}/` or `{implementation_artifacts}/{feature}/` (the `roteiro` goes to `{project_knowledge}/`), with the frontmatter already valid: `feature`, `status` (`rascunho|aprovado|implementado|no-ar`), `atualizado_em` (`YYYY-MM-DD`), `decisoes` (`[D-..]`).
29
36
  - Project-wide artifacts (architecture, project context) stay at the root of the artifact folders.
30
- - The `wizz-artifact-guard.js` hook warns after a Write that lands outside the active feature's folder or without a feature, and after a Write or Edit that leaves the frontmatter broken. **Fix it at once** (move the file, run `F definir`, fix the frontmatter); never leave the warning for later.
37
+ - The `wizz-artifact-guard.js` hook warns after a Write that lands outside the active feature's folder or without a feature, and after a Write or Edit that leaves the frontmatter broken. **Fix it at once** (move the file into the open sprint's folder, fix the frontmatter; run `F definir` only when no sprint is open); never leave the warning for later.
31
38
 
32
39
  ## 3. Project root organized from the start
33
40
 
@@ -69,7 +69,7 @@ If the user already stated the intent, **classify and dispatch directly** (see R
69
69
 
70
70
  ## Feature and organization (before routing, every time)
71
71
 
72
- Before you route, dispatch or write anything, resolve the feature with the steps in [organizacao.md](../../_shared/organizacao.md) section 1: named work with no active feature → `node _wizz/hooks/wizz-feature.js definir <slug from the request>` and **one line** to the owner (never ask for the slug); a different subject switches (`definir <new>`), a continuation keeps it. With no `layout:` in the config, propose and record it (`node _wizz/hooks/wizz-arrumar.js layout`, then `layout --gravar`) in the same single line. Every brief you hand over carries `feature:`.
72
+ Before you route, dispatch or write anything, resolve the feature with the steps in [organizacao.md](../../_shared/organizacao.md) section 1: named work with no active feature → `node _wizz/hooks/wizz-feature.js definir <slug from the request>` and **one line** to the owner (never ask for the slug); the active feature is the open sprint: a new subject goes into it, sized by the work (a small tweak is just done, no story; a multi-step change becomes a story; a new objective becomes an epic), while it fits the sprint's goal; only if it does not fit, ask ONCE: add to this sprint or open another (`definir <new> --novo` only after the owner chooses); a continuation keeps it. With no `layout:` in the config, propose and record it (`node _wizz/hooks/wizz-arrumar.js layout`, then `layout --gravar`) in the same single line. Every brief you hand over carries `feature:`.
73
73
 
74
74
  While the sequence runs you keep the **documentation duty** (section 4 of the same file) without being asked: scope approved → spec with acceptance criteria; each owner decision → `wizz-feature.js decidir`; implementation done → the spec's `## Implementação`; going live → roteiro with rollback in `{project_knowledge}/`. Closing a feature's delivery runs the checklist (`wizz-feature.js verificar`, section 5): **you do not close the sequence with a pending item.**
75
75
 
@@ -107,7 +107,7 @@ In short: **the menu chooses the agent, the registry chooses the skill(s) and th
107
107
 
108
108
  Inside the 1-area branch, three shortcuts **demote on their own**, before counting any factor: a **question with a single definable answer**, a **single delivery**, and the **diagnosis of one specific mechanism** all go to the area agent. "How do I improve / reduce X" is not that question — it has no single answer. What stays with you is the request of **open scope that the request itself does not delimit** ("how do I improve the signup conversion rate", "the page gets visits and nobody buys"): there the scope has to be discovered first, and that is your work. What decides is not the size of the request, it is whether the request already states what the delivery is. **Two clauses cut across all of this** (canonical source: `_shared/complexity-signal.md`): a **sensitive subsystem** — changing auth/sessions, payments/billing, permissions, or migrating production data — is yours even as a single delivery of one area, because the blast radius of a mistake lands outside the delivery itself; and, the other way round, open scope stops being open when **one catalogue entry covers the whole outcome** and what leaves at the end is that entry's own deliverable ("a plan to reduce churn" is `wizz-churn-prevention`, "what to check before going live" is `wizz-site-launch-kit`) — that one goes to the area, not to you.
109
109
 
110
- **Changing the product's code is `dev`, even when the subject is visual** — the delivery is a commit. Ask which artifact leaves at the end: a file in the repo → dev; a decision, a spec or a piece of art → the area that owns it. **Exception: new or rebuilt screens.** When the request asks to BUILD screens in code (a new app, page, dashboard or panel, or a whole screen rebuilt in the product), the visual decision has not been made yet, so `designer` comes first and the sequence is design → dev: the designer runs its research gate (`wizz-design-needs` → `wizz-ui-component-curator` / `wizz-premium-landing-ui-researcher`) before any screen is coded. A generic design skill from outside the registry (e.g. the frontend-design plugin) never stands in for that gate. A tweak to an existing screen (a price, a label, a spacing) stays dev, and redesigning a section with no build asked for (a hero, a card) stays designer.
110
+ **Changing the product's code is `dev`, even when the subject is visual** — the delivery is a commit. Ask which artifact leaves at the end: a file in the repo → dev; a decision, a spec or a piece of art → the area that owns it. **Exception: new or rebuilt screens.** When the request asks to BUILD screens in code (a new app, page, dashboard or panel, or a whole screen rebuilt in the product), the visual decision has not been made yet, so `designer` comes first and the sequence is design → dev: the designer runs its research gate (`wizz-design-needs` → `wizz-ui-component-curator` / `wizz-premium-landing-ui-researcher`) before any screen is coded. A generic design skill from outside the registry (e.g. the frontend-design plugin) never stands in for that gate. A tweak to an existing screen (a price, a label, a spacing) stays dev, and so does building one component in code ("create a card component", a button, a modal): the delivery is a file in the repo. Redesigning a section with no build asked for ("redesign the hero") or choosing which component fits ("find a card that matches our style") stays designer.
111
111
 
112
112
  **What `rel` drags counts as an extra area when the related capability shapes the SAME deliverable.** Ask of each edge: would the thing being handed over be incomplete without this? A sales page without the offer and the conversion structure is half a page (same artifact → escalates); a text post does not become a video job because of a `before: [get-brolls]` edge (different artifact → does not). `pairs_with` alone never escalates.
113
113
 
@@ -23,7 +23,7 @@ include = [
23
23
 
24
24
  persistent_facts = [
25
25
  "Chaining mode: AUTOMATIC. I announce the full sequence at the start and run the agents in order, without stopping between them. I only pause for a business decision that belongs to the user or an irreversible risk (deploy, delete, spending).",
26
- "Organization duty (before dispatch, without being asked): named work with no active feature -> `node _wizz/hooks/wizz-feature.js definir <slug from the request>` and one line; different subject -> definir <new>; continuation -> keep. Every handoff carries `feature:`. Scope approved -> spec with acceptance criteria; owner decision -> `decidir`; implemented -> Implementação section; live -> roteiro + rollback; closing -> `verificar` must pass before `encerrar`. I never close a sequence with a pending item. Details in _shared/organizacao.md.",
26
+ "Organization duty (before dispatch, without being asked): named work with no active feature -> `node _wizz/hooks/wizz-feature.js definir <slug from the request>` and one line; new subject -> enters the open sprint while it fits (small tweak: just do it; multi-step: story; new objective: epic); does not fit -> ask once: add here or open another (`definir <new> --novo` only after the owner chooses); question or conversation -> nothing. Every handoff carries `feature:`. Scope approved -> spec with acceptance criteria; owner decision -> `decidir`; implemented -> Implementação section; live -> roteiro + rollback; closing -> `verificar` must pass before `encerrar`. I never close a sequence with a pending item. Details in _shared/organizacao.md.",
27
27
  "Planning gate: on an EXECUTION request where the 'needs planning' factor = YES and there's no matching PRD/story/brief, I ask once whether to create the artifact first (recommended) or skip it; once the gate is resolved, the chain proceeds automatically. Details in _shared/planning-gate.md.",
28
28
  "I speak easy {communication_language}, short sentences, no jargon. I summarize as if explaining to a client.",
29
29
  "Full details of the Wizz layer are in _shared/ (communication-rules.md, encerramento.md, token-economy.md, cerebro.md) at the root of the wizz module.",
@@ -35,7 +35,7 @@ Written as bare labels, the 3 factors above were true of almost any request, and
35
35
 
36
36
  **And the counterpart has one exception: a single entry already delimits the scope.** If the whole outcome being asked for fits the `when:` of ONE catalogue entry, and what leaves at the end is that entry's own deliverable — a plan, a recommendation, an audit — then the scope is not open, the catalogue delimited it, and the request goes to that entry's area. "A plan to reduce churn" is `wizz-churn-prevention`; "I want ChatGPT and Perplexity to cite us" is `wizz-ai-seo`; "what to check before going live" is `wizz-site-launch-kit`. It stops being the exception the moment delivering the outcome means changing what other areas own: "improve the signup conversion rate" ends up in copy, UI and code, so it comes back to the maestro through the area clause. Ask which single entry would deliver it whole — if it takes two, the scope really is open. (Owner's decision, 2026-09-18.)
37
37
 
38
- **Changing the product's code is `dev`, even when the subject is visual.** "Adjust the secondary button's colour", "fix the mobile spacing": the delivery is a commit, so it is the dev area — `designer` decides what the look should be, it does not ship the change. Ask which artifact leaves at the end: a file in the repo → dev; a decision, a spec or a piece of art → the area that owns it. **Exception: new or rebuilt screens.** When the request asks to BUILD screens in code (a new app, page, dashboard or panel, or a whole screen rebuilt in the product), the visual decision has not been made yet, so `designer` comes first and the sequence is design → dev (2 areas → maestro): the designer runs its research gate before any screen is coded. A tweak to an existing screen (a price, a label, a spacing) stays dev, and redesigning a section with no build asked for (a hero, a card) stays designer.
38
+ **Changing the product's code is `dev`, even when the subject is visual.** "Adjust the secondary button's colour", "fix the mobile spacing": the delivery is a commit, so it is the dev area — `designer` decides what the look should be, it does not ship the change. Ask which artifact leaves at the end: a file in the repo → dev; a decision, a spec or a piece of art → the area that owns it. **Exception: new or rebuilt screens.** When the request asks to BUILD screens in code (a new app, page, dashboard or panel, or a whole screen rebuilt in the product), the visual decision has not been made yet, so `designer` comes first and the sequence is design → dev (2 areas → maestro): the designer runs its research gate before any screen is coded. A tweak to an existing screen (a price, a label, a spacing) stays dev, and so does building one component in code ("create a card component", a button, a modal): the delivery is a file in the repo. Redesigning a section with no build asked for ("redesign the hero") or choosing which component fits ("find a card that matches our style") stays designer.
39
39
 
40
40
  **What `rel` drags counts as an extra area when the related capability shapes the SAME deliverable.** Writing the copy of a sales page without the offer and without the conversion structure delivers half a page: offer, copy and conversion are the same artifact, so that is 2 areas → maestro. What does NOT count is an adjacent capability that would produce a **different** artifact: a plain text post for LinkedIn does not need video capture just because the social content skill declares `before: [get-brolls]` — the b-roll would be another deliverable, not part of this one. `pairs_with` alone never escalates: it is neighbourhood, not dependency. The question to ask about each edge is "would the thing I am about to hand over be incomplete without this?", not "is this related?".
41
41
 
@@ -12,7 +12,8 @@ Invoke this skill BEFORE acting on non-trivial requests (feature, bug, design, m
12
12
 
13
13
  **Single exception:** 1-line edits (typo, rename) and pure conversation. **Objective criterion (TRIVIALITY GATE):** a request touching ≤1 file, ~≤10 lines, no new behavior, a known type of change → execute directly, without triggering the router/maestro.
14
14
 
15
- **Outside Plan Mode:** list candidates via `AskUserQuestion` and wait for confirmation BEFORE firing. Never auto-fire outside Plan Mode.
15
+ **Outside Plan Mode, outside a Wizz project (section B):** list candidates via `AskUserQuestion` and wait for confirmation BEFORE firing. Never auto-fire there.
16
+ **Inside a Wizz project (section A):** do NOT ask to confirm the dispatch. Resolve the feature, then hand off at once; the only questions you may ask are the ambiguous-referent gate below and, with a sprint open, "add to this sprint or open another" when the new subject does not fit it.
16
17
  **Inside Plan Mode:** auto-fire every relevant one to enrich the plan.
17
18
  </EXTREMELY-IMPORTANT>
18
19
 
@@ -74,7 +75,7 @@ This gate does NOT fire for an open-scope request. "My site is bad, sort it out"
74
75
 
75
76
  **And the counterpart has one exception: a single entry already delimits the scope.** If the whole outcome being asked for fits the `when:` of ONE catalogue entry, and what leaves at the end is that entry's own deliverable — a plan, a recommendation, an audit — then the scope is not open, the catalogue delimited it, and the request goes to that entry's area. "A plan to reduce churn" is `wizz-churn-prevention`; "I want ChatGPT and Perplexity to cite us" is `wizz-ai-seo`; "what to check before going live" is `wizz-site-launch-kit`. It stops being the exception the moment delivering the outcome means changing what other areas own: "improve the signup conversion rate" ends up in copy, UI and code, so it comes back to the maestro through the area clause. Ask which single entry would deliver it whole — if it takes two, the scope really is open. (Owner's decision, 2026-09-18.)
76
77
 
77
- **Changing the product's code is `dev`, even when the subject is visual.** "Adjust the secondary button's colour", "fix the mobile spacing": the delivery is a commit, so it is the dev area — `designer` decides what the look should be, it does not ship the change. Ask which artifact leaves at the end: a file in the repo → dev; a decision, a spec or a piece of art → the area that owns it. **Exception: new or rebuilt screens.** When the request asks to BUILD screens in code (a new app, page, dashboard or panel, or a whole screen rebuilt in the product), the visual decision has not been made yet, so `designer` comes first and the sequence is design → dev (2 areas → maestro): the designer runs its research gate before any screen is coded. A tweak to an existing screen (a price, a label, a spacing) stays dev, and redesigning a section with no build asked for (a hero, a card) stays designer.
78
+ **Changing the product's code is `dev`, even when the subject is visual.** "Adjust the secondary button's colour", "fix the mobile spacing": the delivery is a commit, so it is the dev area — `designer` decides what the look should be, it does not ship the change. Ask which artifact leaves at the end: a file in the repo → dev; a decision, a spec or a piece of art → the area that owns it. **Exception: new or rebuilt screens.** When the request asks to BUILD screens in code (a new app, page, dashboard or panel, or a whole screen rebuilt in the product), the visual decision has not been made yet, so `designer` comes first and the sequence is design → dev (2 areas → maestro): the designer runs its research gate before any screen is coded. A tweak to an existing screen (a price, a label, a spacing) stays dev, and so does building one component in code ("create a card component", a button, a modal): the delivery is a file in the repo. Redesigning a section with no build asked for ("redesign the hero") or choosing which component fits ("find a card that matches our style") stays designer.
78
79
 
79
80
  **The area count includes what `rel` drags, when the related capability shapes the SAME deliverable.** Before deciding "1 area", look at the `rel` of the entry that matched, and ask of each edge: **would the thing I am about to hand over be incomplete without this?** — not "is this related?". Writing the copy of a sales page without the offer and without the conversion structure delivers half a page: offer, copy and conversion are the same artifact, so that is 2 areas, hence maestro. What does NOT count is an adjacent capability that would produce a **different** artifact: a plain text post for LinkedIn does not need video capture just because `wizz-social-content` declares `before: [get-brolls]` — the b-roll would be another deliverable, not part of this one. `pairs_with` alone never escalates: it is neighbourhood, not dependency. Every edge is reciprocal by construction (the schema validator rejects one-way edges), so the relation shows up whichever side the user enters from. Resolve id → area via the `ids:` map in `{project-root}/_wizz/_config/registry/index.yaml`; outside a Wizz project, via `skills-registry.yaml` itself.
80
81
 
@@ -89,7 +90,7 @@ This gate does NOT fire for an open-scope request. "My site is bad, sort it out"
89
90
  - *1 area, but 2+ factors → maestro:* `m06` ("infra refactor: database, APIs, deploy, monitoring"): 1 area (dev), but multi-step + needs planning + high-risk architecture → `wizz-maestro` even without crossing areas.
90
91
  - *2+ areas, each part light → still maestro (clause 1):* e.g. "adjust the secondary button's color AND change one word in the CTA": each isolated change is trivial, but it crosses 2 areas (designer + copy) in the same request, so it falls under clause 1 and goes to the maestro even without heavy multi-step work. The current dataset has no equivalent case (`m01`-`m12` always combine 2+ areas with high complexity); it's the calibration gap flagged by finding M1.
91
92
 
92
- **Feature before delegating (mandatory, verifiable):** named work (a feature, a batch, a screen, a page, a campaign, a bug with a scope) with no active feature → derive the slug from the request's subject and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE handing off, with one line to the owner and no question; a different subject from the active feature → `definir <new>`; a continuation keeps it; a question or conversation writes nothing. Full rule in the wizz module's `_shared/organizacao.md`, section 1.
93
+ **Feature before delegating (mandatory, verifiable):** named work (a feature, a batch, a screen, a page, a campaign, a bug with a scope) with no active feature → derive the slug from the request's subject and run `node _wizz/hooks/wizz-feature.js definir <slug>` BEFORE handing off, with one line to the owner and no question; with a feature active, it is the open sprint and the work goes into it: a new subject goes into it, sized by the work (a small tweak is just done, no story; a multi-step change becomes a story; a new objective becomes an epic), while it fits the sprint's goal; only if it does not fit, ask ONCE: add to this sprint or open another (`definir <new> --novo` only after the owner chooses); a continuation keeps it; a question or conversation writes nothing. Full rule in the wizz module's `_shared/organizacao.md`, section 1.
93
94
 
94
95
  When delegating (to the maestro or to the area agent), state the brief in the format of the [shared handoff protocol](../../core-skills/_shared/handoff-protocol.md): `feature: <slug>` (or `feature: nenhuma`), `origem: wizz-router` (anti-loop — whoever receives it never sends the same request back to you), a cerebro summary if one has already been consulted, decisions already made, and the relevant section of the skill. Also include the **verdict on the 4 factors** — especially `precisa planejar: sim/não` — so the receiver can apply the Planning Gate (wizz module, `_shared/planning-gate.md`) without re-deriving it: with the planning factor set to YES and no corresponding PRD/story/brief, the receiver asks once whether to create the artifact before executing; once the gate is resolved, the chain proceeds automatically.
95
96
 
@@ -87,8 +87,8 @@ function wizzReminders(state) {
87
87
  const out = [];
88
88
  if (!state.slug) {
89
89
  out.push(
90
- `NENHUMA FEATURE ATIVA: o primeiro pedido nomeado (feature, lote, tela, bug com escopo) define uma. ` +
91
- `Antes de despachar, derive o slug do pedido e grave: ${HOOK_CMD} definir <slug>. Sem perguntar; uma linha de confirmação.`,
90
+ `NENHUMA FEATURE ATIVA (nenhum sprint aberto): o primeiro pedido de trabalho (feature, lote, tela, bug com escopo) abre um. ` +
91
+ `Antes de despachar, derive o slug do pedido e grave: ${HOOK_CMD} definir <slug>. Sem perguntar; uma linha de confirmação. Pergunta ou conversa não abre nada.`,
92
92
  );
93
93
  } else if (state.featureBranches === 'auto' && state.git.isRepo && state.onTrunk) {
94
94
  out.push(
@@ -120,7 +120,7 @@ function buildFeatureContext(cwd) {
120
120
 
121
121
  if (active) {
122
122
  parts.push(
123
- `FEATURE ATIVA: ${active}` + (fromBranch ? ' (derivada do branch git).' : ' (do config).'),
123
+ `FEATURE ATIVA: ${active}` + (fromBranch ? ' (sprint aberto, derivado do branch git).' : ' (sprint aberto, do config).'),
124
124
  'Use como contexto sem perguntar nada:',
125
125
  state.isWizz
126
126
  ? `artefatos de planejamento e implementação vão para as subpastas ${active}/ com nome <tipo>-<slug>.md (${HOOK_CMD} artefato <tipo>);`
@@ -133,7 +133,7 @@ function buildFeatureContext(cwd) {
133
133
  );
134
134
  }
135
135
  parts.push(
136
- 'NÃO peça confirmação de feature. Pedido de OUTRO assunto troca a feature (definir <novo>); continuação mantém. Pergunte só se houver ambiguidade real entre duas features abertas.',
136
+ 'NÃO peça confirmação de feature. O sprint aberto manda: todo trabalho entra nele até ser encerrado, sem pasta nova. Assunto novo que cabe no objetivo do sprint entra pelo tamanho: ajuste pequeno só faz (sem story), vários passos vira story, objetivo novo vira épico; avise em uma linha. Histórico de sprints fechados fica em _arquivo/ das pastas de artefato: procure lá quando precisar. Só se NÃO couber, pergunte UMA vez: adicionar neste sprint ou abrir outro (definir <novo> --novo, só com a escolha do dono). Pergunta ou conversa não grava nada.',
137
137
  );
138
138
  }
139
139
  if (state.isWizz) parts.push(...wizzReminders(state));
@@ -87,15 +87,16 @@ function artifactWarning(file, content, state, { frontmatterOnly = false } = {})
87
87
  `Grave a feature a partir do pedido (node ${HOOK_CMD}/wizz-feature.js definir <slug>) e mova o arquivo para a subpasta dela.`,
88
88
  );
89
89
  }
90
- } else if (!frontmatterOnly && slug && parts[0] !== slug) {
90
+ } else if (!frontmatterOnly && slug && parts[0] !== slug && parts[0] !== lib.ARCHIVE_DIR) {
91
91
  msgs.push(
92
- `ARTEFATO EM OUTRA FEATURE: ${relFile} está em "${parts[0]}", mas a ativa é "${slug}". ` +
93
- `Se o trabalho é de "${slug}", mova; se o assunto mudou, troque a feature (wizz-feature.js definir ${parts[0]}).`,
92
+ `ARTEFATO EM OUTRO SPRINT: ${relFile} está em "${parts[0]}", mas o sprint aberto é "${slug}". ` +
93
+ `Trabalho novo entra no sprint aberto: mova para ${path.relative(state.root, path.join(base, slug, name))}. Abrir outro sprint só com a escolha do dono (wizz-feature.js definir <slug> --novo).`,
94
94
  );
95
95
  }
96
96
 
97
97
  if (type) {
98
- const problems = lib.frontmatterProblems(content, parts.length > 1 ? parts[0] : slug);
98
+ const owner = parts[0] === lib.ARCHIVE_DIR ? parts[1] : parts[0];
99
+ const problems = lib.frontmatterProblems(content, parts.length > 1 ? owner : slug);
99
100
  if (problems.length > 0) msgs.push(`FRONTMATTER de ${relFile}: ${problems.join('; ')}.`);
100
101
  }
101
102
  return msgs.join(' ');
@@ -213,7 +213,11 @@ function recordClose({ root, slug, status, projeto }) {
213
213
 
214
214
  const date = lib.today();
215
215
  const dirs = lib.artifactDirs(root);
216
- const spec = path.join(dirs.planning, slug, `spec-${slug}.md`);
216
+ // O `encerrar` arquiva o sprint antes de chamar aqui: procura nas duas casas.
217
+ const spec =
218
+ [path.join(dirs.planning, lib.ARCHIVE_DIR, slug), path.join(dirs.planning, slug)]
219
+ .map((d) => path.join(d, `spec-${slug}.md`))
220
+ .find((f) => fs.existsSync(f)) || path.join(dirs.planning, slug, `spec-${slug}.md`);
217
221
  const specText = fs.existsSync(spec) ? fs.readFileSync(spec, 'utf8') : '';
218
222
  const specRel = path.relative(root, spec);
219
223
  const resumo = implementationSummary(specText);
@@ -41,6 +41,9 @@ const PROJECT_WIDE = /^(architecture|arquitetura|project-context|contexto|sprint
41
41
 
42
42
  const CONFIG_REL = path.join('_wizz', 'bmm', 'config.yaml');
43
43
 
44
+ // Arquivo morto dos sprints fechados, dentro de cada pasta de artefato.
45
+ const ARCHIVE_DIR = '_arquivo';
46
+
44
47
  /**
45
48
  * Normaliza texto livre para slug kebab-case: o mesmo formato que a skill
46
49
  * `wizz-set-feature` grava, para branch e config nunca divergirem.
@@ -384,12 +387,48 @@ function sectionContent(text, title) {
384
387
  .trim();
385
388
  }
386
389
 
390
+ // Link markdown relativo: `](alvo)` ou `](alvo#ancora)`, sem esquema nem âncora pura.
391
+ const MD_LINK = /\]\((?!(?:[a-z][\w+.-]*:|#|\/))([^)\s#?]+)([^)\s]*)\)/gi;
392
+
393
+ /**
394
+ * Reescreve os links markdown relativos de um arquivo que mudou de pasta.
395
+ * Cada alvo é resolvido a partir da pasta ANTIGA, passa por `mapAbs` (o alvo
396
+ * também pode ter sido movido) e volta relativo à pasta NOVA. Sem mudança
397
+ * devolve o mesmo texto; link quebrado (alvo inexistente) fica intocado.
398
+ * @param {string} text
399
+ * @param {string} oldDir - pasta antiga do arquivo (absoluta)
400
+ * @param {string} newDir - pasta nova do arquivo (absoluta)
401
+ * @param {(abs: string) => string} [mapAbs] - caminho atual de um alvo movido
402
+ * @returns {string}
403
+ */
404
+ function rebaseMarkdownLinks(text, oldDir, newDir, mapAbs = (p) => p) {
405
+ return String(text).replaceAll(MD_LINK, (whole, target, rest) => {
406
+ let decoded;
407
+ try {
408
+ decoded = decodeURI(target);
409
+ } catch {
410
+ return whole;
411
+ }
412
+ const original = path.resolve(oldDir, decoded);
413
+ const abs = mapAbs(original);
414
+ // Link que já não apontava para nada fica como está: reescrever inventaria outro caminho.
415
+ if (!fs.existsSync(original) && !fs.existsSync(abs)) return whole;
416
+ const next = path.relative(newDir, abs).split(path.sep).join('/') || '.';
417
+ if (next === decoded) return whole;
418
+ // Mantém o estilo do link: cru fica cru; com acento cru, só o espaço é codificado.
419
+ let encoded = next;
420
+ if (target !== decoded) encoded = /[\u0080-\uFFFF]/.test(target) ? next.replaceAll(' ', '%20') : encodeURI(next);
421
+ return `](${encoded}${rest})`;
422
+ });
423
+ }
424
+
387
425
  module.exports = {
388
426
  TRUNK,
389
427
  ARTIFACT_TYPES,
390
428
  STATUS,
391
429
  PROJECT_WIDE,
392
430
  CONFIG_REL,
431
+ ARCHIVE_DIR,
393
432
  toSlug,
394
433
  today,
395
434
  findUp,
@@ -411,4 +450,5 @@ module.exports = {
411
450
  frontmatterProblems,
412
451
  artifactTypeOf,
413
452
  sectionContent,
453
+ rebaseMarkdownLinks,
414
454
  };
@@ -7,16 +7,20 @@
7
7
  // Spider: a regra "derive do pedido" dependia de memória do agente).
8
8
  //
9
9
  // resolver feature ativa e de onde veio (branch > config > nada)
10
- // definir <slug|texto> grava active_feature e cria as subpastas (sem perguntar)
10
+ // definir <slug|texto> abre o sprint: grava active_feature e cria as subpastas
11
+ // (sem perguntar). Com sprint aberto e outro slug, recusa:
12
+ // o trabalho novo entra no sprint aberto; abrir outro só
13
+ // com a escolha do dono (--novo)
11
14
  // artefato <tipo> cria <tipo>-<slug>.md com frontmatter no lugar certo
12
15
  // decidir "<texto>" registra D-NN na spec da feature, na hora da decisão
13
16
  // status <status> rascunho|aprovado|implementado|no-ar na spec
14
17
  // verificar checklist de fechamento (exit 1 se houver pendência)
15
- // encerrar status final na spec, limpa/troca active_feature
18
+ // encerrar status final na spec, arquiva o sprint em _arquivo/
19
+ // (com o sprint-status.yaml da raiz), limpa/troca active_feature
16
20
  // (recusa com pendência do checklist, salvo --forcar);
17
21
  // com --cerebro grava Estado + linha de Sessões no
18
22
  // projetos/<projeto>.md do Cérebro (--projeto <nome>)
19
- // listar features com pasta e o status de cada spec
23
+ // listar sprints abertos e arquivados, com o status de cada spec
20
24
  //
21
25
  // Opções: --json (saída para máquina), --cwd <dir>, --feature <slug>.
22
26
  // Fail-open: fora de projeto Wizz, avisa e sai com 0 sem escrever nada.
@@ -143,6 +147,16 @@ function cmdDefinir(state, opts) {
143
147
  if (!slug) return { ok: false, code: 2, line: 'Uso: definir <slug ou nome da feature>. Nada foi gravado.' };
144
148
  const { root } = state;
145
149
  const previous = state.fromConfig;
150
+ // O sprint aberto manda: assunto novo entra nele. Abrir outro sprint com um
151
+ // aberto é escolha do dono, nunca do agente (pastas demais na Lucrare, 2026-10-15).
152
+ if (previous && previous !== slug && !opts.novo) {
153
+ return {
154
+ ok: false,
155
+ code: 1,
156
+ data: { feature: previous, recusado: slug },
157
+ line: `Sprint aberto: ${previous}. O pedido novo entra nele pelo tamanho (ajuste pequeno só faz; vários passos, story; objetivo novo, épico). Se não couber, pergunte ao dono uma vez: adicionar em ${previous} ou abrir sprint novo? Só com a escolha dele: definir ${slug} --novo. Nada foi gravado.`,
158
+ };
159
+ }
146
160
  lib.writeConfigValue(root, 'active_feature', slug);
147
161
  const dirs = lib.artifactDirs(root);
148
162
  for (const d of [dirs.planning, dirs.implementation]) fs.mkdirSync(path.join(d, slug), { recursive: true });
@@ -150,7 +164,8 @@ function cmdDefinir(state, opts) {
150
164
  const parts = [
151
165
  `Feature: ${slug}. Artefatos em ${rel(root, path.join(dirs.planning, slug))}/ e ${rel(root, path.join(dirs.implementation, slug))}/.`,
152
166
  ];
153
- if (previous && previous !== slug) parts.push(`Troca: ${previous} → ${slug} (${previous} segue aberta até ser encerrada).`);
167
+ if (previous && previous !== slug)
168
+ parts.push(`Sprint novo a pedido do dono: ${previous} → ${slug} (${previous} segue aberto até ser encerrado).`);
154
169
  if (state.fromBranch && state.fromBranch !== slug)
155
170
  parts.push(
156
171
  `Atenção: o branch ${state.git.branch} aponta para ${state.fromBranch} e ganha do config. Troque de branch se ${slug} é outro trabalho.`,
@@ -246,6 +261,72 @@ function looseArtifacts(root, dirs) {
246
261
  return out;
247
262
  }
248
263
 
264
+ /** Move `src` para `dest`, mesclando quando `dest` já existe (sprint reaberto e fechado de novo). */
265
+ function moveInto(src, dest) {
266
+ if (!fs.existsSync(dest)) {
267
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
268
+ fs.renameSync(src, dest);
269
+ return;
270
+ }
271
+ for (const name of fs.readdirSync(src)) moveInto(path.join(src, name), path.join(dest, name));
272
+ fs.rmdirSync(src);
273
+ }
274
+
275
+ function markdownFiles(dir) {
276
+ let entries = [];
277
+ try {
278
+ entries = fs.readdirSync(dir, { withFileTypes: true });
279
+ } catch {
280
+ return [];
281
+ }
282
+ return entries.flatMap((e) => {
283
+ const abs = path.join(dir, e.name);
284
+ if (e.isDirectory()) return markdownFiles(abs);
285
+ return e.isFile() && e.name.endsWith('.md') ? [abs] : [];
286
+ });
287
+ }
288
+
289
+ /**
290
+ * Sprint fechado vai para o arquivo morto: as duas pastas da feature descem
291
+ * para `_arquivo/<slug>/`, e o `sprint-status.yaml` da raiz de implementação
292
+ * (o do sprint aberto) vai junto, para o próximo sprint começar limpo.
293
+ * @returns {{planning: string, implementation: string, sprintStatus: string}}
294
+ */
295
+ function archiveSprint(root, slug) {
296
+ const dirs = lib.artifactDirs(root);
297
+ const out = {};
298
+ const moves = [dirs.planning, dirs.implementation].map((base) => ({
299
+ src: path.join(base, slug),
300
+ dest: path.join(base, lib.ARCHIVE_DIR, slug),
301
+ }));
302
+ const mapAbs = (abs) => {
303
+ const hit = moves.find((m) => abs === m.src || abs.startsWith(m.src + path.sep));
304
+ return hit ? hit.dest + abs.slice(hit.src.length) : abs;
305
+ };
306
+ // Links relativos (`../`) descem um nível junto com o arquivo: reescreve antes de mover.
307
+ for (const { src, dest } of moves) {
308
+ for (const file of markdownFiles(src)) {
309
+ const text = fs.readFileSync(file, 'utf8');
310
+ const next = lib.rebaseMarkdownLinks(text, path.dirname(file), path.dirname(mapAbs(file)), mapAbs);
311
+ if (next !== text) fs.writeFileSync(file, next);
312
+ }
313
+ if (fs.existsSync(src)) moveInto(src, dest);
314
+ }
315
+ out.planning = moves[0].dest;
316
+ out.implementation = moves[1].dest;
317
+ const status = path.join(dirs.implementation, 'sprint-status.yaml');
318
+ out.sprintStatus = '';
319
+ if (fs.existsSync(status)) {
320
+ const dest = path.join(out.implementation, 'sprint-status.yaml');
321
+ fs.mkdirSync(out.implementation, { recursive: true });
322
+ if (!fs.existsSync(dest)) {
323
+ fs.renameSync(status, dest);
324
+ out.sprintStatus = dest;
325
+ }
326
+ }
327
+ return out;
328
+ }
329
+
249
330
  /**
250
331
  * Checklist de fechamento. Itens mecânicos são checados; os de julgamento
251
332
  * (Cérebro, memória) voltam como "confirmar" para o agente responder.
@@ -329,15 +410,18 @@ function cmdEncerrar(state, opts) {
329
410
  line: `Não encerrei ${slug}: ${pend.map((p) => p.item + (p.detalhe ? ` (${p.detalhe})` : '')).join('; ')}. Resolva e rode de novo (ou --forcar se o dono aceitar a pendência).`,
330
411
  };
331
412
  }
332
- const file = setSpecStatus(state, slug, status);
413
+ setSpecStatus(state, slug, status);
414
+ const archived = archiveSprint(state.root, slug);
415
+ const file = path.join(archived.planning, `spec-${slug}.md`);
333
416
  const next = lib.toSlug(typeof opts.proxima === 'string' ? opts.proxima : '');
334
417
  if (state.fromConfig === slug || !state.fromConfig) lib.writeConfigValue(state.root, 'active_feature', next);
335
418
  if (next) {
336
419
  const dirs = lib.artifactDirs(state.root);
337
420
  for (const d of [dirs.planning, dirs.implementation]) fs.mkdirSync(path.join(d, next), { recursive: true });
338
421
  }
339
- const parts = [`Feature ${slug} encerrada (${status}) em ${lib.today()}. Spec: ${rel(state.root, file)}.`];
340
- parts.push(next ? `Feature ativa agora: ${next}.` : 'Nenhuma feature ativa agora.');
422
+ const parts = [`Sprint ${slug} encerrado (${status}) em ${lib.today()} e arquivado. Spec: ${rel(state.root, file)}.`];
423
+ if (archived.sprintStatus) parts.push(`sprint-status.yaml arquivado junto: o próximo sprint começa limpo.`);
424
+ parts.push(next ? `Sprint aberto agora: ${next}.` : 'Nenhum sprint aberto agora: o próximo pedido de trabalho abre um.');
341
425
  if (state.fromBranch === slug) parts.push(`O branch ${state.git.branch} ainda aponta para ${slug}: volte ao tronco ou troque de branch.`);
342
426
  const memo = opts.cerebro ? cerebroClose(state, slug, status, opts) : null;
343
427
  if (memo && memo.gravado) parts.push(`Cérebro atualizado: ${memo.arquivo} (Estado + linha em Sessões).`);
@@ -346,31 +430,49 @@ function cmdEncerrar(state, opts) {
346
430
  parts.push(`Registre no Cérebro: Estado do projeto + linha na tabela Sessões (${slug}: ${status} em ${lib.today()}).`);
347
431
  }
348
432
  if (pend.length > 0) parts.push(`Pendências: ${pend.map((p) => p.item).join('; ')}.`);
349
- const data = { feature: slug, status, proxima: next, pendencias: pend.map((p) => p.item) };
433
+ const data = {
434
+ feature: slug,
435
+ status,
436
+ proxima: next,
437
+ pendencias: pend.map((p) => p.item),
438
+ arquivo: rel(state.root, archived.planning),
439
+ };
350
440
  if (memo) data.cerebro = memo;
351
441
  return { ok: true, data, line: parts.join(' ') };
352
442
  }
353
443
 
354
- function cmdListar(state) {
355
- const dirs = lib.artifactDirs(state.root);
356
- let names = [];
444
+ function sprintDirs(base) {
357
445
  try {
358
- names = fs
359
- .readdirSync(dirs.planning, { withFileTypes: true })
360
- .filter((e) => e.isDirectory())
446
+ return fs
447
+ .readdirSync(base, { withFileTypes: true })
448
+ .filter((e) => e.isDirectory() && e.name !== lib.ARCHIVE_DIR)
361
449
  .map((e) => e.name);
362
450
  } catch {
363
- // sem pasta de planejamento: nenhuma feature
451
+ return [];
364
452
  }
365
- const features = names.map((slug) => {
366
- const spec = artifactPath(state.root, 'spec', slug, dirs);
367
- const status = fs.existsSync(spec) ? lib.parseFrontmatter(fs.readFileSync(spec, 'utf8')).data.status || '?' : 'sem spec';
368
- return { feature: slug, status, ativa: slug === state.slug };
369
- });
370
- const line =
371
- features.length === 0
372
- ? 'Nenhuma feature com pasta ainda.'
373
- : features.map((f) => `${f.ativa ? '*' : ' '} ${f.feature} (${f.status})`).join('\n');
453
+ }
454
+
455
+ function cmdListar(state) {
456
+ const dirs = lib.artifactDirs(state.root);
457
+ const statusOf = (folder, slug) => {
458
+ const spec = path.join(folder, slug, `spec-${slug}.md`);
459
+ return fs.existsSync(spec) ? lib.parseFrontmatter(fs.readFileSync(spec, 'utf8')).data.status || '?' : 'sem spec';
460
+ };
461
+ const archive = path.join(dirs.planning, lib.ARCHIVE_DIR);
462
+ const features = [
463
+ ...sprintDirs(dirs.planning).map((slug) => ({
464
+ feature: slug,
465
+ status: statusOf(dirs.planning, slug),
466
+ ativa: slug === state.slug,
467
+ arquivada: false,
468
+ })),
469
+ ...sprintDirs(archive).map((slug) => ({ feature: slug, status: statusOf(archive, slug), ativa: false, arquivada: true })),
470
+ ];
471
+ const abertas = features.filter((f) => !f.arquivada);
472
+ const arquivadas = features.length - abertas.length;
473
+ const lines = abertas.map((f) => `${f.ativa ? '*' : ' '} ${f.feature} (${f.status})`);
474
+ if (arquivadas > 0) lines.push(` + ${arquivadas} sprint(s) fechado(s) em ${rel(state.root, archive)}/`);
475
+ const line = features.length === 0 ? 'Nenhum sprint com pasta ainda.' : lines.join('\n');
374
476
  return { ok: true, data: { features }, line };
375
477
  }
376
478
 
@@ -161,9 +161,9 @@ function featureLine(wizzRoot) {
161
161
  const state = lib.resolveFeature(wizzRoot);
162
162
  const cmd = 'node _wizz/hooks/wizz-feature.js';
163
163
  if (!state.slug) {
164
- return ` Feature: nenhuma ativa. Pedido nomeado → ANTES de despachar \`${cmd} definir <slug do pedido>\` (sem perguntar); handoff leva \`feature:\`.`;
164
+ return ` Feature: nenhuma ativa (nenhum sprint aberto). Pedido de trabalho → ANTES de despachar \`${cmd} definir <slug do pedido>\` (sem perguntar); pergunta/conversa não abre; handoff leva \`feature:\`.`;
165
165
  }
166
- return ` Feature: ${state.slug}. Outro assunto → \`${cmd} definir <novo>\`; continuação mantém; entrega fechada → \`${cmd} encerrar\`. Handoff leva \`feature: ${state.slug}\`.`;
166
+ return ` Feature: ${state.slug} (sprint aberto). Todo trabalho entra nele (assunto novo que cabe: ajuste pequeno só faz, vários passos vira story, objetivo novo vira épico); não cabe → pergunte 1 vez: adicionar ou abrir outro (\`definir <novo> --novo\` só com o dono); entrega fechada → \`${cmd} encerrar\`. Handoff leva \`feature: ${state.slug}\`.`;
167
167
  } catch (error) {
168
168
  debug('router-enforce', 'featureLine', error);
169
169
  return '';