@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
|
@@ -1,287 +1,315 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MORPH-SPEC Feature Validation Command
|
|
3
|
-
*
|
|
4
|
-
* Content-aware validation of feature outputs at each phase.
|
|
5
|
-
* Checks section presence, schema consistency, and contract correctness.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* morph-spec validate <feature-name> [--phase <phase>] [--json] [--verbose]
|
|
9
|
-
*
|
|
10
|
-
* Example:
|
|
11
|
-
* morph-spec validate user-portal --phase design
|
|
12
|
-
* morph-spec validate user-portal --json
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import fs from 'fs';
|
|
16
|
-
import path from 'path';
|
|
17
|
-
import chalk from 'chalk';
|
|
18
|
-
import { getFeature } from '../../core/state/state-manager.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
'
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
1
|
+
/**
|
|
2
|
+
* MORPH-SPEC Feature Validation Command
|
|
3
|
+
*
|
|
4
|
+
* Content-aware validation of feature outputs at each phase.
|
|
5
|
+
* Checks section presence, schema consistency, and contract correctness.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* morph-spec validate <feature-name> [--phase <phase>] [--json] [--verbose]
|
|
9
|
+
*
|
|
10
|
+
* Example:
|
|
11
|
+
* morph-spec validate user-portal --phase design
|
|
12
|
+
* morph-spec validate user-portal --json
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import chalk from 'chalk';
|
|
18
|
+
import { getFeature } from '../../core/state/state-manager.js';
|
|
19
|
+
import { validateTasksJson } from '../../lib/tasks/tasks-json-parser.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Expected sections per output file
|
|
23
|
+
*/
|
|
24
|
+
const OUTPUT_SECTIONS = {
|
|
25
|
+
'proposal.md': {
|
|
26
|
+
required: ['Overview', 'Problem', 'Solution', 'Scope'],
|
|
27
|
+
optional: ['Agents', 'Stack', 'Risks']
|
|
28
|
+
},
|
|
29
|
+
'spec.md': {
|
|
30
|
+
required: [
|
|
31
|
+
'Overview', 'Functional Requirements', 'Non-Functional Requirements',
|
|
32
|
+
'Technical Architecture', 'Data Model'
|
|
33
|
+
],
|
|
34
|
+
optional: [
|
|
35
|
+
'Infrastructure', 'Testing Strategy', 'Security',
|
|
36
|
+
'Performance', 'Edge Cases', 'Clarifications'
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
'mandate.md': {
|
|
40
|
+
required: ['Stack', 'Standards', 'Done'],
|
|
41
|
+
optional: ['Patterns proibidos', 'Riscos']
|
|
42
|
+
},
|
|
43
|
+
'decisions.md': {
|
|
44
|
+
required: ['ADR', 'Status', 'Context', 'Decision'],
|
|
45
|
+
optional: ['Consequences', 'Alternatives', 'Costs']
|
|
46
|
+
},
|
|
47
|
+
'review-report.md': {
|
|
48
|
+
required: ['Scores', 'Recommendation'],
|
|
49
|
+
optional: ['Blockers', 'Improvements']
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Phase-to-output mapping (v5 NLH)
|
|
55
|
+
*/
|
|
56
|
+
const PHASE_OUTPUTS = {
|
|
57
|
+
proposal: ['proposal.md'],
|
|
58
|
+
uiux: ['design-system.md', 'mockups.md', 'components.md', 'flows.md'],
|
|
59
|
+
plan: ['spec.md', 'mandate.md', 'tasks.json'],
|
|
60
|
+
implement: ['recap.md'],
|
|
61
|
+
review: ['review-report.md'],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Phase-to-folder mapping (v5 filesystem convention)
|
|
66
|
+
*/
|
|
67
|
+
const PHASE_FOLDERS = {
|
|
68
|
+
proposal: '0-proposal',
|
|
69
|
+
uiux: '1-design',
|
|
70
|
+
plan: '2-plan',
|
|
71
|
+
implement: '3-implement',
|
|
72
|
+
review: '4-review',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validate a single output file
|
|
77
|
+
*/
|
|
78
|
+
function validateOutput(featurePath, outputFile) {
|
|
79
|
+
const filePath = path.join(featurePath, outputFile);
|
|
80
|
+
const result = {
|
|
81
|
+
file: outputFile,
|
|
82
|
+
exists: false,
|
|
83
|
+
sections: { found: [], missing: [], optional: [] },
|
|
84
|
+
warnings: [],
|
|
85
|
+
errors: [],
|
|
86
|
+
lineCount: 0
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (!fs.existsSync(filePath)) {
|
|
90
|
+
result.errors.push(`File not found: ${outputFile}`);
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
result.exists = true;
|
|
95
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
96
|
+
result.lineCount = content.split('\n').length;
|
|
97
|
+
|
|
98
|
+
// tasks.json: structural validation against the schema, not markdown sections.
|
|
99
|
+
if (outputFile === 'tasks.json') {
|
|
100
|
+
validateTasksJsonOutput(content, result);
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Skip section checks for files without defined sections
|
|
105
|
+
const sections = OUTPUT_SECTIONS[outputFile];
|
|
106
|
+
if (!sections) {
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// v5: only markdown outputs — contracts moved into spec.md
|
|
111
|
+
validateMarkdownSections(content, sections, result);
|
|
112
|
+
|
|
113
|
+
// Size warnings
|
|
114
|
+
if (result.lineCount < 10) {
|
|
115
|
+
result.warnings.push(`File is very short (${result.lineCount} lines) — may be incomplete`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validate tasks.json structurally: parseable JSON + conforms to the schema +
|
|
123
|
+
* has at least one task.
|
|
124
|
+
*/
|
|
125
|
+
function validateTasksJsonOutput(content, result) {
|
|
126
|
+
let data;
|
|
127
|
+
try {
|
|
128
|
+
data = JSON.parse(content);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
result.errors.push(`tasks.json is not valid JSON: ${err.message}`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const { valid, errors } = validateTasksJson(data);
|
|
134
|
+
if (!valid) {
|
|
135
|
+
for (const e of errors) result.errors.push(e);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (data.tasks.length === 0) {
|
|
139
|
+
result.errors.push('tasks.json has no task entries');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
result.sections.found.push(`${data.tasks.length} task(s)`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Validate markdown sections
|
|
147
|
+
*/
|
|
148
|
+
function validateMarkdownSections(content, sections, result) {
|
|
149
|
+
const contentLower = content.toLowerCase();
|
|
150
|
+
|
|
151
|
+
for (const section of sections.required) {
|
|
152
|
+
if (contentLower.includes(section.toLowerCase())) {
|
|
153
|
+
result.sections.found.push(section);
|
|
154
|
+
} else {
|
|
155
|
+
result.sections.missing.push(section);
|
|
156
|
+
result.errors.push(`Missing required section: "${section}"`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
for (const section of (sections.optional || [])) {
|
|
161
|
+
if (contentLower.includes(section.toLowerCase())) {
|
|
162
|
+
result.sections.optional.push(section);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Main validate command
|
|
169
|
+
*/
|
|
170
|
+
export async function validateFeatureCommand(featureName, options = {}) {
|
|
171
|
+
if (!featureName) {
|
|
172
|
+
console.error(chalk.red('Error: Feature name required'));
|
|
173
|
+
console.error(chalk.gray('Usage: morph-spec validate <feature-name> [--phase <phase>]'));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const featurePath = path.join(process.cwd(), '.morph/features', featureName);
|
|
178
|
+
|
|
179
|
+
if (!fs.existsSync(featurePath)) {
|
|
180
|
+
console.error(chalk.red(`Error: Feature directory not found: ${featurePath}`));
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Determine which outputs to validate
|
|
185
|
+
const phase = options.phase || null;
|
|
186
|
+
let outputFiles;
|
|
187
|
+
|
|
188
|
+
if (phase) {
|
|
189
|
+
outputFiles = PHASE_OUTPUTS[phase];
|
|
190
|
+
if (!outputFiles) {
|
|
191
|
+
console.error(chalk.red(`Unknown phase: ${phase}`));
|
|
192
|
+
console.error(chalk.gray(`Valid phases: ${Object.keys(PHASE_OUTPUTS).join(', ')}`));
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
// Validate all existing outputs across all phase folders
|
|
197
|
+
outputFiles = [];
|
|
198
|
+
const entries = fs.readdirSync(featurePath, { withFileTypes: true });
|
|
199
|
+
const isOutput = name => name.endsWith('.md') || name.endsWith('.cs') || name === 'tasks.json';
|
|
200
|
+
for (const entry of entries) {
|
|
201
|
+
if (entry.isDirectory()) {
|
|
202
|
+
const subDir = path.join(featurePath, entry.name);
|
|
203
|
+
const files = fs.readdirSync(subDir).filter(isOutput);
|
|
204
|
+
outputFiles.push(...files.map(f => ({ file: f, dir: subDir })));
|
|
205
|
+
} else if (isOutput(entry.name)) {
|
|
206
|
+
outputFiles.push({ file: entry.name, dir: featurePath });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Resolve phase folder for path lookup
|
|
212
|
+
const phaseFolder = phase ? PHASE_FOLDERS[phase] : null;
|
|
213
|
+
const searchPath = phaseFolder ? path.join(featurePath, phaseFolder) : featurePath;
|
|
214
|
+
|
|
215
|
+
// Run validation
|
|
216
|
+
const results = phase
|
|
217
|
+
? outputFiles.map(file => validateOutput(searchPath, file))
|
|
218
|
+
: outputFiles.map(({ file, dir }) => validateOutput(dir, file));
|
|
219
|
+
const schemaWarnings = []; // v5: cross-validation removed (contracts in spec.md)
|
|
220
|
+
|
|
221
|
+
// Aggregate results
|
|
222
|
+
const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
|
|
223
|
+
const totalWarnings = results.reduce((sum, r) => sum + r.warnings.length, 0) + schemaWarnings.length;
|
|
224
|
+
const totalFound = results.reduce((sum, r) => sum + r.sections.found.length, 0);
|
|
225
|
+
|
|
226
|
+
// JSON output mode
|
|
227
|
+
if (options.json) {
|
|
228
|
+
const output = {
|
|
229
|
+
feature: featureName,
|
|
230
|
+
phase: phase || 'all',
|
|
231
|
+
valid: totalErrors === 0,
|
|
232
|
+
summary: {
|
|
233
|
+
files: results.length,
|
|
234
|
+
errors: totalErrors,
|
|
235
|
+
warnings: totalWarnings,
|
|
236
|
+
sectionsFound: totalFound
|
|
237
|
+
},
|
|
238
|
+
results: results.map(r => ({
|
|
239
|
+
file: r.file,
|
|
240
|
+
exists: r.exists,
|
|
241
|
+
lineCount: r.lineCount,
|
|
242
|
+
errors: r.errors,
|
|
243
|
+
warnings: r.warnings,
|
|
244
|
+
sectionsFound: r.sections.found,
|
|
245
|
+
sectionsMissing: r.sections.missing
|
|
246
|
+
})),
|
|
247
|
+
schemaWarnings
|
|
248
|
+
};
|
|
249
|
+
console.log(JSON.stringify(output, null, 2));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Visual output
|
|
254
|
+
console.log(chalk.cyan('\n┌────────────────────────────────────────────────────────────┐'));
|
|
255
|
+
console.log(chalk.cyan('│ MORPH-SPEC FEATURE VALIDATOR │'));
|
|
256
|
+
console.log(chalk.cyan('└────────────────────────────────────────────────────────────┘\n'));
|
|
257
|
+
|
|
258
|
+
console.log(chalk.white.bold(`Feature: ${featureName}`));
|
|
259
|
+
if (phase) console.log(chalk.gray(`Phase: ${phase}`));
|
|
260
|
+
console.log('');
|
|
261
|
+
|
|
262
|
+
// File-by-file results
|
|
263
|
+
for (const result of results) {
|
|
264
|
+
const symbol = result.exists
|
|
265
|
+
? (result.errors.length === 0 ? '✅' : '❌')
|
|
266
|
+
: '⬜';
|
|
267
|
+
const name = result.exists
|
|
268
|
+
? (result.errors.length === 0 ? chalk.green(result.file) : chalk.red(result.file))
|
|
269
|
+
: chalk.gray(result.file);
|
|
270
|
+
|
|
271
|
+
console.log(`${symbol} ${name}${result.exists ? chalk.gray(` (${result.lineCount} lines)`) : ''}`);
|
|
272
|
+
|
|
273
|
+
// Sections found
|
|
274
|
+
if (result.sections.found.length > 0 && (options.verbose || result.errors.length > 0)) {
|
|
275
|
+
result.sections.found.forEach(s => {
|
|
276
|
+
console.log(chalk.green(` ✓ ${s}`));
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Errors
|
|
281
|
+
result.errors.forEach(err => {
|
|
282
|
+
console.log(chalk.red(` ✗ ${err}`));
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Warnings
|
|
286
|
+
result.warnings.forEach(warn => {
|
|
287
|
+
console.log(chalk.yellow(` ⚠ ${warn}`));
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Schema cross-validation
|
|
292
|
+
if (schemaWarnings.length > 0) {
|
|
293
|
+
console.log(chalk.yellow('\nSchema Cross-Validation:'));
|
|
294
|
+
schemaWarnings.forEach(w => {
|
|
295
|
+
console.log(chalk.yellow(` ⚠ ${w}`));
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Summary
|
|
300
|
+
console.log('');
|
|
301
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
302
|
+
|
|
303
|
+
if (totalErrors === 0) {
|
|
304
|
+
console.log(chalk.green(`\n✓ VALIDATION PASSED — ${results.filter(r => r.exists).length} files validated`));
|
|
305
|
+
if (totalWarnings > 0) {
|
|
306
|
+
console.log(chalk.yellow(` ${totalWarnings} warning(s) — review recommended`));
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
console.log(chalk.red(`\n✗ VALIDATION FAILED — ${totalErrors} error(s), ${totalWarnings} warning(s)`));
|
|
310
|
+
console.log(chalk.yellow(' Fix errors before advancing to the next phase'));
|
|
311
|
+
}
|
|
312
|
+
console.log('');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export default validateFeatureCommand;
|
|
@@ -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
|
// ============================================================================
|