@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,376 +1,373 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MORPH-SPEC Feature Status Command
|
|
3
|
-
*
|
|
4
|
-
* Displays a comprehensive dashboard showing feature progress, phase status,
|
|
5
|
-
* outputs, tasks, approvals, and next steps.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* morph-spec status <feature-name>
|
|
9
|
-
* morph-spec status <feature-name> --json
|
|
10
|
-
*
|
|
11
|
-
* Example:
|
|
12
|
-
* morph-spec status user-portal
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import chalk from 'chalk';
|
|
16
|
-
import { loadState, getFeature, derivePhase, deriveOutputs } from '../../core/state/state-manager.js';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{ key: '
|
|
34
|
-
{ key: '
|
|
35
|
-
{ key: '
|
|
36
|
-
{ key: '
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (phase.order
|
|
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
|
-
const
|
|
82
|
-
const
|
|
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
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
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
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
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
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (hours
|
|
216
|
-
if (hours
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
console.error(chalk.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
console.error(chalk.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
console.error(chalk.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
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
|
-
console.log(chalk.cyan('
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
console.log(chalk.
|
|
292
|
-
console.log(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
console.log('');
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
console.log(`
|
|
328
|
-
console.log(
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
export default statusCommand;
|
|
1
|
+
/**
|
|
2
|
+
* MORPH-SPEC Feature Status Command
|
|
3
|
+
*
|
|
4
|
+
* Displays a comprehensive dashboard showing feature progress, phase status,
|
|
5
|
+
* outputs, tasks, approvals, and next steps.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* morph-spec status <feature-name>
|
|
9
|
+
* morph-spec status <feature-name> --json
|
|
10
|
+
*
|
|
11
|
+
* Example:
|
|
12
|
+
* morph-spec status user-portal
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
import { loadState, getFeature, derivePhase, deriveOutputs } from '../../core/state/state-manager.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Phase symbols for visual representation
|
|
20
|
+
*/
|
|
21
|
+
const PHASE_SYMBOLS = {
|
|
22
|
+
completed: '✅',
|
|
23
|
+
current: '⏸️',
|
|
24
|
+
pending: '⬜',
|
|
25
|
+
skipped: '⊘'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Phase definitions with order
|
|
30
|
+
*/
|
|
31
|
+
const PHASE_ORDER = [
|
|
32
|
+
{ key: 'proposal', name: 'FASE 0: PROPOSAL', order: 0 },
|
|
33
|
+
{ key: 'uiux', name: 'FASE 1: UI/UX', order: 1, optional: true },
|
|
34
|
+
{ key: 'plan', name: 'FASE 2: PLAN', order: 2 },
|
|
35
|
+
{ key: 'implement', name: 'FASE 3: IMPLEMENT', order: 3 },
|
|
36
|
+
{ key: 'review', name: 'FASE 4: REVIEW', order: 4 }
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Calculate phase status (completed, current, pending, skipped)
|
|
41
|
+
*/
|
|
42
|
+
function getPhaseStatus(phaseKey, currentPhase, feature) {
|
|
43
|
+
const phase = PHASE_ORDER.find(p => p.key === phaseKey);
|
|
44
|
+
const current = PHASE_ORDER.find(p => p.key === currentPhase);
|
|
45
|
+
|
|
46
|
+
if (!phase || !current) return 'pending';
|
|
47
|
+
|
|
48
|
+
// Check if phase was skipped (optional phases)
|
|
49
|
+
if (phase.optional && phase.order < current.order) {
|
|
50
|
+
// Check if any outputs were created for this phase
|
|
51
|
+
const hasOutputs = checkPhaseOutputs(phaseKey, feature);
|
|
52
|
+
if (!hasOutputs) return 'skipped';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (phase.order < current.order) return 'completed';
|
|
56
|
+
if (phase.order === current.order) return 'current';
|
|
57
|
+
return 'pending';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if phase has any outputs created
|
|
62
|
+
*/
|
|
63
|
+
function checkPhaseOutputs(phaseKey, feature) {
|
|
64
|
+
const phaseOutputMap = {
|
|
65
|
+
proposal: ['proposal', 'stackScan', 'migrationPlan'],
|
|
66
|
+
uiux: ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
|
|
67
|
+
plan: ['spec', 'mandate', 'tasks', 'decisions'],
|
|
68
|
+
implement: ['recap'],
|
|
69
|
+
review: ['reviewReport']
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const outputs = phaseOutputMap[phaseKey] || [];
|
|
73
|
+
return outputs.some(output => feature.outputs[output]?.created);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Format phase display with symbol and status
|
|
78
|
+
*/
|
|
79
|
+
function formatPhase(phaseKey, currentPhase, feature, options = {}) {
|
|
80
|
+
const phase = PHASE_ORDER.find(p => p.key === phaseKey);
|
|
81
|
+
const status = getPhaseStatus(phaseKey, currentPhase, feature);
|
|
82
|
+
const symbol = PHASE_SYMBOLS[status];
|
|
83
|
+
|
|
84
|
+
let display = `${symbol} ${phase.name}`;
|
|
85
|
+
|
|
86
|
+
if (phase.optional) {
|
|
87
|
+
display += ` ${chalk.gray('(optional)')}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (status === 'current') {
|
|
91
|
+
display = chalk.cyan(display) + chalk.cyan(' ← CURRENT');
|
|
92
|
+
} else if (status === 'completed') {
|
|
93
|
+
display = chalk.green(display);
|
|
94
|
+
} else if (status === 'skipped') {
|
|
95
|
+
display = chalk.gray(display + ' (skipped)');
|
|
96
|
+
} else {
|
|
97
|
+
display = chalk.gray(display);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Add output files if verbose
|
|
101
|
+
if (options.verbose && status !== 'pending' && status !== 'skipped') {
|
|
102
|
+
const phaseOutputMap = {
|
|
103
|
+
proposal: ['proposal', 'stackScan', 'migrationPlan'],
|
|
104
|
+
uiux: ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
|
|
105
|
+
plan: ['spec', 'mandate', 'tasks', 'decisions'],
|
|
106
|
+
implement: ['recap'],
|
|
107
|
+
review: ['reviewReport']
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const outputs = phaseOutputMap[phaseKey] || [];
|
|
111
|
+
const created = outputs.filter(o => feature.outputs[o]?.created);
|
|
112
|
+
if (created.length > 0) {
|
|
113
|
+
display += chalk.gray(`\n Files: ${created.length}/${outputs.length}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return display;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Calculate completion percentage
|
|
122
|
+
*/
|
|
123
|
+
function calculateCompletionPercentage(feature) {
|
|
124
|
+
const totalPhases = PHASE_ORDER.filter(p => !p.optional).length; // 6 required phases
|
|
125
|
+
const completedPhases = PHASE_ORDER.filter(p => {
|
|
126
|
+
if (p.optional) return false;
|
|
127
|
+
const status = getPhaseStatus(p.key, feature.phase, feature);
|
|
128
|
+
return status === 'completed';
|
|
129
|
+
}).length;
|
|
130
|
+
|
|
131
|
+
return Math.round((completedPhases / totalPhases) * 100);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get next steps based on current phase
|
|
136
|
+
*/
|
|
137
|
+
function getNextSteps(feature) {
|
|
138
|
+
const steps = [];
|
|
139
|
+
|
|
140
|
+
// Check for pending approvals relevant to current phase
|
|
141
|
+
const phaseApprovalMap = {
|
|
142
|
+
proposal: 'proposal',
|
|
143
|
+
uiux: 'uiux',
|
|
144
|
+
plan: 'plan',
|
|
145
|
+
implement: 'plan',
|
|
146
|
+
review: 'review'
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const relevantGate = phaseApprovalMap[feature.phase];
|
|
150
|
+
const approvalGates = feature.approvalGates || {};
|
|
151
|
+
|
|
152
|
+
if (relevantGate && approvalGates[relevantGate] && !approvalGates[relevantGate].approved) {
|
|
153
|
+
steps.push({
|
|
154
|
+
type: 'approval',
|
|
155
|
+
message: `Approve ${relevantGate} to advance`,
|
|
156
|
+
command: `morph-spec approve ${feature.name || 'feature'} ${relevantGate}`
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check for missing outputs in current phase
|
|
161
|
+
const phaseOutputMap = {
|
|
162
|
+
proposal: ['proposal'],
|
|
163
|
+
plan: ['spec', 'mandate', 'tasks'],
|
|
164
|
+
implement: ['recap'],
|
|
165
|
+
review: ['reviewReport']
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const requiredOutputs = phaseOutputMap[feature.phase] || [];
|
|
169
|
+
const missingOutputs = requiredOutputs.filter(o => !feature.outputs[o]?.created);
|
|
170
|
+
|
|
171
|
+
if (missingOutputs.length > 0) {
|
|
172
|
+
steps.push({
|
|
173
|
+
type: 'output',
|
|
174
|
+
message: `Create missing outputs: ${missingOutputs.join(', ')}`,
|
|
175
|
+
command: null
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Suggest next phase if current is complete
|
|
180
|
+
const hasApprovalPending = relevantGate && approvalGates[relevantGate] && !approvalGates[relevantGate].approved;
|
|
181
|
+
if (missingOutputs.length === 0 && !hasApprovalPending) {
|
|
182
|
+
const currentPhaseIndex = PHASE_ORDER.findIndex(p => p.key === feature.phase);
|
|
183
|
+
if (currentPhaseIndex >= 0 && currentPhaseIndex < PHASE_ORDER.length - 1) {
|
|
184
|
+
const nextPhase = PHASE_ORDER[currentPhaseIndex + 1];
|
|
185
|
+
steps.push({
|
|
186
|
+
type: 'advance',
|
|
187
|
+
message: `Advance to ${nextPhase.name}`,
|
|
188
|
+
command: `morph-spec phase advance ${feature.name || 'feature'}`
|
|
189
|
+
});
|
|
190
|
+
} else if (currentPhaseIndex === PHASE_ORDER.length - 1) {
|
|
191
|
+
steps.push({
|
|
192
|
+
type: 'complete',
|
|
193
|
+
message: 'Feature complete! Consider archiving.',
|
|
194
|
+
command: null
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return steps;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Estimate remaining time based on tasks
|
|
204
|
+
*/
|
|
205
|
+
function estimateRemainingTime(feature) {
|
|
206
|
+
if (!feature.tasks || feature.tasks.total === 0) {
|
|
207
|
+
return 'Not estimated';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const remaining = feature.tasks.total - feature.tasks.completed;
|
|
211
|
+
const avgHoursPerTask = 2; // Configurable estimate
|
|
212
|
+
const hours = remaining * avgHoursPerTask;
|
|
213
|
+
|
|
214
|
+
if (hours < 1) return '< 1 hour';
|
|
215
|
+
if (hours === 1) return '1 hour';
|
|
216
|
+
if (hours < 8) return `${hours} hours`;
|
|
217
|
+
const days = Math.ceil(hours / 8);
|
|
218
|
+
return `~${days} day${days > 1 ? 's' : ''} (${hours}h)`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Main status command
|
|
223
|
+
*/
|
|
224
|
+
export async function statusCommand(featureName, options = {}) {
|
|
225
|
+
if (!featureName) {
|
|
226
|
+
console.error(chalk.red('✗ Error: Feature name required'));
|
|
227
|
+
console.error(chalk.gray('Usage: morph-spec status <feature-name>'));
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let feature;
|
|
232
|
+
try {
|
|
233
|
+
feature = getFeature(featureName);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
console.error(chalk.red(`✗ Error: ${err.message}`));
|
|
236
|
+
console.error(chalk.gray('Run \'morph-spec doctor\' to diagnose or fix your state.json'));
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
if (!feature) {
|
|
240
|
+
console.error(chalk.red(`✗ Error: Feature '${featureName}' not found`));
|
|
241
|
+
console.error(chalk.gray(`Run 'morph-spec status' to list known features`));
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Derive phase and outputs from filesystem if not stored in state
|
|
246
|
+
const { join } = await import('path');
|
|
247
|
+
const featurePath = join(process.cwd(), '.morph', 'features', featureName);
|
|
248
|
+
if (!feature.phase) {
|
|
249
|
+
feature.phase = derivePhase(featurePath);
|
|
250
|
+
}
|
|
251
|
+
if (!feature.outputs) {
|
|
252
|
+
feature.outputs = deriveOutputs(featureName, process.cwd());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// JSON output mode
|
|
256
|
+
if (options.json) {
|
|
257
|
+
const status = {
|
|
258
|
+
feature: featureName,
|
|
259
|
+
phase: feature.phase,
|
|
260
|
+
status: feature.status,
|
|
261
|
+
completion: calculateCompletionPercentage(feature),
|
|
262
|
+
phases: PHASE_ORDER.map(p => ({
|
|
263
|
+
key: p.key,
|
|
264
|
+
name: p.name,
|
|
265
|
+
status: getPhaseStatus(p.key, feature.phase, feature),
|
|
266
|
+
optional: p.optional || false
|
|
267
|
+
})),
|
|
268
|
+
outputs: Object.entries(feature.outputs).map(([key, output]) => ({
|
|
269
|
+
type: key,
|
|
270
|
+
created: output.created,
|
|
271
|
+
path: output.path
|
|
272
|
+
})),
|
|
273
|
+
tasks: feature.tasks || { total: 0, completed: 0, inProgress: 0, pending: 0 },
|
|
274
|
+
approvals: feature.approvalGates || {},
|
|
275
|
+
nextSteps: getNextSteps(feature),
|
|
276
|
+
estimatedTimeRemaining: estimateRemainingTime(feature)
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
console.log(JSON.stringify(status, null, 2));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Visual dashboard output
|
|
284
|
+
console.log(chalk.cyan('\n┌────────────────────────────────────────────────────────────┐'));
|
|
285
|
+
console.log(chalk.cyan('│ MORPH-SPEC STATUS │'));
|
|
286
|
+
console.log(chalk.cyan('└────────────────────────────────────────────────────────────┘\n'));
|
|
287
|
+
|
|
288
|
+
// Feature info
|
|
289
|
+
console.log(chalk.white.bold(`Feature: ${featureName}`));
|
|
290
|
+
console.log(chalk.gray(`Status: ${feature.status || 'unknown'}`));
|
|
291
|
+
console.log(chalk.gray(`Completion: ${calculateCompletionPercentage(feature)}%`));
|
|
292
|
+
console.log('');
|
|
293
|
+
|
|
294
|
+
// Phase progression
|
|
295
|
+
console.log(chalk.white.bold('Phases:'));
|
|
296
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────────────┤'));
|
|
297
|
+
PHASE_ORDER.forEach(phase => {
|
|
298
|
+
console.log(`│ ${formatPhase(phase.key, feature.phase, feature, options)}`);
|
|
299
|
+
});
|
|
300
|
+
console.log(chalk.cyan('└─────────────────────────────────────────────────────────────┘'));
|
|
301
|
+
console.log('');
|
|
302
|
+
|
|
303
|
+
// Outputs summary
|
|
304
|
+
const totalOutputs = Object.keys(feature.outputs).length;
|
|
305
|
+
const createdOutputs = Object.values(feature.outputs).filter(o => o.created).length;
|
|
306
|
+
console.log(chalk.white.bold(`Outputs: ${createdOutputs}/${totalOutputs} complete`));
|
|
307
|
+
|
|
308
|
+
if (options.verbose) {
|
|
309
|
+
console.log(chalk.gray('Output files:'));
|
|
310
|
+
Object.entries(feature.outputs).forEach(([key, output]) => {
|
|
311
|
+
const symbol = output.created ? '✅' : '⬜';
|
|
312
|
+
const display = output.created ? chalk.green(key) : chalk.gray(key);
|
|
313
|
+
console.log(` ${symbol} ${display}`);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
console.log('');
|
|
317
|
+
|
|
318
|
+
// Tasks progress
|
|
319
|
+
if (feature.tasks && feature.tasks.total > 0) {
|
|
320
|
+
console.log(chalk.white.bold('Tasks Progress:'));
|
|
321
|
+
const { total, completed, inProgress, pending } = feature.tasks;
|
|
322
|
+
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
323
|
+
|
|
324
|
+
console.log(` Total: ${total}`);
|
|
325
|
+
console.log(chalk.green(` ✓ Completed: ${completed} (${percentage}%)`));
|
|
326
|
+
if (inProgress > 0) console.log(chalk.cyan(` ▶ In Progress: ${inProgress}`));
|
|
327
|
+
if (pending > 0) console.log(chalk.gray(` ⬜ Pending: ${pending}`));
|
|
328
|
+
console.log(` Estimated time remaining: ${estimateRemainingTime(feature)}`);
|
|
329
|
+
console.log('');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Approval gates
|
|
333
|
+
if (feature.approvalGates) {
|
|
334
|
+
const gates = Object.entries(feature.approvalGates);
|
|
335
|
+
const approved = gates.filter(([_, g]) => g.approved).length;
|
|
336
|
+
console.log(chalk.white.bold(`Approval Gates: ${approved}/${gates.length} approved`));
|
|
337
|
+
|
|
338
|
+
if (options.verbose) {
|
|
339
|
+
gates.forEach(([gate, info]) => {
|
|
340
|
+
const symbol = info.approved ? '✅' : '⬜';
|
|
341
|
+
const display = info.approved ? chalk.green(gate) : chalk.gray(gate);
|
|
342
|
+
let extra = '';
|
|
343
|
+
if (info.approved && info.approvedBy) {
|
|
344
|
+
extra = chalk.gray(` (by ${info.approvedBy})`);
|
|
345
|
+
}
|
|
346
|
+
console.log(` ${symbol} ${display}${extra}`);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
console.log('');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Next steps
|
|
353
|
+
const nextSteps = getNextSteps(feature);
|
|
354
|
+
if (nextSteps.length > 0) {
|
|
355
|
+
console.log(chalk.white.bold('Next Steps:'));
|
|
356
|
+
nextSteps.forEach((step, index) => {
|
|
357
|
+
console.log(chalk.yellow(` ${index + 1}. ${step.message}`));
|
|
358
|
+
if (step.command) {
|
|
359
|
+
console.log(chalk.gray(` → ${step.command}`));
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
console.log('');
|
|
363
|
+
} else {
|
|
364
|
+
console.log(chalk.green('✓ All tasks complete!\n'));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Footer
|
|
368
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
369
|
+
console.log(chalk.gray('Run with --verbose for detailed output'));
|
|
370
|
+
console.log(chalk.gray('Run with --json for JSON output\n'));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export default statusCommand;
|