@polymorphism-tech/morph-spec 7.2.0 → 7.4.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/CLAUDE.md +1 -1
- package/README.md +2 -2
- package/bin/morph-spec.js +4 -2
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -2
- package/framework/MORPH.md +29 -12
- package/framework/agents.json +132 -132
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-proposal.md +1 -1
- package/framework/evals/rubrics/README.md +9 -8
- package/framework/hooks/README.md +3 -1
- package/framework/hooks/claude-code/core/gate-guard.js +160 -0
- package/framework/hooks/claude-code/core/state-sync.js +15 -3
- package/framework/hooks/claude-code/core/validate-completion.js +241 -136
- package/framework/hooks/shared/state-reader.js +236 -221
- package/framework/phases.json +1 -1
- package/framework/skills/README.md +2 -2
- package/framework/skills/core/morph-apply/SKILL.md +63 -21
- package/framework/skills/core/morph-brainstorming/SKILL.md +5 -1
- package/framework/skills/core/morph-eval/SKILL.md +4 -5
- package/framework/skills/core/morph-implement/SKILL.md +47 -19
- package/framework/skills/core/morph-plan/SKILL.md +8 -2
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +13 -6
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/ai-agents/setup.md +258 -244
- 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/init.js +517 -515
- package/src/commands/project/status.js +373 -376
- package/src/commands/state/approve.js +251 -213
- package/src/commands/validation/validate-feature.js +356 -287
- package/src/core/state/state-manager.js +910 -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/risk-detector.js +2 -2
- package/src/lib/validators/content/content-validator.js +89 -286
- package/src/lib/validators/content/index.js +0 -1
- package/src/utils/hooks-installer.js +527 -516
- 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,356 @@
|
|
|
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
|
-
optional: ['
|
|
41
|
-
},
|
|
42
|
-
'
|
|
43
|
-
required: [
|
|
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
|
-
const
|
|
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
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
console.
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
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
|
+
* Each section entry can be a string (single substring to match) or an array
|
|
25
|
+
* of acceptable substrings (any-of match). This lets pt-BR and English
|
|
26
|
+
* coexist — e.g. "Problem" matches both "Problem" and "Problema", but
|
|
27
|
+
* "Scope" needs to be paired with "Escopo" explicitly.
|
|
28
|
+
*
|
|
29
|
+
* The matcher (`validateMarkdownSections`) does case-insensitive substring
|
|
30
|
+
* matching against the whole file content.
|
|
31
|
+
*/
|
|
32
|
+
const OUTPUT_SECTIONS = {
|
|
33
|
+
'proposal.md': {
|
|
34
|
+
required: [
|
|
35
|
+
'Overview',
|
|
36
|
+
'Problem', // also matches Problema
|
|
37
|
+
['Solution', 'Solução'],
|
|
38
|
+
['Scope', 'Escopo']
|
|
39
|
+
],
|
|
40
|
+
optional: ['Agents', 'Stack', ['Risks', 'Riscos']]
|
|
41
|
+
},
|
|
42
|
+
'spec.md': {
|
|
43
|
+
required: [
|
|
44
|
+
'Overview',
|
|
45
|
+
['Functional Requirements', 'Requisitos Funcionais'],
|
|
46
|
+
['Non-Functional Requirements', 'Requisitos Não-Funcionais', 'Requisitos Nao-Funcionais'],
|
|
47
|
+
['Technical Architecture', 'Arquitetura'],
|
|
48
|
+
['Data Model', 'Modelo de Dados', 'Contratos']
|
|
49
|
+
],
|
|
50
|
+
optional: [
|
|
51
|
+
['Infrastructure', 'Infraestrutura'],
|
|
52
|
+
['Testing Strategy', 'Estratégia de Testes', 'Testes'],
|
|
53
|
+
['Security', 'Segurança'],
|
|
54
|
+
'Performance',
|
|
55
|
+
['Edge Cases', 'Casos Limite'],
|
|
56
|
+
['Clarifications', 'Esclarecimentos']
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
'mandate.md': {
|
|
60
|
+
required: ['Stack', 'Standards', ['Done', 'Pronto']],
|
|
61
|
+
optional: [
|
|
62
|
+
['Patterns proibidos', 'Padrões proibidos', 'Forbidden Patterns'],
|
|
63
|
+
['Riscos', 'Risks']
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
'decisions.md': {
|
|
67
|
+
required: ['ADR', 'Status', ['Context', 'Contexto'], ['Decision', 'Decisão']],
|
|
68
|
+
optional: [
|
|
69
|
+
['Consequences', 'Consequências', 'Implicações'],
|
|
70
|
+
['Alternatives', 'Alternativas'],
|
|
71
|
+
['Costs', 'Custos']
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
'review-report.md': {
|
|
75
|
+
required: [['Scores', 'Pontuação', 'Scoring'], ['Recommendation', 'Recomendação']],
|
|
76
|
+
optional: [['Blockers', 'Bloqueantes'], ['Improvements', 'Melhorias']]
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Phase-to-output mapping (v5 NLH)
|
|
82
|
+
*/
|
|
83
|
+
const PHASE_OUTPUTS = {
|
|
84
|
+
proposal: ['proposal.md'],
|
|
85
|
+
uiux: ['design-system.md', 'mockups.md', 'components.md', 'flows.md'],
|
|
86
|
+
plan: ['spec.md', 'mandate.md', 'tasks.json'],
|
|
87
|
+
implement: ['recap.md'],
|
|
88
|
+
review: ['review-report.md'],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Phase-to-folder mapping (v5 filesystem convention)
|
|
93
|
+
*/
|
|
94
|
+
const PHASE_FOLDERS = {
|
|
95
|
+
proposal: '0-proposal',
|
|
96
|
+
uiux: '1-design',
|
|
97
|
+
plan: '2-plan',
|
|
98
|
+
implement: '3-implement',
|
|
99
|
+
review: '4-review',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Validate a single output file
|
|
104
|
+
*/
|
|
105
|
+
function validateOutput(featurePath, outputFile) {
|
|
106
|
+
const filePath = path.join(featurePath, outputFile);
|
|
107
|
+
const result = {
|
|
108
|
+
file: outputFile,
|
|
109
|
+
exists: false,
|
|
110
|
+
sections: { found: [], missing: [], optional: [] },
|
|
111
|
+
warnings: [],
|
|
112
|
+
errors: [],
|
|
113
|
+
lineCount: 0
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if (!fs.existsSync(filePath)) {
|
|
117
|
+
result.errors.push(`File not found: ${outputFile}`);
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
result.exists = true;
|
|
122
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
123
|
+
result.lineCount = content.split('\n').length;
|
|
124
|
+
|
|
125
|
+
// tasks.json: structural validation against the schema, not markdown sections.
|
|
126
|
+
if (outputFile === 'tasks.json') {
|
|
127
|
+
validateTasksJsonOutput(content, result);
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Skip section checks for files without defined sections
|
|
132
|
+
const sections = OUTPUT_SECTIONS[outputFile];
|
|
133
|
+
if (!sections) {
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// v5: only markdown outputs — contracts moved into spec.md
|
|
138
|
+
validateMarkdownSections(content, sections, result);
|
|
139
|
+
|
|
140
|
+
// Size warnings
|
|
141
|
+
if (result.lineCount < 10) {
|
|
142
|
+
result.warnings.push(`File is very short (${result.lineCount} lines) — may be incomplete`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validate tasks.json structurally: parseable JSON + conforms to the schema +
|
|
150
|
+
* has at least one task.
|
|
151
|
+
*/
|
|
152
|
+
function validateTasksJsonOutput(content, result) {
|
|
153
|
+
let data;
|
|
154
|
+
try {
|
|
155
|
+
data = JSON.parse(content);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
result.errors.push(`tasks.json is not valid JSON: ${err.message}`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const { valid, errors } = validateTasksJson(data);
|
|
161
|
+
if (!valid) {
|
|
162
|
+
for (const e of errors) result.errors.push(e);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (data.tasks.length === 0) {
|
|
166
|
+
result.errors.push('tasks.json has no task entries');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
result.sections.found.push(`${data.tasks.length} task(s)`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Validate markdown sections.
|
|
174
|
+
*
|
|
175
|
+
* Each `section` entry can be:
|
|
176
|
+
* - a string → must appear as a substring (case-insensitive)
|
|
177
|
+
* - an array → any-of: at least one of the strings must appear
|
|
178
|
+
*
|
|
179
|
+
* The any-of form supports bilingual content (e.g. ['Scope', 'Escopo']).
|
|
180
|
+
*/
|
|
181
|
+
function validateMarkdownSections(content, sections, result) {
|
|
182
|
+
const contentLower = content.toLowerCase();
|
|
183
|
+
const matches = (entry) => {
|
|
184
|
+
const candidates = Array.isArray(entry) ? entry : [entry];
|
|
185
|
+
const hit = candidates.find(c => contentLower.includes(c.toLowerCase()));
|
|
186
|
+
return { hit, label: candidates[0] };
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
for (const entry of sections.required) {
|
|
190
|
+
const { hit, label } = matches(entry);
|
|
191
|
+
if (hit) {
|
|
192
|
+
result.sections.found.push(hit);
|
|
193
|
+
} else {
|
|
194
|
+
result.sections.missing.push(label);
|
|
195
|
+
const acceptable = Array.isArray(entry)
|
|
196
|
+
? entry.map(s => `"${s}"`).join(' or ')
|
|
197
|
+
: `"${entry}"`;
|
|
198
|
+
result.errors.push(`Missing required section: ${acceptable}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (const entry of (sections.optional || [])) {
|
|
203
|
+
const { hit } = matches(entry);
|
|
204
|
+
if (hit) result.sections.optional.push(hit);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Main validate command
|
|
210
|
+
*/
|
|
211
|
+
export async function validateFeatureCommand(featureName, options = {}) {
|
|
212
|
+
if (!featureName) {
|
|
213
|
+
console.error(chalk.red('Error: Feature name required'));
|
|
214
|
+
console.error(chalk.gray('Usage: morph-spec validate <feature-name> [--phase <phase>]'));
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const featurePath = path.join(process.cwd(), '.morph/features', featureName);
|
|
219
|
+
|
|
220
|
+
if (!fs.existsSync(featurePath)) {
|
|
221
|
+
console.error(chalk.red(`Error: Feature directory not found: ${featurePath}`));
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Determine which outputs to validate
|
|
226
|
+
const phase = options.phase || null;
|
|
227
|
+
let outputFiles;
|
|
228
|
+
|
|
229
|
+
if (phase) {
|
|
230
|
+
outputFiles = PHASE_OUTPUTS[phase];
|
|
231
|
+
if (!outputFiles) {
|
|
232
|
+
console.error(chalk.red(`Unknown phase: ${phase}`));
|
|
233
|
+
console.error(chalk.gray(`Valid phases: ${Object.keys(PHASE_OUTPUTS).join(', ')}`));
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
// Validate all existing outputs across all phase folders
|
|
238
|
+
outputFiles = [];
|
|
239
|
+
const entries = fs.readdirSync(featurePath, { withFileTypes: true });
|
|
240
|
+
const isOutput = name => name.endsWith('.md') || name.endsWith('.cs') || name === 'tasks.json';
|
|
241
|
+
for (const entry of entries) {
|
|
242
|
+
if (entry.isDirectory()) {
|
|
243
|
+
const subDir = path.join(featurePath, entry.name);
|
|
244
|
+
const files = fs.readdirSync(subDir).filter(isOutput);
|
|
245
|
+
outputFiles.push(...files.map(f => ({ file: f, dir: subDir })));
|
|
246
|
+
} else if (isOutput(entry.name)) {
|
|
247
|
+
outputFiles.push({ file: entry.name, dir: featurePath });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Resolve phase folder for path lookup
|
|
253
|
+
const phaseFolder = phase ? PHASE_FOLDERS[phase] : null;
|
|
254
|
+
const searchPath = phaseFolder ? path.join(featurePath, phaseFolder) : featurePath;
|
|
255
|
+
|
|
256
|
+
// Run validation
|
|
257
|
+
const results = phase
|
|
258
|
+
? outputFiles.map(file => validateOutput(searchPath, file))
|
|
259
|
+
: outputFiles.map(({ file, dir }) => validateOutput(dir, file));
|
|
260
|
+
const schemaWarnings = []; // v5: cross-validation removed (contracts in spec.md)
|
|
261
|
+
|
|
262
|
+
// Aggregate results
|
|
263
|
+
const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
|
|
264
|
+
const totalWarnings = results.reduce((sum, r) => sum + r.warnings.length, 0) + schemaWarnings.length;
|
|
265
|
+
const totalFound = results.reduce((sum, r) => sum + r.sections.found.length, 0);
|
|
266
|
+
|
|
267
|
+
// JSON output mode
|
|
268
|
+
if (options.json) {
|
|
269
|
+
const output = {
|
|
270
|
+
feature: featureName,
|
|
271
|
+
phase: phase || 'all',
|
|
272
|
+
valid: totalErrors === 0,
|
|
273
|
+
summary: {
|
|
274
|
+
files: results.length,
|
|
275
|
+
errors: totalErrors,
|
|
276
|
+
warnings: totalWarnings,
|
|
277
|
+
sectionsFound: totalFound
|
|
278
|
+
},
|
|
279
|
+
results: results.map(r => ({
|
|
280
|
+
file: r.file,
|
|
281
|
+
exists: r.exists,
|
|
282
|
+
lineCount: r.lineCount,
|
|
283
|
+
errors: r.errors,
|
|
284
|
+
warnings: r.warnings,
|
|
285
|
+
sectionsFound: r.sections.found,
|
|
286
|
+
sectionsMissing: r.sections.missing
|
|
287
|
+
})),
|
|
288
|
+
schemaWarnings
|
|
289
|
+
};
|
|
290
|
+
console.log(JSON.stringify(output, null, 2));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Visual output
|
|
295
|
+
console.log(chalk.cyan('\n┌────────────────────────────────────────────────────────────┐'));
|
|
296
|
+
console.log(chalk.cyan('│ MORPH-SPEC FEATURE VALIDATOR │'));
|
|
297
|
+
console.log(chalk.cyan('└────────────────────────────────────────────────────────────┘\n'));
|
|
298
|
+
|
|
299
|
+
console.log(chalk.white.bold(`Feature: ${featureName}`));
|
|
300
|
+
if (phase) console.log(chalk.gray(`Phase: ${phase}`));
|
|
301
|
+
console.log('');
|
|
302
|
+
|
|
303
|
+
// File-by-file results
|
|
304
|
+
for (const result of results) {
|
|
305
|
+
const symbol = result.exists
|
|
306
|
+
? (result.errors.length === 0 ? '✅' : '❌')
|
|
307
|
+
: '⬜';
|
|
308
|
+
const name = result.exists
|
|
309
|
+
? (result.errors.length === 0 ? chalk.green(result.file) : chalk.red(result.file))
|
|
310
|
+
: chalk.gray(result.file);
|
|
311
|
+
|
|
312
|
+
console.log(`${symbol} ${name}${result.exists ? chalk.gray(` (${result.lineCount} lines)`) : ''}`);
|
|
313
|
+
|
|
314
|
+
// Sections found
|
|
315
|
+
if (result.sections.found.length > 0 && (options.verbose || result.errors.length > 0)) {
|
|
316
|
+
result.sections.found.forEach(s => {
|
|
317
|
+
console.log(chalk.green(` ✓ ${s}`));
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Errors
|
|
322
|
+
result.errors.forEach(err => {
|
|
323
|
+
console.log(chalk.red(` ✗ ${err}`));
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Warnings
|
|
327
|
+
result.warnings.forEach(warn => {
|
|
328
|
+
console.log(chalk.yellow(` ⚠ ${warn}`));
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Schema cross-validation
|
|
333
|
+
if (schemaWarnings.length > 0) {
|
|
334
|
+
console.log(chalk.yellow('\nSchema Cross-Validation:'));
|
|
335
|
+
schemaWarnings.forEach(w => {
|
|
336
|
+
console.log(chalk.yellow(` ⚠ ${w}`));
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Summary
|
|
341
|
+
console.log('');
|
|
342
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
343
|
+
|
|
344
|
+
if (totalErrors === 0) {
|
|
345
|
+
console.log(chalk.green(`\n✓ VALIDATION PASSED — ${results.filter(r => r.exists).length} files validated`));
|
|
346
|
+
if (totalWarnings > 0) {
|
|
347
|
+
console.log(chalk.yellow(` ${totalWarnings} warning(s) — review recommended`));
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
console.log(chalk.red(`\n✗ VALIDATION FAILED — ${totalErrors} error(s), ${totalWarnings} warning(s)`));
|
|
351
|
+
console.log(chalk.yellow(' Fix errors before advancing to the next phase'));
|
|
352
|
+
}
|
|
353
|
+
console.log('');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export default validateFeatureCommand;
|