@polymorphism-tech/morph-spec 3.1.0 → 3.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/CLAUDE.md +534 -0
- package/README.md +78 -4
- package/bin/morph-spec.js +50 -1
- package/bin/render-template.js +56 -10
- package/bin/task-manager.cjs +101 -7
- package/docs/cli-auto-detection.md +219 -0
- package/docs/llm-interaction-config.md +735 -0
- package/docs/troubleshooting.md +269 -0
- package/package.json +5 -1
- package/src/commands/advance-phase.js +93 -2
- package/src/commands/approve.js +221 -0
- package/src/commands/capture-pattern.js +121 -0
- package/src/commands/generate.js +128 -1
- package/src/commands/init.js +37 -0
- package/src/commands/migrate-state.js +158 -0
- package/src/commands/search-patterns.js +126 -0
- package/src/commands/spawn-team.js +172 -0
- package/src/commands/task.js +2 -2
- package/src/commands/update.js +36 -0
- package/src/commands/upgrade.js +346 -0
- package/src/generator/.gitkeep +0 -0
- package/src/generator/config-generator.js +206 -0
- package/src/generator/templates/config.json.template +40 -0
- package/src/generator/templates/project.md.template +67 -0
- package/src/lib/checkpoint-hooks.js +258 -0
- package/src/lib/metadata-extractor.js +380 -0
- package/src/lib/phase-state-machine.js +214 -0
- package/src/lib/state-manager.js +120 -0
- package/src/lib/template-data-sources.js +325 -0
- package/src/lib/validators/content-validator.js +351 -0
- package/src/llm/.gitkeep +0 -0
- package/src/llm/analyzer.js +215 -0
- package/src/llm/environment-detector.js +43 -0
- package/src/llm/few-shot-examples.js +216 -0
- package/src/llm/project-config-schema.json +188 -0
- package/src/llm/prompt-builder.js +96 -0
- package/src/llm/schema-validator.js +121 -0
- package/src/orchestrator.js +206 -0
- package/src/sanitizer/.gitkeep +0 -0
- package/src/sanitizer/context-sanitizer.js +221 -0
- package/src/sanitizer/patterns.js +163 -0
- package/src/scanner/.gitkeep +0 -0
- package/src/scanner/project-scanner.js +242 -0
- package/src/types/index.js +477 -0
- package/src/ui/.gitkeep +0 -0
- package/src/ui/diff-display.js +91 -0
- package/src/ui/interactive-wizard.js +96 -0
- package/src/ui/user-review.js +211 -0
- package/src/ui/wizard-questions.js +190 -0
- package/src/writer/.gitkeep +0 -0
- package/src/writer/file-writer.js +86 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Troubleshooting - CLI Auto Context Detection
|
|
2
|
+
|
|
3
|
+
Common issues and solutions for MORPH-SPEC auto-detection.
|
|
4
|
+
|
|
5
|
+
## Claude Code Issues
|
|
6
|
+
|
|
7
|
+
### Issue: "Claude Code environment not detected"
|
|
8
|
+
|
|
9
|
+
**Cause**: Auto-detection requires Claude Code CLI to be installed and active.
|
|
10
|
+
|
|
11
|
+
**Solutions**:
|
|
12
|
+
|
|
13
|
+
1. **Use wizard mode** (recommended for first-time setup):
|
|
14
|
+
```bash
|
|
15
|
+
morph-spec init --wizard
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
2. **Install Claude Code** and retry:
|
|
19
|
+
- Download from https://claude.ai/claude-code
|
|
20
|
+
- Ensure `claude` command is in PATH
|
|
21
|
+
- Retry `morph-spec init`
|
|
22
|
+
|
|
23
|
+
3. **Skip auto-detection** and configure manually later:
|
|
24
|
+
```bash
|
|
25
|
+
morph-spec init --skip-detection
|
|
26
|
+
# Later: morph-spec update --wizard
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Issue: "LLM analysis timeout"
|
|
30
|
+
|
|
31
|
+
**Cause**: Analysis taking longer than 60 seconds (default timeout for interactive mode).
|
|
32
|
+
|
|
33
|
+
**Solutions**:
|
|
34
|
+
|
|
35
|
+
1. **Clean project before analysis**:
|
|
36
|
+
```bash
|
|
37
|
+
rm -rf node_modules dist build .next
|
|
38
|
+
morph-spec init
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
2. **Use wizard mode** (instant):
|
|
42
|
+
```bash
|
|
43
|
+
morph-spec init --wizard
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
3. **Check Claude Code performance**:
|
|
47
|
+
- Restart Claude Code
|
|
48
|
+
- Check system resources (CPU, memory)
|
|
49
|
+
|
|
50
|
+
## LLM Analysis Errors
|
|
51
|
+
|
|
52
|
+
### Issue: "Schema validation failed"
|
|
53
|
+
|
|
54
|
+
**Cause**: LLM returned JSON that doesn't match the expected schema.
|
|
55
|
+
|
|
56
|
+
**Diagnosis**:
|
|
57
|
+
```bash
|
|
58
|
+
# Enable debug mode to see LLM response
|
|
59
|
+
DEBUG=1 morph-spec init
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Solutions**:
|
|
63
|
+
|
|
64
|
+
1. **Use wizard mode** to provide manual input:
|
|
65
|
+
```bash
|
|
66
|
+
morph-spec init --wizard
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
2. **Report issue** if recurring:
|
|
70
|
+
- Include project type in issue report
|
|
71
|
+
- Share anonymized `package.json` or `.csproj` structure
|
|
72
|
+
|
|
73
|
+
### Issue: "LLM response is not valid JSON"
|
|
74
|
+
|
|
75
|
+
**Cause**: LLM returned malformed JSON (rare).
|
|
76
|
+
|
|
77
|
+
**Solutions**:
|
|
78
|
+
|
|
79
|
+
1. **Retry** - may be a one-time error:
|
|
80
|
+
```bash
|
|
81
|
+
morph-spec init
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
2. **Use wizard mode**:
|
|
85
|
+
```bash
|
|
86
|
+
morph-spec init --wizard
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## File System Issues
|
|
90
|
+
|
|
91
|
+
### Issue: "Permission denied" when creating .morph/
|
|
92
|
+
|
|
93
|
+
**Cause**: Insufficient permissions in project directory.
|
|
94
|
+
|
|
95
|
+
**Solutions**:
|
|
96
|
+
|
|
97
|
+
1. **Run with correct permissions**:
|
|
98
|
+
```bash
|
|
99
|
+
sudo morph-spec init # Unix/Mac
|
|
100
|
+
# Or run as administrator on Windows
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
2. **Check directory ownership**:
|
|
104
|
+
```bash
|
|
105
|
+
ls -la . # Should show your user as owner
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Issue: Config files getting overwritten
|
|
109
|
+
|
|
110
|
+
**Cause**: Running `morph-spec update` without reviewing changes.
|
|
111
|
+
|
|
112
|
+
**Protection**: Auto-detection creates backups:
|
|
113
|
+
- `.morph/project.md.YYYY-MM-DDTHH-MM-SS.backup`
|
|
114
|
+
- `.morph/config/config.json.YYYY-MM-DDTHH-MM-SS.backup`
|
|
115
|
+
|
|
116
|
+
**Solutions**:
|
|
117
|
+
|
|
118
|
+
1. **Review diff before approving**:
|
|
119
|
+
- Auto-detection shows diff when updating
|
|
120
|
+
- Choose "Edit" to modify before saving
|
|
121
|
+
|
|
122
|
+
2. **Restore from backup** if needed:
|
|
123
|
+
```bash
|
|
124
|
+
cp .morph/project.md.2026-02-14T10-30-00.backup .morph/project.md
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
3. **Skip auto-detection** during update:
|
|
128
|
+
```bash
|
|
129
|
+
morph-spec update --skip-detection
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Detection Accuracy Issues
|
|
133
|
+
|
|
134
|
+
### Issue: Wrong project type detected
|
|
135
|
+
|
|
136
|
+
**Example**: Monorepo detected as `nextjs` instead of `monorepo`.
|
|
137
|
+
|
|
138
|
+
**Solutions**:
|
|
139
|
+
|
|
140
|
+
1. **Use wizard mode** for accurate manual input:
|
|
141
|
+
```bash
|
|
142
|
+
morph-spec init --wizard
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
2. **Edit config after generation**:
|
|
146
|
+
- Choose "Edit" during review
|
|
147
|
+
- Manually correct `type` field in config.json
|
|
148
|
+
|
|
149
|
+
3. **Report detection issue**:
|
|
150
|
+
- Helps improve LLM prompt and examples
|
|
151
|
+
|
|
152
|
+
### Issue: Missing dependencies detected
|
|
153
|
+
|
|
154
|
+
**Cause**: Auto-detection only reads whitelisted files (package.json, .csproj).
|
|
155
|
+
|
|
156
|
+
**Solution**:
|
|
157
|
+
|
|
158
|
+
- This is by design (privacy/security)
|
|
159
|
+
- If critical dependency is missed, manually edit `.morph/project.md`
|
|
160
|
+
|
|
161
|
+
### Issue: Low confidence score (<70%)
|
|
162
|
+
|
|
163
|
+
**Cause**: Project structure is unclear or non-standard.
|
|
164
|
+
|
|
165
|
+
**Solutions**:
|
|
166
|
+
|
|
167
|
+
1. **Add README.md or CLAUDE.md** with project description
|
|
168
|
+
2. **Use wizard mode** for manual configuration
|
|
169
|
+
3. **Accept low confidence** - configs can be edited later
|
|
170
|
+
|
|
171
|
+
## Edge Case Handling
|
|
172
|
+
|
|
173
|
+
### Empty Project (no package.json or .csproj)
|
|
174
|
+
|
|
175
|
+
**Behavior**: Auto-fallback to wizard mode.
|
|
176
|
+
|
|
177
|
+
**Solution**: Answer 7 wizard questions to configure manually.
|
|
178
|
+
|
|
179
|
+
### Monorepo with Multiple Stacks
|
|
180
|
+
|
|
181
|
+
**Behavior**: Detects all stacks, sets `type: "monorepo"`.
|
|
182
|
+
|
|
183
|
+
**Example Output**:
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"type": "monorepo",
|
|
187
|
+
"stack": {
|
|
188
|
+
"frontend": { "tech": "Next.js", "version": "15" },
|
|
189
|
+
"backend": { "tech": ".NET", "version": "10" }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Project with No Database
|
|
195
|
+
|
|
196
|
+
**Behavior**: `stack.database` set to `null`.
|
|
197
|
+
|
|
198
|
+
**Warning**: May show "Database technology not detected" in warnings.
|
|
199
|
+
|
|
200
|
+
### Git Repo Without Remote
|
|
201
|
+
|
|
202
|
+
**Behavior**: `gitRemote` set to `null`.
|
|
203
|
+
|
|
204
|
+
**Not an error** - local repos are valid.
|
|
205
|
+
|
|
206
|
+
## Privacy & Security Concerns
|
|
207
|
+
|
|
208
|
+
### Question: What data is sent to LLM?
|
|
209
|
+
|
|
210
|
+
**Answer**: Only sanitized, whitelisted data:
|
|
211
|
+
- package.json (dependencies only, secrets removed)
|
|
212
|
+
- .csproj filenames (not contents)
|
|
213
|
+
- First 500 chars of README.md and CLAUDE.md
|
|
214
|
+
- Directory structure (folder names only)
|
|
215
|
+
- Infrastructure file counts (not contents)
|
|
216
|
+
|
|
217
|
+
**Never sent**:
|
|
218
|
+
- `.env` files
|
|
219
|
+
- Secrets/passwords/API keys
|
|
220
|
+
- File contents (except whitelisted files)
|
|
221
|
+
- node_modules/
|
|
222
|
+
- Source code
|
|
223
|
+
|
|
224
|
+
### Question: Is data sent to Anthropic servers?
|
|
225
|
+
|
|
226
|
+
**Answer**: No. LLM analysis runs locally in Claude Code. No external API calls.
|
|
227
|
+
|
|
228
|
+
### Question: What if sensitive data is detected?
|
|
229
|
+
|
|
230
|
+
**Behavior**: Secret redaction removes:
|
|
231
|
+
- API keys (10+ patterns detected)
|
|
232
|
+
- Passwords in connection strings
|
|
233
|
+
- Bearer tokens
|
|
234
|
+
- Private keys (PEM format)
|
|
235
|
+
- GitHub/NPM tokens
|
|
236
|
+
|
|
237
|
+
**Verification**: Review sanitized context in debug mode:
|
|
238
|
+
```bash
|
|
239
|
+
DEBUG=1 morph-spec init
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Getting Help
|
|
243
|
+
|
|
244
|
+
### Enable Debug Mode
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
DEBUG=1 morph-spec init
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Shows:
|
|
251
|
+
- Full LLM prompts
|
|
252
|
+
- Raw LLM responses
|
|
253
|
+
- Schema validation errors
|
|
254
|
+
- File read operations
|
|
255
|
+
|
|
256
|
+
### Report Issues
|
|
257
|
+
|
|
258
|
+
GitHub: https://github.com/polymorphism-tech/morph-spec-framework/issues
|
|
259
|
+
|
|
260
|
+
Include:
|
|
261
|
+
- MORPH-SPEC version (`morph-spec --version`)
|
|
262
|
+
- Claude Code version
|
|
263
|
+
- Project type (Next.js, Blazor, etc.)
|
|
264
|
+
- Error message (with DEBUG=1 output if possible)
|
|
265
|
+
- Anonymized `package.json` or `.csproj` structure
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
**Need more help?** Check the [CLI Auto Detection Guide](./cli-auto-detection.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polymorphism-tech/morph-spec",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "MORPH-SPEC v3: AI-First development framework with validation pipeline, .NET 10, Microsoft Agent Framework, and Fluent UI Blazor",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -52,8 +52,12 @@
|
|
|
52
52
|
"ajv-formats": "^3.0.1",
|
|
53
53
|
"chalk": "^5.3.0",
|
|
54
54
|
"commander": "^12.0.0",
|
|
55
|
+
"diff": "^5.2.0",
|
|
55
56
|
"fs-extra": "^11.2.0",
|
|
56
57
|
"glob": "^10.3.0",
|
|
58
|
+
"handlebars": "^4.7.8",
|
|
59
|
+
"inquirer": "^9.2.0",
|
|
60
|
+
"minimatch": "^9.0.5",
|
|
57
61
|
"ora": "^8.0.0",
|
|
58
62
|
"yaml": "^2.3.4"
|
|
59
63
|
},
|
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import chalk from 'chalk';
|
|
13
|
-
import { loadState, saveState, getFeature } from '../lib/state-manager.js';
|
|
13
|
+
import { loadState, saveState, getFeature, getApprovalGate } from '../lib/state-manager.js';
|
|
14
14
|
import { PHASES, validatePhase } from './validate-phase.js';
|
|
15
15
|
import { detectDesignSystem, hasUIAgentsActive } from '../lib/design-system-detector.js';
|
|
16
16
|
import { validateSpec } from '../lib/spec-validator.js';
|
|
17
|
+
import { validateTransition, getPhaseDisplayName } from '../lib/phase-state-machine.js';
|
|
18
|
+
import { validateSpecContent, validateTasksContent, validateFeatureOutputs } from '../lib/validators/content-validator.js';
|
|
17
19
|
|
|
18
20
|
// Phase order for advancing (skips optional phases unless active)
|
|
19
21
|
const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks', 'implement', 'sync'];
|
|
@@ -96,6 +98,42 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
96
98
|
const nextPhaseDef = PHASES[nextPhase];
|
|
97
99
|
console.log(chalk.gray('Next Phase:'), nextPhaseDef.name);
|
|
98
100
|
|
|
101
|
+
// === GATE 1: State Machine Validation ===
|
|
102
|
+
// Ensure phase transition is valid (no skipping required phases)
|
|
103
|
+
if (!options.force) {
|
|
104
|
+
try {
|
|
105
|
+
validateTransition(currentPhase, nextPhase);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.log(chalk.red('\n✗ Invalid phase transition'));
|
|
108
|
+
console.log(chalk.yellow(error.message));
|
|
109
|
+
console.log(chalk.gray('\nUse --force to override (not recommended)\n'));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// === GATE 2: Approval Gate Check ===
|
|
115
|
+
// Check if current phase requires approval before advancing
|
|
116
|
+
const approvalGateMap = {
|
|
117
|
+
'design': 'design',
|
|
118
|
+
'tasks': 'tasks',
|
|
119
|
+
'uiux': 'uiux'
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const requiredGate = approvalGateMap[currentPhase];
|
|
123
|
+
if (requiredGate && !options.skipApproval) {
|
|
124
|
+
const gateStatus = getApprovalGate(feature, requiredGate);
|
|
125
|
+
|
|
126
|
+
if (!gateStatus || !gateStatus.approved) {
|
|
127
|
+
console.log(chalk.red(`\n✗ Phase "${currentPhase}" requires approval before advancing`));
|
|
128
|
+
console.log(chalk.yellow(`\nRun: morph-spec approve ${feature} ${requiredGate}`));
|
|
129
|
+
console.log(chalk.gray('Or use --skip-approval to bypass (not recommended)\n'));
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log(chalk.green(`✓ Approval gate "${requiredGate}" passed`));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// === GATE 3: Output Requirements ===
|
|
99
137
|
// Validate that current phase requirements are met before advancing
|
|
100
138
|
const validation = validatePhase(feature, nextPhase);
|
|
101
139
|
|
|
@@ -112,8 +150,34 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
112
150
|
console.log(chalk.yellow(`\n⚠️ ${validation.stateWarning}`));
|
|
113
151
|
}
|
|
114
152
|
|
|
115
|
-
//
|
|
153
|
+
// === GATE 4: Content Validation ===
|
|
154
|
+
// Validate spec.md and contracts.cs when advancing from design phase
|
|
116
155
|
if (currentPhase === 'design' && (nextPhase === 'clarify' || nextPhase === 'tasks')) {
|
|
156
|
+
// Check spec.md structure and content
|
|
157
|
+
if (featureData.outputs?.spec?.created) {
|
|
158
|
+
const specContentValidation = validateSpecContent(featureData.outputs.spec.path);
|
|
159
|
+
|
|
160
|
+
if (!specContentValidation.valid) {
|
|
161
|
+
console.log(chalk.red('\n✗ Spec content validation failed:'));
|
|
162
|
+
specContentValidation.missing.forEach(section => {
|
|
163
|
+
console.log(chalk.red(` - Missing section: ${section}`));
|
|
164
|
+
});
|
|
165
|
+
specContentValidation.errors.forEach(error => {
|
|
166
|
+
console.log(chalk.red(` - ${error}`));
|
|
167
|
+
});
|
|
168
|
+
console.log('');
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (specContentValidation.warnings?.length > 0 && !options.skipWarnings) {
|
|
173
|
+
console.log(chalk.yellow('\n⚠️ Spec content warnings:'));
|
|
174
|
+
specContentValidation.warnings.forEach(warning => {
|
|
175
|
+
console.log(chalk.yellow(` - ${warning}`));
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Run existing spec validator (anti-patterns, IaC checks)
|
|
117
181
|
const specValidation = await validateSpec('.', feature);
|
|
118
182
|
|
|
119
183
|
if (specValidation.errors > 0) {
|
|
@@ -135,6 +199,33 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
135
199
|
}
|
|
136
200
|
}
|
|
137
201
|
|
|
202
|
+
// === GATE 5: Tasks Content Validation ===
|
|
203
|
+
// Validate tasks.json structure when advancing to implement
|
|
204
|
+
if (currentPhase === 'tasks' && nextPhase === 'implement') {
|
|
205
|
+
if (featureData.outputs?.tasks?.created) {
|
|
206
|
+
const tasksContentValidation = validateTasksContent(featureData.outputs.tasks.path);
|
|
207
|
+
|
|
208
|
+
if (!tasksContentValidation.valid) {
|
|
209
|
+
console.log(chalk.red('\n✗ Tasks content validation failed:'));
|
|
210
|
+
tasksContentValidation.errors.forEach(error => {
|
|
211
|
+
console.log(chalk.red(` - ${error}`));
|
|
212
|
+
});
|
|
213
|
+
console.log('');
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (tasksContentValidation.warnings?.length > 0 && !options.skipWarnings) {
|
|
218
|
+
console.log(chalk.yellow('\n⚠️ Tasks content warnings:'));
|
|
219
|
+
tasksContentValidation.warnings.forEach(warning => {
|
|
220
|
+
console.log(chalk.yellow(` - ${warning}`));
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Show tasks stats
|
|
225
|
+
console.log(chalk.green(`\n✓ Tasks validated: ${tasksContentValidation.stats.totalTasks} total (${tasksContentValidation.stats.regularTasks} tasks + ${tasksContentValidation.stats.checkpoints} checkpoints)`));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
138
229
|
// Gate: Check design system when advancing to implement with UI agents
|
|
139
230
|
if (nextPhase === 'implement') {
|
|
140
231
|
const gateResult = designSystemGate(feature);
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { getFeature, setApprovalGate, getApprovalGate, getApprovalHistory } from '../lib/state-manager.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Approval Commands - Manage approval gates for MORPH workflow
|
|
7
|
+
*
|
|
8
|
+
* Commands:
|
|
9
|
+
* - approve: Mark a gate as approved
|
|
10
|
+
* - reject: Reject a gate with reason
|
|
11
|
+
* - approval-status: Show approval status for all gates
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const program = new Command();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Valid approval gate names
|
|
18
|
+
*/
|
|
19
|
+
const VALID_GATES = ['proposal', 'uiux', 'design', 'tasks'];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Approve action function - Mark a gate as approved
|
|
23
|
+
*/
|
|
24
|
+
export async function approveCommand(featureName, gateName, options = {}) {
|
|
25
|
+
try {
|
|
26
|
+
// Validate gate name
|
|
27
|
+
if (!VALID_GATES.includes(gateName)) {
|
|
28
|
+
console.error(chalk.red(`\n❌ Invalid gate: ${gateName}`));
|
|
29
|
+
console.log(chalk.gray(`Valid gates: ${VALID_GATES.join(', ')}\n`));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get feature state
|
|
34
|
+
const feature = getFeature(featureName);
|
|
35
|
+
if (!feature) {
|
|
36
|
+
console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if already approved
|
|
41
|
+
const currentStatus = getApprovalGate(featureName, gateName);
|
|
42
|
+
if (currentStatus?.approved) {
|
|
43
|
+
console.log(chalk.yellow(`\n⚠️ Gate "${gateName}" is already approved`));
|
|
44
|
+
console.log(chalk.gray(`Approved at: ${currentStatus.timestamp}`));
|
|
45
|
+
console.log(chalk.gray(`Approved by: ${currentStatus.approvedBy || 'Unknown'}\n`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Set approval
|
|
50
|
+
setApprovalGate(featureName, gateName, true, {
|
|
51
|
+
approvedBy: options.approver || process.env.USER || process.env.USERNAME || 'user',
|
|
52
|
+
approvedAt: new Date().toISOString(),
|
|
53
|
+
notes: options.notes
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(chalk.green(`\n✅ Gate "${gateName}" approved for feature: ${featureName}`));
|
|
57
|
+
console.log(chalk.gray(`Workflow can now proceed past ${gateName} phase\n`));
|
|
58
|
+
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(chalk.red(`\n❌ Error approving gate: ${error.message}\n`));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Approve command - Mark a gate as approved
|
|
67
|
+
*/
|
|
68
|
+
program
|
|
69
|
+
.command('approve <feature> <gate>')
|
|
70
|
+
.description('Approve a phase gate to allow workflow progression')
|
|
71
|
+
.action(approveCommand);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Reject action function - Reject a gate with optional reason
|
|
75
|
+
*/
|
|
76
|
+
export async function rejectCommand(featureName, gateName, reason, options = {}) {
|
|
77
|
+
try {
|
|
78
|
+
// Validate gate name
|
|
79
|
+
if (!VALID_GATES.includes(gateName)) {
|
|
80
|
+
console.error(chalk.red(`\n❌ Invalid gate: ${gateName}`));
|
|
81
|
+
console.log(chalk.gray(`Valid gates: ${VALID_GATES.join(', ')}\n`));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Get feature state
|
|
86
|
+
const feature = getFeature(featureName);
|
|
87
|
+
if (!feature) {
|
|
88
|
+
console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Set rejection
|
|
93
|
+
setApprovalGate(featureName, gateName, false, {
|
|
94
|
+
rejectedBy: process.env.USER || process.env.USERNAME || 'user',
|
|
95
|
+
rejectedAt: new Date().toISOString(),
|
|
96
|
+
reason: reason || 'No reason provided'
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
console.log(chalk.red(`\n❌ Gate "${gateName}" rejected for feature: ${featureName}`));
|
|
100
|
+
if (reason) {
|
|
101
|
+
console.log(chalk.gray(`Reason: ${reason}`));
|
|
102
|
+
}
|
|
103
|
+
console.log(chalk.yellow(`\n⚠️ Address the concerns and request approval again\n`));
|
|
104
|
+
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(chalk.red(`\n❌ Error rejecting gate: ${error.message}\n`));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Reject command - Reject a gate with optional reason
|
|
113
|
+
*/
|
|
114
|
+
program
|
|
115
|
+
.command('reject <feature> <gate> [reason]')
|
|
116
|
+
.description('Reject a phase gate with optional reason')
|
|
117
|
+
.action(rejectCommand);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Approval status action function - Show all gates and their status
|
|
121
|
+
*/
|
|
122
|
+
export async function approvalStatusCommand(featureName, options = {}) {
|
|
123
|
+
try {
|
|
124
|
+
// Get feature state
|
|
125
|
+
const feature = getFeature(featureName);
|
|
126
|
+
if (!feature) {
|
|
127
|
+
console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// JSON output
|
|
132
|
+
if (options.json) {
|
|
133
|
+
const status = {
|
|
134
|
+
feature: featureName,
|
|
135
|
+
phase: feature.phase,
|
|
136
|
+
status: feature.status,
|
|
137
|
+
gates: {}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
for (const gateName of VALID_GATES) {
|
|
141
|
+
status.gates[gateName] = getApprovalGate(featureName, gateName) || { approved: false };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(JSON.stringify(status, null, 2));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log(chalk.bold(`\n📋 Approval Status for: ${featureName}`));
|
|
149
|
+
console.log('━'.repeat(60));
|
|
150
|
+
|
|
151
|
+
// Show current phase
|
|
152
|
+
console.log(chalk.cyan(`\nCurrent Phase: ${feature.phase || 'unknown'}`));
|
|
153
|
+
console.log(chalk.gray(`Feature Status: ${feature.status || 'unknown'}\n`));
|
|
154
|
+
|
|
155
|
+
// Show each gate
|
|
156
|
+
console.log(chalk.bold('Approval Gates:'));
|
|
157
|
+
|
|
158
|
+
for (const gateName of VALID_GATES) {
|
|
159
|
+
const gateStatus = getApprovalGate(featureName, gateName);
|
|
160
|
+
|
|
161
|
+
if (!gateStatus) {
|
|
162
|
+
console.log(` ${chalk.gray('○')} ${gateName}: ${chalk.gray('Not yet evaluated')}`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (gateStatus.approved) {
|
|
167
|
+
console.log(` ${chalk.green('✓')} ${gateName}: ${chalk.green('Approved')}`);
|
|
168
|
+
if (gateStatus.timestamp) {
|
|
169
|
+
console.log(chalk.gray(` Approved at: ${gateStatus.timestamp}`));
|
|
170
|
+
}
|
|
171
|
+
if (gateStatus.approvedBy) {
|
|
172
|
+
console.log(chalk.gray(` Approved by: ${gateStatus.approvedBy}`));
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
console.log(` ${chalk.red('✗')} ${gateName}: ${chalk.red('Rejected / Not Approved')}`);
|
|
176
|
+
if (gateStatus.reason) {
|
|
177
|
+
console.log(chalk.gray(` Reason: ${gateStatus.reason}`));
|
|
178
|
+
}
|
|
179
|
+
if (gateStatus.timestamp) {
|
|
180
|
+
console.log(chalk.gray(` Rejected at: ${gateStatus.timestamp}`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Show approval history if available
|
|
186
|
+
const history = getApprovalHistory(featureName);
|
|
187
|
+
if (history && history.length > 0) {
|
|
188
|
+
console.log(chalk.bold('\nApproval History:'));
|
|
189
|
+
history.slice(-5).forEach(entry => {
|
|
190
|
+
const status = entry.approved ? chalk.green('✓ Approved') : chalk.red('✗ Rejected');
|
|
191
|
+
console.log(` ${status} ${entry.gate} - ${entry.timestamp}`);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log('\n' + '━'.repeat(60) + '\n');
|
|
196
|
+
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error(chalk.red(`\n❌ Error showing approval status: ${error.message}\n`));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Approval status command - Show all gates and their status
|
|
205
|
+
*/
|
|
206
|
+
program
|
|
207
|
+
.command('approval-status <feature>')
|
|
208
|
+
.alias('status')
|
|
209
|
+
.description('Show approval status for all gates')
|
|
210
|
+
.action(approvalStatusCommand);
|
|
211
|
+
|
|
212
|
+
// Only parse if run directly (not imported as module)
|
|
213
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
214
|
+
if (process.argv.length > 2) {
|
|
215
|
+
program.parse(process.argv);
|
|
216
|
+
} else {
|
|
217
|
+
program.help();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export default program;
|