@polymorphism-tech/morph-spec 7.1.1 → 7.3.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/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +4 -2
- package/framework/MORPH.md +3 -3
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-proposal.md +1 -1
- package/framework/hooks/README.md +1 -1
- package/framework/hooks/claude-code/core/protect-spec-files.js +193 -72
- package/framework/hooks/claude-code/core/validate-completion.js +136 -136
- package/framework/hooks/shared/phase-utils.js +10 -0
- package/framework/phases.json +1 -1
- package/framework/rules/morph-workflow.md +16 -9
- package/framework/skills/README.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +1 -0
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -0
- package/framework/skills/core/morph-eval/SKILL.md +1 -0
- package/framework/skills/core/morph-implement/SKILL.md +7 -0
- package/framework/skills/core/morph-plan/SKILL.md +1 -0
- package/framework/skills/core/morph-proposal/SKILL.md +1 -0
- package/framework/skills/core/morph-review/SKILL.md +1 -0
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -0
- package/framework/skills/core/morph-standards/SKILL.md +1 -0
- package/framework/templates/README.md +708 -709
- package/framework/templates/REGISTRY.json +1 -20
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/proposal.md +171 -171
- package/framework/templates/feature/recap.md +145 -145
- package/package.json +1 -1
- package/src/commands/project/doctor.js +941 -946
- package/src/commands/project/status.js +373 -376
- package/src/commands/validation/validate-feature.js +315 -287
- package/src/core/paths/output-schema.js +18 -1
- package/src/core/state/state-manager.js +900 -934
- package/src/dashboard/app/App.jsx +29 -31
- package/src/dashboard/app/components/Sidebar.jsx +44 -45
- package/src/dashboard/scanner/index.js +20 -21
- package/src/lib/context/context-builder.js +3 -3
- package/src/lib/generators/context-generator.js +2 -3
- package/src/lib/generators/mandate-generator.js +48 -92
- package/src/lib/generators/metadata-extractor.js +385 -386
- package/src/lib/generators/recap-generator.js +285 -285
- package/src/lib/validators/content/content-validator.js +89 -286
- package/src/lib/validators/content/index.js +0 -1
- package/framework/templates/feature/tasks.md +0 -199
- package/framework/workflows/README.md +0 -1041
- package/framework/workflows/configs/fast-track.json +0 -46
- package/framework/workflows/configs/full.json +0 -64
- package/framework/workflows/configs/quick.json +0 -32
- package/framework/workflows/configs/standard.json +0 -57
- package/framework/workflows/docs/STORY-DRIVEN-DEVELOPMENT.md +0 -392
- package/framework/workflows/docs/design-impl.md +0 -37
- package/framework/workflows/docs/enforcement-pipeline.md +0 -668
- package/framework/workflows/docs/fast-track.md +0 -29
- package/framework/workflows/docs/full-morph.md +0 -69
- package/framework/workflows/docs/standard.md +0 -49
- package/framework/workflows/docs/ui-refresh.md +0 -39
- package/src/commands/state/index.js +0 -7
- package/src/core/workflows/workflow-detector.js +0 -586
- package/src/core/workflows/workflow-router.js +0 -204
- package/src/dashboard/app/pages/Workflows.jsx +0 -346
- package/src/dashboard/scanner/workflows-scanner.js +0 -18
- package/src/lib/phase-chain/phase-validator.js +0 -359
- package/src/lib/validators/content/cross-phase-validator.js +0 -281
- package/src/lib/validators/spec-validator.js +0 -312
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.
|
|
6
|
+
**Version:** 7.3.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.
|
|
368
|
+
*morph-spec v7.3.0 by [Polymorphism Tech](https://polymorphism.tech)*
|
package/claude-plugin.json
CHANGED
package/docs/CHEATSHEET.md
CHANGED
package/docs/QUICKSTART.md
CHANGED
package/framework/CLAUDE.md
CHANGED
|
@@ -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.
|
|
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.
|
|
@@ -41,7 +43,7 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
|
|
|
41
43
|
|-------|-------------|
|
|
42
44
|
| `morph-brainstorming` | Entender negócio, escanear stack, produzir proposal.md |
|
|
43
45
|
| `morph-stack-scan` | Detectar desvios de stack em projeto brownfield |
|
|
44
|
-
| `morph-plan` | Produzir spec.md + mandate.md + tasks.
|
|
46
|
+
| `morph-plan` | Produzir spec.md + mandate.md + tasks.json |
|
|
45
47
|
| `morph-implement` | Executar uma tarefa com auto-advance entre tasks |
|
|
46
48
|
| `morph-eval` | Aplicar rubricas e produzir score 0-10 |
|
|
47
49
|
| `morph-review` | Produzir review-report.md (Gate 3) |
|
package/framework/MORPH.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
@@ -25,7 +25,7 @@ Se algum estiver faltando, abortar e pedir ao usuário para rodar `/morph-propos
|
|
|
25
25
|
|
|
26
26
|
## Anti-padrões
|
|
27
27
|
|
|
28
|
-
- Não pausar entre tarefas para confirmação — o tasks.
|
|
28
|
+
- Não pausar entre tarefas para confirmação — o tasks.json já foi aprovado no Gate 2.
|
|
29
29
|
- Não escrever código fora de `3-implement/` ou da source tree do projeto.
|
|
30
30
|
- Não modificar `spec.md` ou `mandate.md` durante a implementação — esses arquivos foram congelados pelo Gate 2.
|
|
31
31
|
|
|
@@ -32,7 +32,7 @@ Conduz a feature do request inicial até o Gate 2 (plano aprovado), sem interven
|
|
|
32
32
|
|
|
33
33
|
4. **Após Gate 1, invocar `morph-plan`** (skill em `framework/skills/core/morph-plan/`):
|
|
34
34
|
- Lê `proposal.md`.
|
|
35
|
-
- Produz `spec.md`, `mandate.md`, `tasks.
|
|
35
|
+
- Produz `spec.md`, `mandate.md`, `tasks.json` em `.morph/features/{feature-name}/2-plan/`.
|
|
36
36
|
- Apresenta o **Gate 2** com template fixo (ver SKILL.md passo 5).
|
|
37
37
|
|
|
38
38
|
5. **Aguardar aprovação do Gate 2.**
|
|
@@ -128,7 +128,7 @@ Claude Code sends JSON to stdin: { tool_input: { file_path: "..." } }
|
|
|
128
128
|
protect-spec-files.js
|
|
129
129
|
├── Is in .morph/features/{feature}/?
|
|
130
130
|
│ ├── Is spec.md and design gate approved? → BLOCK
|
|
131
|
-
│ ├── Is tasks.
|
|
131
|
+
│ ├── Is tasks.json and tasks gate approved? → BLOCK
|
|
132
132
|
│ └── Gate not approved → pass
|
|
133
133
|
└── Not a feature file → pass
|
|
134
134
|
↓
|
|
@@ -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,
|
|
10
|
-
* - 1-design/design-system.md, etc. → blocked if 'uiux' gate approved
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
}
|