@polymorphism-tech/morph-spec 7.1.1 → 7.2.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 CHANGED
@@ -3,7 +3,7 @@
3
3
  > Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
4
4
 
5
5
  **Package:** `@polymorphism-tech/morph-spec`
6
- **Version:** 7.1.1
6
+ **Version:** 7.2.0
7
7
  **Requires:** Node.js 22+, Claude Code
8
8
 
9
9
  ---
@@ -365,4 +365,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
365
365
 
366
366
  ---
367
367
 
368
- *morph-spec v7.1.1 by [Polymorphism Tech](https://polymorphism.tech)*
368
+ *morph-spec v7.2.0 by [Polymorphism Tech](https://polymorphism.tech)*
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morph-spec",
3
- "version": "7.1.1",
3
+ "version": "7.2.0",
4
4
  "displayName": "MORPH-SPEC Framework",
5
5
  "description": "Spec-driven development with 38 agents and 8-phase workflow for .NET/Blazor/Next.js/Azure",
6
6
  "publisher": "polymorphism-tech",
@@ -196,4 +196,4 @@ These files are never edited directly. Use CLI commands or `morph-spec update` i
196
196
 
197
197
  ---
198
198
 
199
- *morph-spec v7.1.1 by Polymorphism Tech*
199
+ *morph-spec v7.2.0 by Polymorphism Tech*
@@ -197,4 +197,4 @@ morph-spec doctor
197
197
 
198
198
  ---
199
199
 
200
- *morph-spec v7.1.1 by Polymorphism Tech*
200
+ *morph-spec v7.2.0 by Polymorphism Tech*
@@ -14,12 +14,14 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
14
14
 
15
15
  **NUNCA:**
16
16
  - Pular para código sem proposal aprovado (Gate 1) e plano aprovado (Gate 2).
17
- - Modificar `spec.md`, `mandate.md`, `tasks.md` depois do Gate 2 — eles estão congelados.
17
+ - Modificar o contrato do plano depois do Gate 2: `spec.md`, `mandate.md`, e os campos de plano de `tasks.json` (`title`, `description`, `dependencies`, `effort`, `doneCriteria`, `group`) estão congelados.
18
+ - Usar `Edit` em `tasks.json` após o Gate 2 — o arquivo é gravado inteiro via `writeTasksJson` (Write). O hook `protect-spec-files` bloqueia `Edit`.
18
19
  - Listar perguntas em texto simples — sempre use a tool `AskUserQuestion` (1-4 perguntas por chamada).
19
20
  - Usar `Guid.NewGuid()`, classes não-sealed sem justificativa, exceptions como fluxo de controle, camada de Service/Manager entre handler e dados, ou SQL cru sem justificativa.
20
21
 
21
22
  **SEMPRE:**
22
23
  - Auto-advance entre tarefas durante a fase implement — não pause sem ambiguidade bloqueante.
24
+ - Atualizar o progresso em `tasks.json` via `writeTasksJson` ao concluir cada task — apenas os campos `status`, `outputs` e `notes` mudam; o contrato do plano permanece congelado.
23
25
  - Apresentar gates com o template fixo definido em MORPH.md / morph-plan.
24
26
  - Registrar decisões não triviais em `decisions.md`.
25
27
  - Aplicar `morph-eval` após cada tarefa; silêncio em score ≥ 8 é o sinal correto.
@@ -64,8 +64,8 @@ Responsabilidades: ler o mandato, entender o escopo, propor o plano de ataque, i
64
64
  Anti-padrão: o Orchestrator não escreve código. Se você está no papel de Orchestrator e percebe que começou a implementar algo, pare — você transitou de papel sem intenção, e o plano provavelmente ainda não está fechado.
65
65
 
66
66
  **Specialist** — ativo durante a fase de implementação.
67
- Responsabilidades: executar tarefas do tasks.md em ordem, consultar standards relevantes antes de cada tarefa, registrar decisões e desvios no decisions.md, manter o recap.md atualizado ao final de cada tarefa.
68
- Anti-padrão: o Specialist não cria novas tarefas. Descobertas de escopo durante a implementação vão para o decisions.md com flag "escopo expandido" — o Orchestrator avalia se o tasks.md precisa ser atualizado.
67
+ Responsabilidades: executar tarefas do tasks.json em ordem, consultar standards relevantes antes de cada tarefa, registrar decisões e desvios no decisions.md, manter o recap.md atualizado ao final de cada tarefa.
68
+ Anti-padrão: o Specialist não cria novas tarefas. Descobertas de escopo durante a implementação vão para o decisions.md com flag "escopo expandido" — o Orchestrator avalia se o tasks.json precisa ser atualizado.
69
69
 
70
70
  **Evaluator** — ativo após uma tarefa ou fase ser concluída.
71
71
  Responsabilidades: aplicar as rubricas em `framework/evals/rubrics/`, produzir score 0-10, emitir feedback priorizado quando score < 8, escalar para revisão humana quando score < 6.
@@ -201,7 +201,7 @@ Despache um sub-agent quando a tarefa atende a pelo menos um dos critérios abai
201
201
  - Risco de "contaminar" o contexto principal com detalhes de baixo nível irrelevantes para o fluxo
202
202
 
203
203
  **O que passar ao sub-agent:**
204
- - Especificação da tarefa (do tasks.md)
204
+ - Especificação da tarefa (do tasks.json)
205
205
  - Path do standard relevante (apenas um)
206
206
  - Lista de arquivos que ele precisa ler ou modificar
207
207
  - Critério de done claro e verificável
@@ -1,72 +1,193 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * PreToolUse Hook: Protect Approved Spec Files
5
- *
6
- * Event: PreToolUse | Matcher: Write|Edit
7
- *
8
- * Blocks Write/Edit to spec artifacts after their approval gate has passed:
9
- * - 2-plan/spec.md, contracts.cs, tasks.md, etc. → blocked if 'plan' gate approved
10
- * - 1-design/design-system.md, etc. → blocked if 'uiux' gate approved
11
- *
12
- * Fail-open: exits 0 on any error.
13
- */
14
-
15
- import { readStdin } from '../../shared/stdin-reader.js';
16
- import { stateExists, isGateApproved } from '../../shared/state-reader.js';
17
- import {
18
- isFeaturePath,
19
- extractFeatureName,
20
- getBasename,
21
- PROTECTED_SPEC_FILES
22
- } from '../../shared/phase-utils.js';
23
- import { block, pass } from '../../shared/hook-response.js';
24
- import { logHookActivity } from '../../shared/activity-logger.js';
25
- import { getFilePath } from '../../shared/payload-utils.js';
26
-
27
- try {
28
- // Amend mode: bypass spec protection for legitimate in-implementation corrections
29
- if (process.env.MORPH_AMEND_PHASE) {
30
- console.error(`[morph-spec] AMEND MODE: bypassing spec protection for phase '${process.env.MORPH_AMEND_PHASE}'`);
31
- pass();
32
- }
33
-
34
- if (!stateExists()) pass();
35
-
36
- const payload = await readStdin();
37
- if (!payload) pass();
38
-
39
- const filePath = getFilePath(payload);
40
- if (!filePath) pass();
41
-
42
- // Only check files inside .morph/features/
43
- if (!isFeaturePath(filePath)) pass();
44
-
45
- const featureName = extractFeatureName(filePath);
46
- if (!featureName) pass();
47
-
48
- const filename = getBasename(filePath);
49
- const requiredGate = PROTECTED_SPEC_FILES[filename];
50
-
51
- // Not a protected file
52
- if (!requiredGate) pass();
53
-
54
- // Check if the gate has been approved
55
- if (isGateApproved(featureName, requiredGate)) {
56
- logHookActivity('protect-spec-files', 'PreToolUse', 'blocked');
57
- block(
58
- `MORPH-SPEC: '${filename}' is locked — the '${requiredGate}' gate has been approved.\n` +
59
- `Editing approved specs breaks the spec contract.\n\n` +
60
- `If changes are truly needed, ask the user to revoke the gate approval\n` +
61
- `in .morph/state.json (set approvals.${requiredGate} to false), make\n` +
62
- `your edits, then re-present the gate for approval.`
63
- );
64
- }
65
-
66
- // Gate not yet approved — allow editing
67
- logHookActivity('protect-spec-files', 'PreToolUse', 'ok');
68
- pass();
69
- } catch {
70
- // Fail-open
71
- process.exit(0);
72
- }
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PreToolUse Hook: Protect Approved Spec Files
5
+ *
6
+ * Event: PreToolUse | Matcher: Write|Edit
7
+ *
8
+ * Blocks Write/Edit to spec artifacts after their approval gate has passed:
9
+ * - 2-plan/spec.md, mandate.md, etc. → blocked if 'plan' gate approved
10
+ * - 1-design/design-system.md, etc. → blocked if 'uiux' gate approved
11
+ *
12
+ * Semantic exception for files in MUTABLE_SPEC_FIELDS (e.g. tasks.json):
13
+ * the plan contract is frozen, but per-task progress fields (status, outputs,
14
+ * notes) may still change during the implement phase. For those files a Write
15
+ * is allowed only when the semantic diff touches solely the mutable fields;
16
+ * Edit is always blocked because the legitimate progress path is a full Write
17
+ * via writeTasksJson(). This lets the implement phase record task status
18
+ * without unfreezing the plan.
19
+ *
20
+ * Fail-open: exits 0 on any error.
21
+ */
22
+
23
+ import { readFileSync } from 'fs';
24
+ import { isAbsolute, join } from 'path';
25
+ import { readStdin } from '../../shared/stdin-reader.js';
26
+ import { stateExists, isGateApproved } from '../../shared/state-reader.js';
27
+ import {
28
+ isFeaturePath,
29
+ extractFeatureName,
30
+ getBasename,
31
+ PROTECTED_SPEC_FILES,
32
+ MUTABLE_SPEC_FIELDS
33
+ } from '../../shared/phase-utils.js';
34
+ import { block, pass } from '../../shared/hook-response.js';
35
+ import { logHookActivity } from '../../shared/activity-logger.js';
36
+ import { getFilePath, getToolName } from '../../shared/payload-utils.js';
37
+
38
+ /**
39
+ * Compare two tasks.json structures and return the list of plan-contract
40
+ * violations — changes that touch anything other than the mutable fields.
41
+ *
42
+ * @param {object} oldDoc - parsed previous file content
43
+ * @param {object} newDoc - parsed proposed file content
44
+ * @param {string[]} mutableFields - field names allowed to change
45
+ * @returns {string[]} human-readable violation messages (empty = OK)
46
+ */
47
+ function diffPlanContract(oldDoc, newDoc, mutableFields) {
48
+ const violations = [];
49
+ const mutable = new Set(mutableFields);
50
+
51
+ if (oldDoc.version !== newDoc.version) {
52
+ violations.push(`'version' changed (${oldDoc.version} → ${newDoc.version})`);
53
+ }
54
+ if (oldDoc.feature !== newDoc.feature) {
55
+ violations.push(`'feature' changed (${oldDoc.feature} → ${newDoc.feature})`);
56
+ }
57
+
58
+ const oldTasks = Array.isArray(oldDoc.tasks) ? oldDoc.tasks : [];
59
+ const newTasks = Array.isArray(newDoc.tasks) ? newDoc.tasks : [];
60
+
61
+ if (oldTasks.length !== newTasks.length) {
62
+ violations.push(`task count changed (${oldTasks.length} → ${newTasks.length}) — tasks cannot be added or removed`);
63
+ }
64
+
65
+ const oldById = new Map(oldTasks.map(t => [t.id, t]));
66
+ const newById = new Map(newTasks.map(t => [t.id, t]));
67
+
68
+ // A task present before must still exist (no removals / id renames).
69
+ for (const id of oldById.keys()) {
70
+ if (!newById.has(id)) violations.push(`task '${id}' was removed`);
71
+ }
72
+ for (const id of newById.keys()) {
73
+ if (!oldById.has(id)) violations.push(`task '${id}' was added`);
74
+ }
75
+
76
+ // For tasks present on both sides, only mutable fields may differ.
77
+ for (const [id, newTask] of newById) {
78
+ const oldTask = oldById.get(id);
79
+ if (!oldTask) continue;
80
+ const keys = new Set([...Object.keys(oldTask), ...Object.keys(newTask)]);
81
+ for (const key of keys) {
82
+ if (mutable.has(key)) continue;
83
+ if (JSON.stringify(oldTask[key]) !== JSON.stringify(newTask[key])) {
84
+ violations.push(`task '${id}': immutable field '${key}' changed`);
85
+ }
86
+ }
87
+ }
88
+
89
+ return violations;
90
+ }
91
+
92
+ try {
93
+ // Amend mode: bypass spec protection for legitimate in-implementation corrections
94
+ if (process.env.MORPH_AMEND_PHASE) {
95
+ console.error(`[morph-spec] AMEND MODE: bypassing spec protection for phase '${process.env.MORPH_AMEND_PHASE}'`);
96
+ pass();
97
+ }
98
+
99
+ if (!stateExists()) pass();
100
+
101
+ const payload = await readStdin();
102
+ if (!payload) pass();
103
+
104
+ const filePath = getFilePath(payload);
105
+ if (!filePath) pass();
106
+
107
+ // Only check files inside .morph/features/
108
+ if (!isFeaturePath(filePath)) pass();
109
+
110
+ const featureName = extractFeatureName(filePath);
111
+ if (!featureName) pass();
112
+
113
+ const filename = getBasename(filePath);
114
+ const requiredGate = PROTECTED_SPEC_FILES[filename];
115
+
116
+ // Not a protected file
117
+ if (!requiredGate) pass();
118
+
119
+ // Gate not yet approved — allow editing
120
+ if (!isGateApproved(featureName, requiredGate)) {
121
+ logHookActivity('protect-spec-files', 'PreToolUse', 'ok');
122
+ pass();
123
+ }
124
+
125
+ // ── Gate approved — file is locked ──────────────────────────────────────
126
+ const mutableFields = MUTABLE_SPEC_FIELDS[filename];
127
+
128
+ // Files with mutable progress fields (e.g. tasks.json): allow a full Write
129
+ // that touches only those fields. Anything else stays blocked.
130
+ if (mutableFields) {
131
+ const toolName = getToolName(payload);
132
+ const newContent = payload?.tool_input?.content;
133
+
134
+ // Edit is never the legitimate progress path — progress is written as a
135
+ // full file via writeTasksJson(). Block Edit; fall through to the message.
136
+ if (toolName !== 'Write' || typeof newContent !== 'string') {
137
+ logHookActivity('protect-spec-files', 'PreToolUse', 'blocked');
138
+ block(
139
+ `MORPH-SPEC: '${filename}' is locked — the '${requiredGate}' gate has been approved.\n` +
140
+ `Progress updates (${mutableFields.join(', ')}) must be written as a full-file\n` +
141
+ `Write, not an Edit. The plan contract (titles, dependencies, doneCriteria…)\n` +
142
+ `is frozen. To change the plan itself, ask the user to revoke the\n` +
143
+ `'${requiredGate}' gate in .morph/state.json, then re-present it for approval.`
144
+ );
145
+ }
146
+
147
+ // Semantic diff: old = current file on disk, new = proposed content.
148
+ // Any failure to read/parse → fail-open (pass), never a false block.
149
+ let oldDoc, newDoc;
150
+ try {
151
+ // filePath may already be absolute (Claude Code usually sends absolute
152
+ // paths); only join against cwd when it is relative.
153
+ const absPath = isAbsolute(filePath) ? filePath : join(process.cwd(), filePath);
154
+ oldDoc = JSON.parse(readFileSync(absPath, 'utf-8'));
155
+ newDoc = JSON.parse(newContent);
156
+ } catch {
157
+ // Cannot compare — fail-open rather than block a possibly-valid write.
158
+ logHookActivity('protect-spec-files', 'PreToolUse', 'ok');
159
+ pass();
160
+ }
161
+
162
+ const violations = diffPlanContract(oldDoc, newDoc, mutableFields);
163
+ if (violations.length === 0) {
164
+ // Only mutable progress fields changed — this is a legitimate
165
+ // implement-phase update.
166
+ logHookActivity('protect-spec-files', 'PreToolUse', 'ok');
167
+ pass();
168
+ }
169
+
170
+ logHookActivity('protect-spec-files', 'PreToolUse', 'blocked');
171
+ block(
172
+ `MORPH-SPEC: '${filename}' is locked — the '${requiredGate}' gate has been approved.\n` +
173
+ `This Write changes the frozen plan contract:\n` +
174
+ violations.map(v => ` • ${v}`).join('\n') + '\n\n' +
175
+ `During the implement phase only these fields may change: ${mutableFields.join(', ')}.\n` +
176
+ `To change the plan itself, ask the user to revoke the '${requiredGate}' gate\n` +
177
+ `in .morph/state.json, then re-present it for approval.`
178
+ );
179
+ }
180
+
181
+ // Fully-locked file (spec.md, mandate.md, design files): blanket block.
182
+ logHookActivity('protect-spec-files', 'PreToolUse', 'blocked');
183
+ block(
184
+ `MORPH-SPEC: '${filename}' is locked — the '${requiredGate}' gate has been approved.\n` +
185
+ `Editing approved specs breaks the spec contract.\n\n` +
186
+ `If changes are truly needed, ask the user to revoke the gate approval\n` +
187
+ `in .morph/state.json (set approvals.${requiredGate} to false), make\n` +
188
+ `your edits, then re-present the gate for approval.`
189
+ );
190
+ } catch {
191
+ // Fail-open
192
+ process.exit(0);
193
+ }
@@ -65,6 +65,16 @@ export const PROTECTED_SPEC_FILES = {
65
65
  'tasks.json': 'plan',
66
66
  };
67
67
 
68
+ /**
69
+ * For protected files that carry mutable progress fields: filename → the
70
+ * top-level array-item fields that may change after the gate is approved.
71
+ * When a protected file is listed here, the protect-spec-files hook does a
72
+ * semantic diff (allowing only these fields to change) instead of a blanket block.
73
+ */
74
+ export const MUTABLE_SPEC_FIELDS = {
75
+ 'tasks.json': ['status', 'outputs', 'notes'],
76
+ };
77
+
68
78
  /**
69
79
  * Extract feature name from a .morph/features/{feature}/... path
70
80
  * @param {string} filePath - File path to analyze
@@ -125,19 +125,26 @@ After making structural changes (new packages, dependencies, project config chan
125
125
 
126
126
  BEFORE implementing ANY task during the implement phase:
127
127
 
128
- 1. **Read tasks.md** → identify the FR references for the current task (e.g., `[FR001, FR003]`)
129
- 2. **Read spec.md** → the sections for those specific FRs
130
- 3. **Read contracts.cs/.ts** → the DTOs/interfaces referenced by those FRs
128
+ 1. **Read tasks.json** → identify the current task object (`id`, `description`, `doneCriteria`, `dependencies`)
129
+ 2. **Read spec.md** → the FR sections that the task `description` references
130
+ 3. **Read the contracts** → the DTOs/interfaces are defined inside `spec.md` (v5 keeps contracts in the spec, not separate files)
131
131
  4. Only then begin implementation
132
132
 
133
133
  This is **NON-NEGOTIABLE**. The spec is the source of truth. Code that doesn't match the spec is a bug.
134
134
 
135
- Every task in `tasks.md` MUST have FR cross-references in this format:
136
- ```
137
- ### T001: Create user endpoint [FR001, FR003]
138
- - **Spec refs:** FR001 (user CRUD), FR003 (input validation)
139
- - **Contracts:** CreateUserRequest, CreateUserResponse, UserErrors
140
- - **Files:** Features/UserFeature/Create/Handler.cs, Validator.cs, Endpoint.cs
135
+ Every task in `tasks.json` MUST reference its FR(s) in the `description` field so the link to the spec is explicit:
136
+ ```json
137
+ {
138
+ "id": "T1",
139
+ "title": "Create user endpoint",
140
+ "description": "Implement user CRUD (FR001) and input validation (FR003). Contracts: CreateUserRequest, CreateUserResponse, UserErrors.",
141
+ "dependencies": [],
142
+ "effort": "M",
143
+ "doneCriteria": "POST /users creates a user and rejects invalid input",
144
+ "status": "pending",
145
+ "outputs": ["Features/UserFeature/Create/Handler.cs", "Validator.cs", "Endpoint.cs"],
146
+ "group": "backend"
147
+ }
141
148
  ```
142
149
 
143
150
  ---
@@ -4,6 +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: "7.2.0"
7
8
  ---
8
9
 
9
10
  # morph-apply
@@ -4,6 +4,7 @@ description: Entende o negócio, os usuários e a arquitetura antes de qualquer
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: "7.2.0"
7
8
  ---
8
9
 
9
10
  # morph-brainstorming
@@ -4,6 +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: "7.2.0"
7
8
  ---
8
9
 
9
10
  # morph-eval
@@ -4,6 +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: "7.2.0"
7
8
  ---
8
9
 
9
10
  # morph-implement
@@ -78,6 +79,12 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
78
79
  await writeTasksJson(tasksPath, data);
79
80
  ```
80
81
 
82
+ `tasks.json` é protegido após o Gate 2, mas os campos de progresso (`status`,
83
+ `outputs`, `notes`) continuam graváveis durante a fase implement. `writeTasksJson`
84
+ grava o arquivo **inteiro** (Write) — esse é o caminho permitido. Nunca use a
85
+ tool `Edit` em `tasks.json` após o Gate 2: o hook `protect-spec-files` bloqueia
86
+ Edit e qualquer mudança em campos de plano (`title`, `dependencies`, `doneCriteria`…).
87
+
81
88
  9. Quando invocada por `morph-apply`, retornar o controle à skill orquestradora — ela decide qual task vem em seguida via `getReadyTasks(data)`. Quando invocada manualmente, verificar se há próxima task ready e iniciá-la imediatamente aplicando esta skill desde o passo 1 (auto-advance). Pause apenas se encontrar ambiguidade bloqueante não coberta pelo mandate.
82
89
 
83
90
  ## Output esperado
@@ -4,6 +4,7 @@ description: Guia a produção dos três artefatos de planejamento de uma featur
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: "7.2.0"
7
8
  ---
8
9
 
9
10
  # morph-plan
@@ -4,6 +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: "7.2.0"
7
8
  ---
8
9
 
9
10
  # morph-proposal
@@ -4,6 +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: "7.2.0"
7
8
  ---
8
9
 
9
10
  # morph-review
@@ -4,6 +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: "7.2.0"
7
8
  ---
8
9
 
9
10
  # morph-stack-scan
@@ -4,6 +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: "7.2.0"
7
8
  ---
8
9
 
9
10
  # morph-standards
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "7.1.1",
3
+ "version": "7.2.0",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -20,6 +20,11 @@ import { join } from 'path';
20
20
  * phase - The morph-spec phase that produces this output
21
21
  * protected - Whether the file requires an approval gate before editing
22
22
  * approvalGate - (optional) Which gate protects this file
23
+ * mutableFields- (optional) For protected JSON files: top-level array-item
24
+ * field names that MAY still change after the gate is approved.
25
+ * Lets the implement phase update task progress without
26
+ * unfreezing the plan contract. When present, the protect
27
+ * hook does a semantic diff instead of a blanket block.
23
28
  */
24
29
  export const OUTPUT_SCHEMA = {
25
30
  // 0-proposal/
@@ -36,7 +41,7 @@ export const OUTPUT_SCHEMA = {
36
41
  // 2-plan/
37
42
  spec: { filename: 'spec.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
38
43
  mandate: { filename: 'mandate.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
39
- tasks: { filename: 'tasks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
44
+ tasks: { filename: 'tasks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan', mutableFields: ['status', 'outputs', 'notes'] },
40
45
  decisions: { filename: 'decisions.md', phaseDir: '2-plan', phase: 'plan', protected: false },
41
46
 
42
47
  // 3-implement/
@@ -119,6 +124,18 @@ export const PROTECTED_SPEC_FILES = Object.fromEntries(
119
124
  .map(([, entry]) => [entry.filename, entry.approvalGate])
120
125
  );
121
126
 
127
+ /**
128
+ * For protected files that carry mutable progress fields: filename → the
129
+ * top-level array-item fields that may change after the gate is approved.
130
+ * The protect-spec-files hook uses this to allow progress updates (e.g.
131
+ * task status) while still freezing the plan contract.
132
+ */
133
+ export const MUTABLE_SPEC_FIELDS = Object.fromEntries(
134
+ Object.entries(OUTPUT_SCHEMA)
135
+ .filter(([, entry]) => entry.protected && entry.approvalGate && entry.mutableFields)
136
+ .map(([, entry]) => [entry.filename, entry.mutableFields])
137
+ );
138
+
122
139
  // ============================================================================
123
140
  // Backwards-compat shim (will be removed in v5.1)
124
141
  // ============================================================================