@polymorphism-tech/morph-spec 8.11.0 → 8.14.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/README.md +2 -2
- package/bin/morph-spec.js +2 -2
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +3 -2
- package/framework/MORPH.md +5 -1
- package/framework/commands/morph-proposal.md +4 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +21 -9
- package/framework/hooks/claude-code/core/gate-guard.js +68 -35
- package/framework/hooks/claude-code/core/protect-spec-files.js +11 -6
- package/framework/hooks/claude-code/core/state-sync.js +66 -26
- package/framework/hooks/claude-code/core/trace-autogen.js +9 -4
- package/framework/hooks/claude-code/core/worktree-context.js +12 -3
- package/framework/hooks/shared/feature-locator.js +164 -0
- package/framework/hooks/shared/payload-utils.js +71 -0
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +2 -2
- package/framework/skills/core/morph-brainstorming/SKILL.md +67 -32
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +9 -3
- package/framework/skills/core/morph-implement/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +2 -2
- package/framework/skills/core/morph-proposal/SKILL.md +8 -9
- package/framework/skills/core/morph-review/SKILL.md +2 -2
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/package.json +1 -1
- package/src/commands/project/claim.js +76 -7
- package/src/commands/state/advance.js +43 -0
- package/src/lib/validators/nextjs/next-component-validator.js +37 -1
- package/src/utils/hooks-installer.js +10 -2
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locate which tree owns a feature, and which feature a gate question is about.
|
|
3
|
+
*
|
|
4
|
+
* `resolveMorphRoot` (payload-utils) answers this from a file path. Some hook
|
|
5
|
+
* events carry no path at all — an `AskUserQuestion` gate approval is the one
|
|
6
|
+
* that matters — and those were resolved by "the first in_progress feature of
|
|
7
|
+
* whatever tree the session happens to sit in". That is how a Gate 2 approval
|
|
8
|
+
* for one feature landed on `export-hub`, an unrelated feature that merely
|
|
9
|
+
* happened to be active in the primary root.
|
|
10
|
+
*
|
|
11
|
+
* The honest signal for a pathless event is the question text itself: every
|
|
12
|
+
* gate template in the core skills names its feature — "Gate 1 — Proposta de
|
|
13
|
+
* {feature} aprovada?", "Gate 2 — Plano de {feature} aprovado?", "Gate 3 —
|
|
14
|
+
* Review de {feature} aprovado?", "Gate 3 — Hotfix de {feature} pronto para
|
|
15
|
+
* ship?". Rather than parse those sentences (which would break the moment a
|
|
16
|
+
* template is reworded), match the question against the set of feature slugs
|
|
17
|
+
* that actually exist on disk.
|
|
18
|
+
*
|
|
19
|
+
* When the text names no known feature, the caller must REFUSE TO GUESS.
|
|
20
|
+
* Stamping a gate onto the wrong feature is close to unrecoverable — it inverts
|
|
21
|
+
* gate order and invites a second stamp on the real owner; writing nothing
|
|
22
|
+
* costs one `morph-spec approve`.
|
|
23
|
+
*
|
|
24
|
+
* Fail-open by contract: every function swallows errors and returns an empty
|
|
25
|
+
* result rather than throwing.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { execSync } from 'child_process';
|
|
29
|
+
import { loadState } from './state-reader.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Feature slugs known to `root`.
|
|
33
|
+
*
|
|
34
|
+
* Deliberately `loadState` and not a `readdirSync` of `.morph/features/`: state
|
|
35
|
+
* lives in BOTH shapes — the thin `.morph/state.json` index and the per-feature
|
|
36
|
+
* `feature.json` — and `loadState` is the one place that already merges them.
|
|
37
|
+
* Listing directories alone misses every feature carried only by the index.
|
|
38
|
+
*/
|
|
39
|
+
export function featureNamesIn(root) {
|
|
40
|
+
try {
|
|
41
|
+
const state = loadState(root);
|
|
42
|
+
return state?.features ? Object.keys(state.features) : [];
|
|
43
|
+
} catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Every working tree of this repo — the primary root plus every registered
|
|
50
|
+
* secondary worktree. Fail-open to `[sessionRoot]` when git is unavailable or
|
|
51
|
+
* the directory is not a repo.
|
|
52
|
+
*/
|
|
53
|
+
export function allWorkTrees(sessionRoot) {
|
|
54
|
+
const roots = [sessionRoot];
|
|
55
|
+
try {
|
|
56
|
+
const out = execSync('git worktree list --porcelain', {
|
|
57
|
+
cwd: sessionRoot,
|
|
58
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
59
|
+
timeout: 2000,
|
|
60
|
+
}).toString();
|
|
61
|
+
for (const line of out.split('\n')) {
|
|
62
|
+
if (!line.startsWith('worktree ')) continue;
|
|
63
|
+
const path = line.slice('worktree '.length).trim();
|
|
64
|
+
if (path && !roots.includes(path)) roots.push(path);
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
/* fail-open — the session root alone */
|
|
68
|
+
}
|
|
69
|
+
return roots;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The single feature slug named in `text`, or null.
|
|
76
|
+
*
|
|
77
|
+
* Slug boundaries are `[^a-z0-9-]` so a longer name never matches inside a
|
|
78
|
+
* shorter one (`export` must not match the text "export-hub"). Zero matches
|
|
79
|
+
* means the text named nothing; two or more means it named two features and is
|
|
80
|
+
* ambiguous. Both are null — "I don't know" and "it's ambiguous" must lead to
|
|
81
|
+
* the same refusal, not to a coin flip.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} text
|
|
84
|
+
* @param {string[]} names - candidate slugs
|
|
85
|
+
* @returns {string|null}
|
|
86
|
+
*/
|
|
87
|
+
export function featureNamedIn(text, names) {
|
|
88
|
+
if (typeof text !== 'string' || !Array.isArray(names) || names.length === 0) return null;
|
|
89
|
+
const haystack = text.toLowerCase();
|
|
90
|
+
const hits = names.filter((name) =>
|
|
91
|
+
new RegExp(`(^|[^a-z0-9-])${escapeRe(String(name).toLowerCase())}([^a-z0-9-]|$)`).test(haystack)
|
|
92
|
+
);
|
|
93
|
+
return hits.length === 1 ? hits[0] : null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A token in `text` shaped like a feature slug (kebab-case, at least one
|
|
98
|
+
* hyphen — `morph-spec create`'s naming convention) that names none of
|
|
99
|
+
* `names`. Signals "the text is talking about a specific feature I don't
|
|
100
|
+
* know" as opposed to "the text names nothing in particular".
|
|
101
|
+
*
|
|
102
|
+
* This is what the single-feature fallback below was missing: a gate question
|
|
103
|
+
* for a feature not yet registered (no `create`, no `proposal.md` written
|
|
104
|
+
* yet — a fresh hotfix, say) names NO known feature either, and used to fall
|
|
105
|
+
* through to "the tree's one feature must be it". That is exactly how a Gate 3
|
|
106
|
+
* for `backfill-bool-filter` landed on `export-hub`, the tree's only
|
|
107
|
+
* registered feature at the time.
|
|
108
|
+
*
|
|
109
|
+
* Deliberately narrow (requires a hyphen) to match real feature slugs
|
|
110
|
+
* (`export-hub`, `backfill-bool-filter`) without flagging ordinary prose —
|
|
111
|
+
* the fixed portions of every gate template ("Proposta", "aprovado", "Sinal",
|
|
112
|
+
* "pronto para ship"...) are single words with no internal hyphen.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} text
|
|
115
|
+
* @param {string[]} names - every feature slug known across all trees
|
|
116
|
+
* @returns {string|null}
|
|
117
|
+
*/
|
|
118
|
+
export function unrecognizedSlugLike(text, names) {
|
|
119
|
+
if (typeof text !== 'string') return null;
|
|
120
|
+
const known = new Set((names || []).map((n) => String(n).toLowerCase()));
|
|
121
|
+
const tokens = text.toLowerCase().match(/[a-z0-9]+(?:-[a-z0-9]+)+/g) || [];
|
|
122
|
+
return tokens.find((t) => !known.has(t)) ?? null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Resolve a gate question to `{ name, root }`.
|
|
127
|
+
*
|
|
128
|
+
* The session root is consulted first so the common case costs no git call, and
|
|
129
|
+
* so a feature present in BOTH trees resolves to the tree the session is
|
|
130
|
+
* actually in — the same tree `morph-spec approve` would write from that cwd.
|
|
131
|
+
* Only when the session root knows no such feature do we pay for enumerating
|
|
132
|
+
* the worktrees.
|
|
133
|
+
*
|
|
134
|
+
* @returns {{name: string, root: string}|null} null ⇒ caller must not guess
|
|
135
|
+
*/
|
|
136
|
+
export function locateFeatureForQuestion(text, sessionRoot) {
|
|
137
|
+
const trees = allWorkTrees(sessionRoot);
|
|
138
|
+
const localNames = featureNamesIn(sessionRoot);
|
|
139
|
+
|
|
140
|
+
const local = featureNamedIn(text, localNames);
|
|
141
|
+
if (local) return { name: local, root: sessionRoot };
|
|
142
|
+
|
|
143
|
+
for (const root of trees) {
|
|
144
|
+
if (root === sessionRoot) continue;
|
|
145
|
+
const found = featureNamedIn(text, featureNamesIn(root));
|
|
146
|
+
if (found) return { name: found, root };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// The text names a specific feature none of the trees know — refuse rather
|
|
150
|
+
// than guess it meant the tree's one registered feature (see
|
|
151
|
+
// `unrecognizedSlugLike` above).
|
|
152
|
+
const allNames = trees.flatMap(featureNamesIn);
|
|
153
|
+
if (unrecognizedSlugLike(text, allNames)) return null;
|
|
154
|
+
|
|
155
|
+
// Unambiguous fallback: the session's tree holds exactly ONE feature, so
|
|
156
|
+
// there is no other feature the answer could have been about. This is the
|
|
157
|
+
// single-feature project — the common case — and refusing there would break
|
|
158
|
+
// gate questions phrased without a name ("Gate 1 — Proposta aprovada?").
|
|
159
|
+
// With two or more features the fallback is exactly the guess that put a
|
|
160
|
+
// Gate 2 on `export-hub`, so it stops here.
|
|
161
|
+
if (localNames.length === 1) return { name: localNames[0], root: sessionRoot };
|
|
162
|
+
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
@@ -47,6 +47,77 @@ export function resolveRootForFile(filePath, fallbackCwd, markers = ['package.js
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the project root that OWNS a `.morph/features/...` path — the tree
|
|
52
|
+
* whose `.morph/` the hook must read and write for that file.
|
|
53
|
+
*
|
|
54
|
+
* A hook process knows only where the SESSION lives (`process.cwd()` /
|
|
55
|
+
* `CLAUDE_PROJECT_DIR`). That is not the same thing as where the FILE lives: a
|
|
56
|
+
* session rooted in the primary repo can legitimately write to a feature that
|
|
57
|
+
* lives in a secondary git worktree. Resolving state from the process instead
|
|
58
|
+
* of the path is what made `enforce-phase-writes` block a legal `2-plan/` write
|
|
59
|
+
* (the root still had the feature at `proposal`) while `protect-spec-files`
|
|
60
|
+
* simultaneously went DARK there (the root had no approved gate to enforce) —
|
|
61
|
+
* one defect, opposite directions.
|
|
62
|
+
*
|
|
63
|
+
* The anchor is exact, so no marker walk is needed: everything before the
|
|
64
|
+
* file's own `.morph/features/` segment IS the owning root. `lastIndexOf` picks
|
|
65
|
+
* the CLOSEST enclosing `.morph/` — the worktree's, not the root it nests under.
|
|
66
|
+
*
|
|
67
|
+
* Note this cannot be solved by locating the hook file itself: `worktree setup`
|
|
68
|
+
* junctions `.morph/framework/` back to the primary root, so every worktree
|
|
69
|
+
* executes the very same hook file. Only the target path identifies the tree.
|
|
70
|
+
*
|
|
71
|
+
* Fail-open: never throws — returns `fallbackCwd` for any non-feature path.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} filePath - target file path (absolute or relative)
|
|
74
|
+
* @param {string} fallbackCwd - root to use when the path owns no `.morph/`
|
|
75
|
+
* @returns {string} the resolved project root
|
|
76
|
+
*/
|
|
77
|
+
export function resolveMorphRoot(filePath, fallbackCwd) {
|
|
78
|
+
try {
|
|
79
|
+
if (!filePath) return fallbackCwd;
|
|
80
|
+
const p = toAbsolutePosix(filePath, fallbackCwd);
|
|
81
|
+
|
|
82
|
+
const idx = p.lastIndexOf('/.morph/features/');
|
|
83
|
+
if (idx <= 0) return fallbackCwd;
|
|
84
|
+
return p.slice(0, idx);
|
|
85
|
+
} catch {
|
|
86
|
+
return fallbackCwd;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Is this an already-absolute path, on EITHER convention?
|
|
92
|
+
*
|
|
93
|
+
* `path.isAbsolute` is platform-bound: `C:/x` is absolute on win32 and relative
|
|
94
|
+
* on posix. That asymmetry is a real defect here, not a theoretical one — a
|
|
95
|
+
* Windows-shaped path reaching a posix `join()` produces `/cwd/C:/x`, silently
|
|
96
|
+
* resolving state against a directory that cannot exist. Recognizing the drive
|
|
97
|
+
* letter explicitly makes the answer the same everywhere (and is a no-op on
|
|
98
|
+
* win32, where `isAbsolute` already accepts it).
|
|
99
|
+
*/
|
|
100
|
+
const isAbsolutePath = (p) => isAbsolute(p) || /^[a-zA-Z]:\//.test(p);
|
|
101
|
+
|
|
102
|
+
/** Normalize to forward slashes and make absolute against `fallbackCwd`. */
|
|
103
|
+
function toAbsolutePosix(filePath, fallbackCwd) {
|
|
104
|
+
const p = String(filePath).replace(/\\/g, '/');
|
|
105
|
+
return isAbsolutePath(p) ? p : join(fallbackCwd, p).replace(/\\/g, '/');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Absolute path of a hook's target file, resolved against the root that owns
|
|
110
|
+
* it. Pairs with `resolveMorphRoot` so a hook never mixes "the tree I read
|
|
111
|
+
* state from" with "the file I read content from".
|
|
112
|
+
*
|
|
113
|
+
* @param {string} filePath
|
|
114
|
+
* @param {string} fallbackCwd
|
|
115
|
+
* @returns {string}
|
|
116
|
+
*/
|
|
117
|
+
export function resolveTargetPath(filePath, fallbackCwd) {
|
|
118
|
+
return toAbsolutePosix(filePath || '', fallbackCwd);
|
|
119
|
+
}
|
|
120
|
+
|
|
50
121
|
/**
|
|
51
122
|
* Get the file path from a hook payload, normalizing backslashes.
|
|
52
123
|
* @param {Object} payload - Hook payload from stdin
|
|
@@ -4,7 +4,7 @@ description: Guia a criação de uma landing page nova ponta-a-ponta a partir do
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar um novo projeto de LP com o template Umbraco genérico
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.14.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# create-umbraco-lp
|
|
@@ -4,7 +4,7 @@ description: Migra uma landing page existente (React/Vite, Next.js estático ou
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber instrução "use /migrate-lp-to-umbraco {projeto}" do usuário, ou quando o usuário descrever necessidade de migrar uma LP para Umbraco
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.14.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# migrate-lp-to-umbraco
|
|
@@ -4,7 +4,7 @@ description: Orquestra a execução autônoma de tasks de uma feature morph-spec
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: após Gate 2 aprovado, ao receber instrução "use /morph-apply" ou "implementar feature"
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.14.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-apply
|
|
@@ -220,7 +220,7 @@ O comando grava `gateDecisions.review = { pause, reason, signals, trust, decided
|
|
|
220
220
|
|
|
221
221
|
### 6. Pós Gate 3 — encerramento
|
|
222
222
|
|
|
223
|
-
- Aprovar o Gate 3: em auto, `morph-spec approve {feature} review --mode auto`; em manual, o hook state-sync registra a resposta "aprovado" em AskUserQuestion. O gate fica gravado no `feature.json` autoritativo da feature — nunca via Edit à mão de `state.json`.
|
|
223
|
+
- Aprovar o Gate 3: em auto, `morph-spec approve {feature} review --mode auto`; em manual, o hook state-sync registra a resposta "aprovado" em AskUserQuestion — **desde que a pergunta nomeie a feature** (o template acima nomeia; se você improvisar sem o nome e houver mais de uma feature na árvore, o hook recusa a gravar e avisa, e você aprova por `morph-spec approve`). O gate fica gravado no `feature.json` autoritativo da feature — nunca via Edit à mão de `state.json`.
|
|
224
224
|
- **Fechar a feature** com `morph-spec finish {feature} --pr|--merge` — um passo só: arquiva commitando `features/{f} → archive/{f}` dentro de `morph/{f}`, integra e desmonta o worktree (se houver). Escolha o modo com `AskUserQuestion` (header `"Integração"`):
|
|
225
225
|
- **Abrir PR** (`--pr`): faz `push` de `morph/{f}` + `gh pr create` com base na default — para review por outro humano/CI antes do merge. Exige `origin` + `gh` autenticado (checados **antes** de arquivar; se faltarem, `finish` recusa sem efeito colateral — caia para `--merge`). A branch é preservada e a sessão fica em `morph/{f}`.
|
|
226
226
|
- **Merge local** (`--merge`): merge `--no-ff` na default **localmente**, sem push — para o fluxo single-user direto. Remove a branch e o worktree.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: morph-brainstorming
|
|
3
|
-
description: Entende o negócio, os usuários e a arquitetura antes de qualquer planejamento técnico. Para projetos existentes, detecta desvios de stack e propõe plano de compliance.
|
|
3
|
+
description: Entende o negócio, os usuários e a arquitetura antes de qualquer planejamento técnico. Varre o ambiente primeiro, monta a árvore de decisões abertas e abre com a escolha de modo de descoberta (entrevista uma-pergunta-por-vez ou automático). Para projetos existentes, detecta desvios de stack e propõe plano de compliance.
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber um novo request de feature, antes de morph-plan
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.14.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-brainstorming
|
|
@@ -31,44 +31,72 @@ O harness conhece o stack por padrão. Não pergunte sobre tecnologia a menos qu
|
|
|
31
31
|
|
|
32
32
|
## Modo bug-lean (`workType: bug`)
|
|
33
33
|
|
|
34
|
-
Quando a feature tem `workType: bug` (roteada pela `morph-proposal` — ver MORPH.md §2.1 "Roteamento por tipo de trabalho"), o brainstorming é enxuto
|
|
34
|
+
Quando a feature tem `workType: bug` (roteada pela `morph-proposal` — ver MORPH.md §2.1 "Roteamento por tipo de trabalho"), o brainstorming é enxuto. A varredura (Parte 0) continua rodando normalmente — o diagnóstico precisa do contexto técnico —, mas a árvore da Parte 1 é de **repro, diagnóstico e comportamento do sistema**, não de negócio; em modo automático (Parte 3) isso significa no máximo 1 pergunta de repro, e só se o pedido original não deixar claro. O `proposal.md` (Parte 4) foca em **Repro / Diagnóstico / Root cause provável / Escopo do fix**, sem as seções de negócio completas.
|
|
35
35
|
|
|
36
36
|
Para `workType: feature` (ou features sem `workType` — compat, ver MORPH.md §2.1), o fluxo completo abaixo permanece integral.
|
|
37
37
|
|
|
38
38
|
## O que fazer
|
|
39
39
|
|
|
40
|
-
### Parte
|
|
40
|
+
### Parte 0 — Varredura (antes de qualquer pergunta)
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
- Qual problema de negócio esta feature resolve?
|
|
44
|
-
- Quem são os usuários afetados e quais são os fluxos críticos deles?
|
|
45
|
-
- Qual é o critério de sucesso — como saberemos que a feature está pronta?
|
|
46
|
-
- Há integrações externas, dependências de outras features ou restrições de prazo?
|
|
42
|
+
Fato não se pergunta. Antes de levar a primeira pergunta ao usuário, levante o que o ambiente já responde:
|
|
47
43
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
### Parte 2 — Detecção de contexto (greenfield vs brownfield)
|
|
51
|
-
|
|
52
|
-
2. Invoque `Skill(morph-stack-scan)` para detectar contexto. A skill:
|
|
44
|
+
1. Invoque `Skill(morph-stack-scan)`. A skill:
|
|
53
45
|
- Curto-circuita em projetos greenfield (sem source files) — retorna relatório vazio.
|
|
54
46
|
- Em brownfield, varre stack atual (.csproj, package.json, migrations), padrões (Guid.NewGuid vs Guid.CreateVersion7, sealed, result pattern), features existentes.
|
|
55
47
|
- Produz `.morph/features/{feature}/0-proposal/stack-scan.md`.
|
|
56
48
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
49
|
+
2. Leia o contexto que já existe no projeto: `.morph/context/` (se existir), `.morph/config/config.json` (stack, integrations, trust), as features ativas em `.morph/features/` e o **acervo de use cases** em `.morph/specs/usecases/`. O comportamento já implementado é o contexto mais barato que existe — e é ele que evita perguntar o que o sistema já responde.
|
|
50
|
+
|
|
51
|
+
3. Reconciliação de design-system (se aplicável): comparar `.morph/context/design-system.md` (se existir) com o que foi detectado no stack-scan e com bundles/mocks recentes do usuário. Se houver **divergência** (tokens diferentes, framework version mismatch, fonte aprovada distinta), marque `signals.designSystemDivergence = true` — a escolha da fonte vira uma **decisão da árvore** (Parte 1), não uma pergunta solta. Resolvida, atualize `.morph/context/design-system.md` com a fonte escolhida + entrada de changelog (data, motivo).
|
|
52
|
+
|
|
53
|
+
4. Desvio de stack detectado: gere `migration-plan.md` em `.morph/features/{feature}/0-proposal/`. "A migração entra nesta feature ou vira feature separada?" também é decisão da árvore.
|
|
54
|
+
|
|
55
|
+
### Parte 1 — Árvore de decisões abertas
|
|
56
|
+
|
|
57
|
+
Enumere **só o que a varredura não respondeu**. Uma decisão entra na árvore quando (a) muda o escopo, o comportamento ou a arquitetura da feature, e (b) o ambiente não a resolve.
|
|
58
|
+
|
|
59
|
+
Ordene por dependência: decisão que **poda** outras vem primeiro. "O fluxo é síncrono ou assíncrono?" antecede "qual é a política de retry" — a primeira resposta pode eliminar a segunda inteira.
|
|
60
|
+
|
|
61
|
+
Publique a **árvore de decisões** ao usuário antes de perguntar qualquer coisa. Ela é o sinal de progresso da entrevista e a base da recomendação de modo na Parte 2.
|
|
62
|
+
|
|
63
|
+
### Parte 2 — Pergunta de abertura (modo de descoberta)
|
|
64
|
+
|
|
65
|
+
Aplica-se a `workType: feature` e `bug`. **`chore` e `hotfix` não passam por aqui** — nascem com os Gates 1 e 2 carimbados `policy:{tipo}` e começam direto em `implement`.
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
- **Greenfield:** seguir para a Parte 3.
|
|
66
|
-
- **Stack padrão:** seguir para a Parte 3.
|
|
67
|
-
- **Desvio de stack:** gerar `migration-plan.md` em `.morph/features/{feature}/0-proposal/`. Perguntar via `AskUserQuestion`: "A migração de stack deve ser incluída nesta feature ou tratada numa feature separada?" Registrar decisão e seguir.
|
|
67
|
+
Faça **uma** `AskUserQuestion` apresentando a árvore e recomendando um modo. A recomendação sai do tamanho e do acoplamento da árvore: muitas decisões abertas, ou decisões que dependem umas das outras → recomende **entrevista**; árvore rasa ou já respondida pela varredura → recomende **automático**. A opção recomendada vai em primeiro lugar, com o sufixo `(Recomendado)` no label.
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
```
|
|
70
|
+
question: "Antes de começar {feature}: entrevista ou automático? Encontrei {n} decisões abertas: {resumo da árvore}"
|
|
71
|
+
header: "Descoberta"
|
|
72
|
+
options:
|
|
73
|
+
- { label: "{modo recomendado} (Recomendado)", description: "{por que a árvore recomenda este modo}" }
|
|
74
|
+
- { label: "{o outro modo}", description: "{o que muda na prática}" }
|
|
75
|
+
```
|
|
70
76
|
|
|
71
|
-
|
|
77
|
+
### Parte 3 — Entendimento
|
|
78
|
+
|
|
79
|
+
**Modo entrevista.** Percorra a árvore, **uma pergunta por chamada** de `AskUserQuestion` — perguntas em lote desorientam e impedem que uma resposta pode ramos. Regras:
|
|
80
|
+
|
|
81
|
+
- Toda pergunta traz **resposta recomendada**, como primeira opção, marcada `(Recomendado)`.
|
|
82
|
+
- Cada resposta risca, poda ou abre ramos; mostre a árvore atualizada quando ela mudar de forma.
|
|
83
|
+
- Fato descoberto no ambiente nunca vira pergunta. As **decisões** são do usuário; os **fatos** são seus.
|
|
84
|
+
- Termina quando a árvore zera. Então apresente o **entendimento compartilhado** (problema, escopo com exclusões, usuários e fluxos, riscos) — a confirmação dele é o Gate 1, que **pausa sempre** neste modo.
|
|
85
|
+
- O usuário pode encerrar a entrevista a qualquer momento; o que ficou aberto vira exclusão explícita de escopo no `proposal.md`.
|
|
86
|
+
|
|
87
|
+
**Modo automático.** Até 4 perguntas via `AskUserQuestion` cobrindo o que a árvore deixou aberto:
|
|
88
|
+
- Qual problema de negócio esta feature resolve?
|
|
89
|
+
- Quem são os usuários afetados e quais são os fluxos críticos deles?
|
|
90
|
+
- Qual é o critério de sucesso — como saberemos que a feature está pronta?
|
|
91
|
+
- Há integrações externas, dependências de outras features ou restrições de prazo?
|
|
92
|
+
|
|
93
|
+
Árvore vazia → nenhuma pergunta. O Gate 1 segue trust-aware (regras do MORPH.md §5).
|
|
94
|
+
|
|
95
|
+
**Em `workType: bug`**, a árvore é de **repro, diagnóstico e comportamento do sistema** — não de negócio. Em modo automático vale o bug-lean: no máximo 1 pergunta de repro, e só se o pedido original não deixar claro.
|
|
96
|
+
|
|
97
|
+
### Parte 4 — Produção do proposal.md
|
|
98
|
+
|
|
99
|
+
1. Produza `proposal.md` em `.morph/features/{feature}/0-proposal/` com:
|
|
72
100
|
- **Problema:** o que está sendo resolvido e para quem (máx. 2 parágrafos)
|
|
73
101
|
- **Escopo:** o que está incluído e o que está explicitamente excluído
|
|
74
102
|
- **Usuários e fluxos críticos:** quem usa e qual é o caminho principal
|
|
@@ -82,21 +110,28 @@ Para `workType: feature` (ou features sem `workType` — compat, ver MORPH.md §
|
|
|
82
110
|
|
|
83
111
|
Um bom proposal.md tem no máximo 2 páginas. Se está crescendo além disso, o escopo precisa ser cortado.
|
|
84
112
|
|
|
85
|
-
|
|
113
|
+
2. Apresente o Gate 1 segundo o modo de descoberta e o trust mode.
|
|
86
114
|
|
|
87
115
|
Ler `.morph/config/config.json → trust` (default `"auto"`). Calcular os sinais de risco em linguagem natural — sem scripts:
|
|
88
116
|
|
|
89
|
-
- `
|
|
117
|
+
- `discoveryMode` = `"interview"` ou `"auto"`, conforme a resposta da Parte 2.
|
|
118
|
+
- `designSystemDivergence` = detectado na Parte 0 (fontes de design conflitantes).
|
|
90
119
|
- `standardsViolations` = lista de violações detectadas.
|
|
91
120
|
|
|
92
|
-
**Decisão de pausa** (regras da tabela do MORPH.md §5): `trust: manual` → pausa sempre; `designSystemDivergence` → pausa; `standardsViolations` não-vazio → pausa; caso contrário → auto-passa (`pause: false`). Monte `reason` com o sinal que disparou.
|
|
121
|
+
**Decisão de pausa** (regras da tabela do MORPH.md §5): **modo entrevista → pausa sempre** ("não aja até eu confirmar" é literalmente o Gate 1: a confirmação do entendimento compartilhado é o gate); `trust: manual` → pausa sempre; `designSystemDivergence` → pausa; `standardsViolations` não-vazio → pausa; caso contrário → auto-passa (`pause: false`). Monte `reason` com o sinal que disparou.
|
|
93
122
|
|
|
94
123
|
**Persista a decisão ANTES de apresentar ou auto-aprovar — via CLI, nunca editando feature.json à mão** (o Edit manual colide com o hook state-sync, que reescreve o arquivo entre o seu Read e o Edit — erro "file modified since read"):
|
|
95
124
|
|
|
96
125
|
```
|
|
97
|
-
|
|
126
|
+
# modo entrevista — o gate pausa sempre
|
|
127
|
+
morph-spec gate-decision {feature} proposal --pause true --reason "modo entrevista — entendimento compartilhado confirmado pelo usuário" --signals '{"discoveryMode":"interview","designSystemDivergence":false,"standardsViolations":[]}'
|
|
128
|
+
|
|
129
|
+
# modo automático, sem sinais de risco
|
|
130
|
+
morph-spec gate-decision {feature} proposal --pause false --reason "nenhum sinal de risco" --signals '{"discoveryMode":"auto","designSystemDivergence":false,"standardsViolations":[]}'
|
|
98
131
|
```
|
|
99
132
|
|
|
133
|
+
`signals` é free-form no `feature.schema.json` — `discoveryMode` não exige mudança de schema, e deixa o modo escolhido auditável no `feature.json` commitado.
|
|
134
|
+
|
|
100
135
|
O comando grava `gateDecisions.proposal = { pause, reason, signals, trust, decidedAt }` no `feature.json` autoritativo com timestamp server-side (shape canônico em `.morph/framework/schemas/feature.schema.json`). O hook `gate-guard.js` confere depois se a ação (pausar vs auto-aprovar) bateu com esta decisão e avisa em divergência. Sem o registro o guard não verifica — não pule.
|
|
101
136
|
|
|
102
137
|
**Se `pause === true`** (manual mode OU risco detectado em auto), use `AskUserQuestion`:
|
|
@@ -124,7 +159,7 @@ Para `workType: feature` (ou features sem `workType` — compat, ver MORPH.md §
|
|
|
124
159
|
**Riscos:** {principal} (sinais detectados: nenhum)
|
|
125
160
|
```
|
|
126
161
|
|
|
127
|
-
No caminho manual, o hook state-sync.js detecta a aprovação automaticamente (grava o gate no `feature.json` autoritativo, `approvedBy: "manual"`). Em modo auto sem AskUserQuestion, aprove via `morph-spec approve {feature} proposal --mode auto` (ou `morph-spec advance {feature}` para já criar a pasta 2-plan/) — nunca via Edit à mão de state.json.
|
|
162
|
+
No caminho manual, o hook state-sync.js detecta a aprovação automaticamente (grava o gate no `feature.json` autoritativo, `approvedBy: "manual"`) — **atribuindo pela feature nomeada na pergunta**; sem o nome, e com mais de uma feature na árvore, ele recusa a gravar e avisa (aprove então por `morph-spec approve {feature} proposal`). Em modo auto sem AskUserQuestion, aprove via `morph-spec approve {feature} proposal --mode auto` (ou `morph-spec advance {feature}` para já criar a pasta 2-plan/) — nunca via Edit à mão de state.json.
|
|
128
163
|
|
|
129
164
|
## Output esperado
|
|
130
165
|
- `.morph/features/{feature}/0-proposal/proposal.md` — proposta aprovada
|
|
@@ -134,5 +169,5 @@ Para `workType: feature` (ou features sem `workType` — compat, ver MORPH.md §
|
|
|
134
169
|
## Referências
|
|
135
170
|
- `.morph/framework/MORPH.md` — seções 1 (Papéis), 2 (Fases), 5 (Gates), Trust Mode
|
|
136
171
|
- `.morph/framework/standards/STANDARDS.json` — para referência de standards relevantes à stack
|
|
137
|
-
- `framework/skills/core/morph-stack-scan/SKILL.md` — invocada na Parte
|
|
172
|
+
- `framework/skills/core/morph-stack-scan/SKILL.md` — invocada na Parte 0 (varredura), antes de qualquer pergunta
|
|
138
173
|
- `.morph/framework/MORPH.md` §5 — regras de pausa do Gate 1 (trust mode)
|
|
@@ -4,7 +4,7 @@ description: Avalia a qualidade de uma tarefa ou fase com rubricas graduadas 0-1
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao completar uma tarefa ou ao finalizar uma fase de implementação
|
|
6
6
|
user-invokable: false
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.14.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-eval
|
|
@@ -4,7 +4,7 @@ description: ADW cirúrgico de hotfix — resolve uma queda/bug crítico de prod
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber "/morph-hotfix {feature}", quando a morph-proposal classifica o pedido como workType hotfix (Passo 0), ou o usuário diz "produção caiu / hotfix / urgente"
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.14.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-hotfix
|
|
@@ -98,7 +98,13 @@ options:
|
|
|
98
98
|
- { label: "rollback", description: "Desfazer o fix — voltar ao estado anterior" }
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
**NUNCA `morph-spec approve {feature} review --mode auto`** — o CLI recusa explicitamente essa combinação (`src/commands/state/approve.js`: `gateName === 'review' && feature.workType === 'hotfix' && approvedBy === 'auto'` é bloqueado) porque `forcedPauseGates` sobrepõe qualquer trust mode. Em contexto headless (sem `AskUserQuestion` disponível),
|
|
101
|
+
**NUNCA `morph-spec approve {feature} review --mode auto`** — o CLI recusa explicitamente essa combinação (`src/commands/state/approve.js`: `gateName === 'review' && feature.workType === 'hotfix' && approvedBy === 'auto'` é bloqueado) porque `forcedPauseGates` sobrepõe qualquer trust mode. Em contexto headless (sem `AskUserQuestion` disponível), **o `state-sync` NÃO cobre este caso**: ele é um hook `PostToolUse` com matcher `Write|Edit|AskUserQuestion`, e uma resposta em texto puro do usuário não é tool call — não gera evento e não grava gate nenhum. Esperar por ele deixa o Gate 3 por aprovar em silêncio. O caminho é registrar a aprovação humana explicitamente:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
morph-spec approve {feature} review --approver "<nome de quem aprovou>"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`--approver` é a única assinatura que passa pelo backstop em headless — as outras rotas de `resolveApprovedBy` (`--mode auto`, e "sem TTY") resolvem para `"auto"` e são recusadas. Ela é **auto-declarada**: o CLI não consegue verificar que houve um humano, só recusa as assinaturas definitivamente não-humanas. Por isso: **nunca invente o nome** — use `--approver` só quando o humano de fato aprovou, senão o backstop do hotfix vira teatro.
|
|
102
108
|
|
|
103
109
|
### 8. Pós-gate (o "test loop → ship" de `MORPH.md` §2.1)
|
|
104
110
|
|
|
@@ -119,7 +125,7 @@ options:
|
|
|
119
125
|
- `.morph/features/{feature}/2-plan/tasks.json` — T1 (fix) + T2 (verify/regressão)
|
|
120
126
|
- `.morph/features/{feature}/3-implement/recap.md` — evidência real de teste
|
|
121
127
|
- `.morph/features/{feature}/4-review/review-report.md` — diff + evidência, Gate 3 sempre humano
|
|
122
|
-
- Gates `proposal`/`plan` carimbados `approvedBy: "policy:hotfix"`; Gate 3 registrado `
|
|
128
|
+
- Gates `proposal`/`plan` carimbados `approvedBy: "policy:hotfix"`; Gate 3 registrado em `feature.json` (autoritativo) com uma **assinatura humana** — `"manual"` pelo `state-sync` (resposta ao `AskUserQuestion`), o nome passado em `--approver`, ou `$USER` num terminal interativo. **Nunca `"auto"`** — é o que o backstop recusa.
|
|
123
129
|
|
|
124
130
|
## Referências
|
|
125
131
|
|
|
@@ -4,7 +4,7 @@ description: Guia a execução de cada tarefa de implementação com standard e
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar cada task de implementação listada em tasks.json
|
|
6
6
|
user-invokable: false
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.14.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-implement
|
|
@@ -4,7 +4,7 @@ description: Guia a produção dos artefatos de planejamento de uma feature (use
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar a fase plan de uma feature, após o Gate 1 (proposal aprovado)
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.14.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-plan
|
|
@@ -186,7 +186,7 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
186
186
|
**Tasks:** {N} | Esforço total: {soma} | Caminho crítico: {IDs sem deps paralelas}
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
-
No caminho manual, o hook state-sync registra a aprovação automaticamente em answers de AskUserQuestion (grava o gate no `feature.json` autoritativo, `approvedBy: "manual"`). Em auto sem AskUserQuestion, aprove via `morph-spec approve {feature} plan --mode auto` (ou `morph-spec advance {feature}` para já criar a pasta da próxima fase) — nunca via Edit à mão de state.json.
|
|
189
|
+
No caminho manual, o hook state-sync registra a aprovação automaticamente em answers de AskUserQuestion (grava o gate no `feature.json` autoritativo, `approvedBy: "manual"`) — **atribuindo pela feature nomeada na pergunta**; sem o nome, e com mais de uma feature na árvore, ele recusa a gravar e avisa (aprove então por `morph-spec approve {feature} plan`). Em auto sem AskUserQuestion, aprove via `morph-spec approve {feature} plan --mode auto` (ou `morph-spec advance {feature}` para já criar a pasta da próxima fase) — nunca via Edit à mão de state.json.
|
|
190
190
|
|
|
191
191
|
## Output esperado
|
|
192
192
|
|
|
@@ -4,7 +4,7 @@ description: Orquestra a fase de proposta+plano de uma feature morph-spec. Encad
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber instrução "use /morph-proposal" do usuário, ou ao iniciar uma nova feature
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.14.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-proposal
|
|
@@ -47,7 +47,7 @@ morph-spec create {feature-name} --request "<pedido do usuário>"
|
|
|
47
47
|
|
|
48
48
|
- **`hotfix`** → invoque `Skill(morph-hotfix)` passando `{feature-name}` e **ENCERRE** — não siga o restante deste fluxo. A skill cuida de scout, brief, fix cirúrgico, test loop e Gate 3 (sempre humano).
|
|
49
49
|
- **`chore`** → fast-track: os Gates 1 e 2 já nasceram carimbados `policy:chore` no `create`. Escreva um `2-plan/tasks.json` mínimo (1-3 tasks) e invoque `Skill(morph-apply)` diretamente — **exceção documentada** à regra do passo 7 ("não despachar `morph-apply` automaticamente"): não pausar numa mudança óbvia é exatamente o valor do fast-track.
|
|
50
|
-
- **`bug`** → siga para `Skill(morph-brainstorming)` em modo **bug-lean**: produz um `proposal.md` enxuto (repro / diagnóstico / root cause / escopo
|
|
50
|
+
- **`bug`** → siga para `Skill(morph-brainstorming)` em modo **bug-lean**: produz um `proposal.md` enxuto (repro / diagnóstico / root cause / escopo do fix — não a proposta completa de uma feature nova; **sem critérios de aceite**, que moram na pós-condição do use case) e depois um plano enxuto via `Skill(morph-plan)`. Gates 1 e 2 continuam trust-aware (passos 3 e 6) — só o conteúdo dos artefatos é mais curto.
|
|
51
51
|
- **`feature`** (default — inclui features criadas antes do v8 sem `workType`) → siga o fluxo completo abaixo (passos 1-7): brainstorming → Gate 1 → plan → Gate 2.
|
|
52
52
|
|
|
53
53
|
### 1. Determinar feature name e estado
|
|
@@ -59,20 +59,19 @@ morph-spec create {feature-name} --request "<pedido do usuário>"
|
|
|
59
59
|
|
|
60
60
|
### 2. Invocar `Skill(morph-brainstorming)`
|
|
61
61
|
|
|
62
|
-
Passa o nome da feature. A skill:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
- Detecta divergência entre fontes de design-system e marca `signals.designSystemDivergence`.
|
|
66
|
-
- Produz `proposal.md` em `.morph/features/{feature}/0-proposal/`.
|
|
62
|
+
Passa o nome da feature. A skill é a **dona da fase de entendimento** — não re-descreva o comportamento dela aqui. Em resumo: varre o ambiente antes de perguntar qualquer coisa (`Skill(morph-stack-scan)` + contexto do projeto + acervo de use cases), monta a **árvore de decisões abertas**, abre com a escolha de **modo de descoberta** (entrevista ou automático — só `feature` e `bug`), detecta divergência entre fontes de design-system (`signals.designSystemDivergence`) e produz `proposal.md` em `.morph/features/{feature}/0-proposal/`. Detalhes em `framework/skills/core/morph-brainstorming/SKILL.md`.
|
|
63
|
+
|
|
64
|
+
No **modo entrevista** o Gate 1 pausa sempre — a confirmação do entendimento compartilhado é o gate. O modo escolhido chega ao passo 3 em `gateDecisions.proposal.signals.discoveryMode`.
|
|
67
65
|
|
|
68
66
|
### 3. Gate 1 (trust-aware)
|
|
69
67
|
|
|
70
68
|
Ler `.morph/config/config.json → trust` (default `"auto"`). Calcular sinais de risco:
|
|
71
69
|
|
|
70
|
+
- `discoveryMode` — `"interview"` ou `"auto"`, decidido na pergunta de abertura da morph-brainstorming.
|
|
72
71
|
- `designSystemDivergence` — detectado pela morph-brainstorming.
|
|
73
72
|
- `standardsViolations` — violações de mandate listadas na proposal (não deveria, mas verifica).
|
|
74
73
|
|
|
75
|
-
**Decisão de pausa** (regras da tabela do MORPH.md §5, aplicadas em linguagem natural — sem scripts): `trust: manual` → `pause: true` sempre; `designSystemDivergence` → pausa; `standardsViolations` não-vazio → pausa; caso contrário → `pause: false` (auto-passa). Monte `reason` com o sinal que disparou.
|
|
74
|
+
**Decisão de pausa** (regras da tabela do MORPH.md §5, aplicadas em linguagem natural — sem scripts): `discoveryMode: "interview"` → `pause: true` sempre; `trust: manual` → `pause: true` sempre; `designSystemDivergence` → pausa; `standardsViolations` não-vazio → pausa; caso contrário → `pause: false` (auto-passa). Monte `reason` com o sinal que disparou.
|
|
76
75
|
|
|
77
76
|
**Se `pause === true`**, apresentar via `AskUserQuestion`:
|
|
78
77
|
|
|
@@ -97,7 +96,7 @@ Escopo: {incluído} | Excluído: {exclusões}
|
|
|
97
96
|
Stack: {compliance}
|
|
98
97
|
```
|
|
99
98
|
|
|
100
|
-
No caminho manual, o hook `state-sync.js` registra a aprovação automaticamente via PostToolUse quando a `AskUserQuestion` é respondida com "aprovado" (grava o gate no `feature.json` autoritativo, `approvedBy: "manual"`). Em `trust: auto` (sem AskUserQuestion), **aprove via CLI — nunca via Edit à mão de state.json**:
|
|
99
|
+
No caminho manual, o hook `state-sync.js` registra a aprovação automaticamente via PostToolUse quando a `AskUserQuestion` é respondida com "aprovado" (grava o gate no `feature.json` autoritativo, `approvedBy: "manual"`) — **atribuindo pela feature nomeada na pergunta**; sem o nome, e com mais de uma feature na árvore, ele recusa a gravar e avisa (aprove então por `morph-spec approve {feature} <gate>`). Em `trust: auto` (sem AskUserQuestion), **aprove via CLI — nunca via Edit à mão de state.json**:
|
|
101
100
|
|
|
102
101
|
```
|
|
103
102
|
morph-spec approve {feature} proposal --mode auto
|
|
@@ -4,7 +4,7 @@ description: Conduz a fase de revisão e produz o review-report.md para o Gate 3
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar a fase review, após todas as tasks de tasks.json estarem com status `done`
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.14.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-review
|
|
@@ -124,7 +124,7 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
|
|
|
124
124
|
**Recomendação:** Aprovado
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
No caminho manual, o hook state-sync detecta a aprovação em answers de AskUserQuestion e grava `approvalGates.review = { approved: true, timestamp, approvedBy: "manual" }` no `feature.json` autoritativo da feature. Em auto sem AskUserQuestion, aprove via `morph-spec approve {feature} review --mode auto` — nunca via Edit à mão de state.json.
|
|
127
|
+
No caminho manual, o hook state-sync detecta a aprovação em answers de AskUserQuestion e grava `approvalGates.review = { approved: true, timestamp, approvedBy: "manual" }` no `feature.json` autoritativo da feature — **a atribuição vem do nome da feature na pergunta** (o template acima o carrega). Pergunta sem nome, com mais de uma feature na árvore: o hook recusa a gravar e avisa; aprove então por `morph-spec approve {feature} review`. Em auto sem AskUserQuestion, aprove via `morph-spec approve {feature} review --mode auto` — nunca via Edit à mão de state.json.
|
|
128
128
|
|
|
129
129
|
**Próximo passo (não é desta skill):** com o Gate 3 aprovado, a feature fecha por `morph-spec finish {feature} --pr|--merge` — a `morph-apply` §6 conduz a escolha PR vs merge. Não arquive nem integre aqui.
|
|
130
130
|
|
|
@@ -4,7 +4,7 @@ description: Varre o codebase existente, detecta stack atual, compara com o stac
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar uma feature em projeto brownfield (codebase já existente)
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.14.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-stack-scan
|
|
@@ -4,7 +4,7 @@ description: Localiza e carrega um standard específico do STANDARDS.json sob de
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: quando precisar consultar um standard específico durante a implementação
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.14.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-standards
|