@polymorphism-tech/morph-spec 2.1.2 → 2.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/CLAUDE.md +389 -40
- package/bin/morph-spec.js +121 -0
- package/bin/task-manager.js +368 -0
- package/bin/validate-agents-skills.js +17 -5
- package/bin/validate.js +268 -0
- package/content/.claude/skills/specialists/ef-modeler.md +11 -0
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +10 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +40 -0
- package/content/.claude/skills/stacks/dotnet-blazor.md +18 -0
- package/content/.morph/examples/state-v3.json +188 -0
- package/detectors/structure-detector.js +32 -3
- package/package.json +1 -1
- package/src/commands/create-story.js +68 -0
- package/src/commands/init.js +59 -5
- package/src/commands/state.js +1 -1
- package/src/commands/task.js +75 -0
- package/src/lib/continuous-validator.js +440 -0
- package/src/lib/learning-system.js +520 -0
- package/src/lib/mockup-generator.js +366 -0
- package/src/lib/ui-detector.js +350 -0
- package/src/lib/validators/architecture-validator.js +387 -0
- package/src/lib/validators/package-validator.js +360 -0
- package/src/lib/validators/ui-contrast-validator.js +422 -0
- package/src/utils/file-copier.js +26 -0
package/bin/morph-spec.js
CHANGED
|
@@ -15,9 +15,12 @@ import { createStoryCommand } from '../src/commands/create-story.js';
|
|
|
15
15
|
import { shardSpecCommand } from '../src/commands/shard-spec.js';
|
|
16
16
|
import { sprintStatusCommand } from '../src/commands/sprint-status.js';
|
|
17
17
|
import { stateCommand } from '../src/commands/state.js';
|
|
18
|
+
import { taskDoneCommand, taskStartCommand, taskNextCommand } from '../src/commands/task.js';
|
|
18
19
|
import { costCommand } from '../src/commands/cost.js';
|
|
19
20
|
import { generateDesignSystemCommand } from '../src/commands/generate.js';
|
|
20
21
|
import { updateResourcePricing, showPricing, validatePricing } from '../src/commands/update-pricing.js';
|
|
22
|
+
import { validateCommand } from './validate.js';
|
|
23
|
+
import { LearningSystem } from '../src/lib/learning-system.js';
|
|
21
24
|
|
|
22
25
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
26
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -111,6 +114,26 @@ program
|
|
|
111
114
|
.option('--json', 'Output as JSON (get command)')
|
|
112
115
|
.action((action, args, options) => stateCommand(action, args, options));
|
|
113
116
|
|
|
117
|
+
// Task management commands (MORPH-SPEC 3.0)
|
|
118
|
+
const taskCommand = program
|
|
119
|
+
.command('task')
|
|
120
|
+
.description('Manage feature tasks (done | start | next)');
|
|
121
|
+
|
|
122
|
+
taskCommand
|
|
123
|
+
.command('done <feature> <task-ids...>')
|
|
124
|
+
.description('Mark tasks as completed')
|
|
125
|
+
.action((feature, taskIds, options) => taskDoneCommand(feature, taskIds, options));
|
|
126
|
+
|
|
127
|
+
taskCommand
|
|
128
|
+
.command('start <feature> <task-id>')
|
|
129
|
+
.description('Start a task (mark as in_progress)')
|
|
130
|
+
.action((feature, taskId, options) => taskStartCommand(feature, taskId, options));
|
|
131
|
+
|
|
132
|
+
taskCommand
|
|
133
|
+
.command('next <feature>')
|
|
134
|
+
.description('Show next suggested task')
|
|
135
|
+
.action((feature, options) => taskNextCommand(feature, options));
|
|
136
|
+
|
|
114
137
|
// Cost calculation command
|
|
115
138
|
program
|
|
116
139
|
.command('cost <bicep-files>')
|
|
@@ -170,4 +193,102 @@ generateCommand
|
|
|
170
193
|
.option('--dry-run', 'Preview without writing files')
|
|
171
194
|
.action(generateDesignSystemCommand);
|
|
172
195
|
|
|
196
|
+
// Validation commands (Sprint 4: Continuous Validation)
|
|
197
|
+
program
|
|
198
|
+
.command('validate [validator]')
|
|
199
|
+
.description('Run project validations (all | packages | architecture | contrast)')
|
|
200
|
+
.option('-v, --verbose', 'Show detailed output')
|
|
201
|
+
.option('--auto-fix, --fix', 'Auto-fix issues where possible')
|
|
202
|
+
.option('--no-fail', 'Don\'t exit with error code')
|
|
203
|
+
.option('-i, --insights', 'Show learning insights')
|
|
204
|
+
.option('--wcag-aaa', 'Use WCAG AAA standard (stricter)')
|
|
205
|
+
.action((validator, options) => {
|
|
206
|
+
const args = validator ? [validator] : ['all'];
|
|
207
|
+
if (options.verbose) args.push('--verbose');
|
|
208
|
+
if (options.autoFix) args.push('--auto-fix');
|
|
209
|
+
if (options.noFail) args.push('--no-fail');
|
|
210
|
+
if (options.insights) args.push('--insights');
|
|
211
|
+
if (options.wcagAaa) args.push('--wcag-aaa');
|
|
212
|
+
validateCommand(args);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Learning commands (Sprint 4: Learning System)
|
|
216
|
+
const learnCommand = program
|
|
217
|
+
.command('learn')
|
|
218
|
+
.description('AI learning system commands');
|
|
219
|
+
|
|
220
|
+
learnCommand
|
|
221
|
+
.command('analyze')
|
|
222
|
+
.description('Learn from project history (decisions.md files)')
|
|
223
|
+
.option('-v, --verbose', 'Show detailed learning progress')
|
|
224
|
+
.action(async (options) => {
|
|
225
|
+
const learner = new LearningSystem('.');
|
|
226
|
+
await learner.learnFromProject();
|
|
227
|
+
|
|
228
|
+
if (options.verbose) {
|
|
229
|
+
learner.formatInsights();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
learnCommand
|
|
234
|
+
.command('insights')
|
|
235
|
+
.description('Show AI insights and patterns')
|
|
236
|
+
.option('--json', 'Output as JSON')
|
|
237
|
+
.action((options) => {
|
|
238
|
+
const learner = new LearningSystem('.');
|
|
239
|
+
|
|
240
|
+
if (options.json) {
|
|
241
|
+
console.log(JSON.stringify(learner.getInsightsSummary(), null, 2));
|
|
242
|
+
} else {
|
|
243
|
+
learner.formatInsights();
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
learnCommand
|
|
248
|
+
.command('suggest [category]')
|
|
249
|
+
.description('Get AI suggestions for a category (uiLibrary | architecture | infrastructure | authentication | stateManagement | testing)')
|
|
250
|
+
.option('--json', 'Output as JSON')
|
|
251
|
+
.action((category, options) => {
|
|
252
|
+
const learner = new LearningSystem('.');
|
|
253
|
+
|
|
254
|
+
if (category) {
|
|
255
|
+
const suggestion = learner.getSuggestion(category);
|
|
256
|
+
if (options.json) {
|
|
257
|
+
console.log(JSON.stringify(suggestion, null, 2));
|
|
258
|
+
} else {
|
|
259
|
+
console.log(chalk.cyan(`\n💡 Suggestion for ${category}:\n`));
|
|
260
|
+
if (suggestion.confidence === 'none') {
|
|
261
|
+
console.log(chalk.yellow(` ${suggestion.reason}`));
|
|
262
|
+
} else {
|
|
263
|
+
console.log(chalk.white(` → ${suggestion.suggestion} (${suggestion.percentage}% confidence)`));
|
|
264
|
+
console.log(chalk.gray(` ${suggestion.reason}`));
|
|
265
|
+
}
|
|
266
|
+
console.log('');
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
const suggestions = learner.getAllSuggestions();
|
|
270
|
+
if (options.json) {
|
|
271
|
+
console.log(JSON.stringify(suggestions, null, 2));
|
|
272
|
+
} else {
|
|
273
|
+
learner.formatSuggestions(suggestions);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
learnCommand
|
|
279
|
+
.command('reset')
|
|
280
|
+
.description('Reset knowledge base (clear all learned patterns)')
|
|
281
|
+
.option('--force', 'Skip confirmation')
|
|
282
|
+
.action((options) => {
|
|
283
|
+
if (!options.force) {
|
|
284
|
+
console.log(chalk.yellow('\n⚠️ This will delete all learned patterns and preferences.'));
|
|
285
|
+
console.log(chalk.gray(' Run with --force to confirm.\n'));
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const learner = new LearningSystem('.');
|
|
290
|
+
learner.reset();
|
|
291
|
+
console.log(chalk.green('\n✅ Knowledge base reset\n'));
|
|
292
|
+
});
|
|
293
|
+
|
|
173
294
|
program.parse();
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MORPH-SPEC Task Manager
|
|
5
|
+
*
|
|
6
|
+
* Manages task completion, dependencies, checkpoints, and progress tracking.
|
|
7
|
+
* Part of MORPH-SPEC 3.0 - Event-driven state management.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs').promises;
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
|
|
14
|
+
class TaskManager {
|
|
15
|
+
constructor(statePath = '.morph/state.json') {
|
|
16
|
+
this.statePath = statePath;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Load state.json
|
|
21
|
+
*/
|
|
22
|
+
async loadState() {
|
|
23
|
+
try {
|
|
24
|
+
const content = await fs.readFile(this.statePath, 'utf-8');
|
|
25
|
+
return JSON.parse(content);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error.code === 'ENOENT') {
|
|
28
|
+
throw new Error(`State file not found: ${this.statePath}. Run 'npx morph-spec state init' first.`);
|
|
29
|
+
}
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Save state.json
|
|
36
|
+
*/
|
|
37
|
+
async saveState(state) {
|
|
38
|
+
state.project.updatedAt = new Date().toISOString();
|
|
39
|
+
await fs.writeFile(this.statePath, JSON.stringify(state, null, 2), 'utf-8');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Complete one or more tasks
|
|
44
|
+
*/
|
|
45
|
+
async completeTasks(featureName, taskIds) {
|
|
46
|
+
const state = await this.loadState();
|
|
47
|
+
const feature = state.features[featureName];
|
|
48
|
+
|
|
49
|
+
if (!feature) {
|
|
50
|
+
throw new Error(`Feature '${featureName}' not found in state.json`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const results = [];
|
|
54
|
+
|
|
55
|
+
for (const taskId of taskIds) {
|
|
56
|
+
const task = feature.tasks.find(t => t.id === taskId);
|
|
57
|
+
|
|
58
|
+
if (!task) {
|
|
59
|
+
console.error(chalk.red(`❌ Task ${taskId} not found`));
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (task.status === 'completed') {
|
|
64
|
+
console.log(chalk.yellow(`⚠️ Task ${taskId} already completed`));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Validate dependencies
|
|
69
|
+
const missingDeps = this.checkDependencies(task, feature.tasks);
|
|
70
|
+
if (missingDeps.length > 0) {
|
|
71
|
+
console.error(chalk.red(`❌ Cannot complete ${taskId}: missing dependencies: ${missingDeps.join(', ')}`));
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Mark as completed
|
|
76
|
+
task.status = 'completed';
|
|
77
|
+
task.completedAt = new Date().toISOString();
|
|
78
|
+
task.completedBy = 'claude';
|
|
79
|
+
|
|
80
|
+
results.push(task);
|
|
81
|
+
|
|
82
|
+
console.log(chalk.green(`✅ Task ${taskId} completed!`));
|
|
83
|
+
|
|
84
|
+
// Register checkpoint if task has one
|
|
85
|
+
if (task.checkpoint) {
|
|
86
|
+
this.registerCheckpoint(feature, task);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Update progress
|
|
91
|
+
feature.progress = this.calculateProgress(feature.tasks);
|
|
92
|
+
|
|
93
|
+
// Auto-checkpoint every 3 tasks
|
|
94
|
+
const recentCompleted = this.getRecentCompleted(feature.tasks, 3);
|
|
95
|
+
if (recentCompleted.length === 3) {
|
|
96
|
+
const lastCheckpoint = feature.checkpoints[feature.checkpoints.length - 1];
|
|
97
|
+
const shouldAutoCheckpoint = !lastCheckpoint ||
|
|
98
|
+
!recentCompleted.every(t =>
|
|
99
|
+
lastCheckpoint.tasksCompleted.includes(t.id)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (shouldAutoCheckpoint) {
|
|
103
|
+
this.autoCheckpoint(feature, recentCompleted);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Save state
|
|
108
|
+
await this.saveState(state);
|
|
109
|
+
|
|
110
|
+
// Display progress
|
|
111
|
+
this.displayProgress(feature);
|
|
112
|
+
|
|
113
|
+
// Suggest next task
|
|
114
|
+
const nextTask = this.getNextTask(feature.tasks);
|
|
115
|
+
if (nextTask) {
|
|
116
|
+
console.log(chalk.cyan(`\n⏭️ Next: ${nextTask.id} - ${nextTask.title}`));
|
|
117
|
+
if (nextTask.dependencies && nextTask.dependencies.length > 0) {
|
|
118
|
+
console.log(chalk.gray(` Dependencies: ${nextTask.dependencies.join(', ')}`));
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
console.log(chalk.green('\n🎉 All tasks completed!'));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if task dependencies are met
|
|
129
|
+
*/
|
|
130
|
+
checkDependencies(task, allTasks) {
|
|
131
|
+
if (!task.dependencies || task.dependencies.length === 0) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return task.dependencies.filter(depId => {
|
|
136
|
+
const dep = allTasks.find(t => t.id === depId);
|
|
137
|
+
return !dep || dep.status !== 'completed';
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Calculate progress
|
|
143
|
+
*/
|
|
144
|
+
calculateProgress(tasks) {
|
|
145
|
+
const total = tasks.length;
|
|
146
|
+
const completed = tasks.filter(t => t.status === 'completed').length;
|
|
147
|
+
const inProgress = tasks.filter(t => t.status === 'in_progress').length;
|
|
148
|
+
const pending = tasks.filter(t => t.status === 'pending').length;
|
|
149
|
+
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
total,
|
|
153
|
+
completed,
|
|
154
|
+
inProgress,
|
|
155
|
+
pending,
|
|
156
|
+
percentage
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Register checkpoint (from task flag)
|
|
162
|
+
*/
|
|
163
|
+
registerCheckpoint(feature, task) {
|
|
164
|
+
const checkpoint = {
|
|
165
|
+
id: task.checkpoint,
|
|
166
|
+
timestamp: new Date().toISOString(),
|
|
167
|
+
tasksCompleted: [task.id],
|
|
168
|
+
note: `Checkpoint: ${task.title}`
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
feature.checkpoints = feature.checkpoints || [];
|
|
172
|
+
feature.checkpoints.push(checkpoint);
|
|
173
|
+
|
|
174
|
+
console.log(chalk.magenta(`\n🎯 ${task.checkpoint} reached!`));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Auto-checkpoint every 3 tasks
|
|
179
|
+
*/
|
|
180
|
+
autoCheckpoint(feature, tasks) {
|
|
181
|
+
const checkpoint = {
|
|
182
|
+
id: `CHECKPOINT_AUTO_${Date.now()}`,
|
|
183
|
+
timestamp: new Date().toISOString(),
|
|
184
|
+
tasksCompleted: tasks.map(t => t.id),
|
|
185
|
+
note: `Auto-checkpoint: ${tasks.length} tasks completed`
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
feature.checkpoints = feature.checkpoints || [];
|
|
189
|
+
feature.checkpoints.push(checkpoint);
|
|
190
|
+
|
|
191
|
+
console.log(chalk.magenta(`\n🎉 Auto-checkpoint reached! ${tasks.length} tasks completed`));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get recently completed tasks (last N)
|
|
196
|
+
*/
|
|
197
|
+
getRecentCompleted(tasks, count) {
|
|
198
|
+
const completed = tasks
|
|
199
|
+
.filter(t => t.status === 'completed' && t.completedAt)
|
|
200
|
+
.sort((a, b) => new Date(b.completedAt) - new Date(a.completedAt));
|
|
201
|
+
|
|
202
|
+
return completed.slice(0, count);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get next pending task (based on dependencies)
|
|
207
|
+
*/
|
|
208
|
+
getNextTask(tasks) {
|
|
209
|
+
const pending = tasks.filter(t => t.status === 'pending');
|
|
210
|
+
|
|
211
|
+
// Find first task with all dependencies completed
|
|
212
|
+
for (const task of pending) {
|
|
213
|
+
const missingDeps = this.checkDependencies(task, tasks);
|
|
214
|
+
if (missingDeps.length === 0) {
|
|
215
|
+
return task;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Display progress
|
|
224
|
+
*/
|
|
225
|
+
displayProgress(feature) {
|
|
226
|
+
const { total, completed, inProgress, pending, percentage } = feature.progress;
|
|
227
|
+
|
|
228
|
+
console.log(chalk.bold(`\n📊 Progress: ${percentage}% (${completed}/${total})`));
|
|
229
|
+
console.log(chalk.gray(` Completed: ${completed} | In Progress: ${inProgress} | Pending: ${pending}`));
|
|
230
|
+
|
|
231
|
+
// Progress bar
|
|
232
|
+
const barLength = 30;
|
|
233
|
+
const filledLength = Math.round((percentage / 100) * barLength);
|
|
234
|
+
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
|
|
235
|
+
console.log(chalk.cyan(` [${bar}] ${percentage}%`));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Start a task (mark as in_progress)
|
|
240
|
+
*/
|
|
241
|
+
async startTask(featureName, taskId) {
|
|
242
|
+
const state = await this.loadState();
|
|
243
|
+
const feature = state.features[featureName];
|
|
244
|
+
|
|
245
|
+
if (!feature) {
|
|
246
|
+
throw new Error(`Feature '${featureName}' not found`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const task = feature.tasks.find(t => t.id === taskId);
|
|
250
|
+
|
|
251
|
+
if (!task) {
|
|
252
|
+
throw new Error(`Task ${taskId} not found`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (task.status === 'completed') {
|
|
256
|
+
console.log(chalk.yellow(`⚠️ Task ${taskId} already completed`));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Validate dependencies
|
|
261
|
+
const missingDeps = this.checkDependencies(task, feature.tasks);
|
|
262
|
+
if (missingDeps.length > 0) {
|
|
263
|
+
throw new Error(`Cannot start ${taskId}: missing dependencies: ${missingDeps.join(', ')}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
task.status = 'in_progress';
|
|
267
|
+
task.startedAt = new Date().toISOString();
|
|
268
|
+
|
|
269
|
+
await this.saveState(state);
|
|
270
|
+
|
|
271
|
+
console.log(chalk.blue(`▶️ Task ${taskId} started: ${task.title}`));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get next task suggestion
|
|
276
|
+
*/
|
|
277
|
+
async getNext(featureName) {
|
|
278
|
+
const state = await this.loadState();
|
|
279
|
+
const feature = state.features[featureName];
|
|
280
|
+
|
|
281
|
+
if (!feature) {
|
|
282
|
+
throw new Error(`Feature '${featureName}' not found`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const nextTask = this.getNextTask(feature.tasks);
|
|
286
|
+
|
|
287
|
+
if (nextTask) {
|
|
288
|
+
console.log(chalk.cyan(`\n⏭️ Next task: ${nextTask.id} - ${nextTask.title}`));
|
|
289
|
+
if (nextTask.dependencies && nextTask.dependencies.length > 0) {
|
|
290
|
+
console.log(chalk.gray(` Dependencies: ${nextTask.dependencies.join(', ')}`));
|
|
291
|
+
}
|
|
292
|
+
if (nextTask.files && nextTask.files.length > 0) {
|
|
293
|
+
console.log(chalk.gray(` Files:`));
|
|
294
|
+
nextTask.files.forEach(f => console.log(chalk.gray(` - ${f}`)));
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
console.log(chalk.green('🎉 All tasks completed!'));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// CLI
|
|
303
|
+
async function main() {
|
|
304
|
+
const args = process.argv.slice(2);
|
|
305
|
+
const command = args[0];
|
|
306
|
+
|
|
307
|
+
const manager = new TaskManager();
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
switch (command) {
|
|
311
|
+
case 'done':
|
|
312
|
+
case 'complete': {
|
|
313
|
+
const featureName = args[1];
|
|
314
|
+
const taskIds = args.slice(2);
|
|
315
|
+
|
|
316
|
+
if (!featureName || taskIds.length === 0) {
|
|
317
|
+
console.error(chalk.red('Usage: npx morph-spec task done <feature> <task-id> [task-id...]'));
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
await manager.completeTasks(featureName, taskIds);
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
case 'start': {
|
|
326
|
+
const featureName = args[1];
|
|
327
|
+
const taskId = args[2];
|
|
328
|
+
|
|
329
|
+
if (!featureName || !taskId) {
|
|
330
|
+
console.error(chalk.red('Usage: npx morph-spec task start <feature> <task-id>'));
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
await manager.startTask(featureName, taskId);
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
case 'next': {
|
|
339
|
+
const featureName = args[1];
|
|
340
|
+
|
|
341
|
+
if (!featureName) {
|
|
342
|
+
console.error(chalk.red('Usage: npx morph-spec task next <feature>'));
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
await manager.getNext(featureName);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
default:
|
|
351
|
+
console.error(chalk.red(`Unknown command: ${command}`));
|
|
352
|
+
console.log(chalk.gray('\nAvailable commands:'));
|
|
353
|
+
console.log(chalk.gray(' done <feature> <task-id...> - Mark tasks as completed'));
|
|
354
|
+
console.log(chalk.gray(' start <feature> <task-id> - Start a task (mark as in_progress)'));
|
|
355
|
+
console.log(chalk.gray(' next <feature> - Show next suggested task'));
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
} catch (error) {
|
|
359
|
+
console.error(chalk.red(`\n❌ Error: ${error.message}`));
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (require.main === module) {
|
|
365
|
+
main();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
module.exports = TaskManager;
|
|
@@ -118,11 +118,15 @@ function validateAgentsSkills(config, verbose = false) {
|
|
|
118
118
|
const skillPath = `.claude/skills/${skillFile.replace('.claude/skills/', '')}`;
|
|
119
119
|
|
|
120
120
|
if (!agentSkillPaths.has(skillPath)) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
// Orphan skills são OK - podem ser usados manualmente
|
|
122
|
+
// Apenas reportar em verbose mode
|
|
123
|
+
if (verbose) {
|
|
124
|
+
info.push({
|
|
125
|
+
type: 'manual-skill',
|
|
126
|
+
skillPath: skillPath,
|
|
127
|
+
message: `ℹ️ Manual skill: ${skillPath} (no auto-activation)`
|
|
128
|
+
});
|
|
129
|
+
}
|
|
126
130
|
}
|
|
127
131
|
}
|
|
128
132
|
|
|
@@ -194,6 +198,14 @@ function displayResults(results, verbose) {
|
|
|
194
198
|
log('║ ✅ All agents and skills are properly mapped! ║', 'green');
|
|
195
199
|
}
|
|
196
200
|
|
|
201
|
+
// Nota sobre manual skills
|
|
202
|
+
const manualSkills = info.filter(i => i.type === 'manual-skill').length;
|
|
203
|
+
if (manualSkills > 0 && verbose) {
|
|
204
|
+
log('║ ║', 'cyan');
|
|
205
|
+
log(`║ ℹ️ ${manualSkills} manual-only skills found (use --verbose to see) ║`, 'gray');
|
|
206
|
+
log('║ These are extra skills without auto-activation. ║', 'gray');
|
|
207
|
+
}
|
|
208
|
+
|
|
197
209
|
log('╚════════════════════════════════════════════════════════════════╝\n', 'cyan');
|
|
198
210
|
}
|
|
199
211
|
|