@polymorphism-tech/morph-spec 2.2.0 → 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 CHANGED
@@ -570,17 +570,74 @@ Formato: Sempre as 3 ações acima (padrão para esta fase)
570
570
 
571
571
  ### FASE 5: IMPLEMENT
572
572
  ```
573
- Gatilho: Tasks aprovadas
573
+ Gatilho: Tasks aprovadas (FASE 4 concluída)
574
+
574
575
  Ações:
575
- 1. Implementar task por task
576
- 2. Executar testes a cada task
577
- 3. Checkpoint a cada 3 tasks
578
- 4. Gerar recap ao final
576
+ 1. Implementar task por task seguindo tasks em state.json
577
+ 2. **SEMPRE chamar CLI após completar cada task:**
578
+ ```bash
579
+ npx morph-spec task done {feature} {task-id}
580
+ ```
581
+ 3. Aguardar confirmação do CLI (validação de dependencies)
582
+ 4. Framework atualiza state.json automaticamente:
583
+ - Marca task como completed
584
+ - Atualiza progress (percentage, counters)
585
+ - Detecta checkpoints automaticamente (a cada 3 tasks ou flag checkpoint)
586
+ - Mostra próxima task sugerida
587
+ 5. Executar testes quando task requer
588
+ 6. Gerar recap.md ao final de todas as tasks
589
+
590
+ **Workflow de implementação:**
591
+
592
+ # Claude implementa T001
593
+ Claude: "Vou criar a entidade ScheduledReport..."
594
+ [Gera Domain/Entities/ScheduledReport.cs]
595
+ [Gera Infrastructure/Persistence/Config/ScheduledReportConfig.cs]
596
+
597
+ # Claude chama CLI explicitamente
598
+ Claude executa:
599
+ npx morph-spec task done scheduled-reports T001
600
+
601
+ # CLI valida e atualiza state.json
602
+ CLI output:
603
+ ✅ Task T001 completed!
604
+ 📊 Progress: 11% (1/9)
605
+ [█████░░░░░░░░░░░░░░░░░░░░░░░░] 11%
606
+ ⏭️ Next: T002 - Create CreateReportCommand + Handler
607
+
608
+ # Claude confirma ao usuário
609
+ Claude: "✅ T001 concluída!
610
+ Entidade ScheduledReport criada com EF Core mapping.
611
+ Progresso: 11% (1/9)
612
+ Próxima: T002 - Create CreateReportCommand + Handler"
613
+
614
+ # Após completar 3 tasks
615
+ npx morph-spec task done scheduled-reports T003
616
+
617
+ CLI output:
618
+ ✅ Task T003 completed!
619
+ 🎉 Auto-checkpoint reached! 3 tasks completed
620
+ 📊 Progress: 33% (3/9)
621
+ ⏭️ Next: T004 - Create ReportList component
622
+
623
+ **NUNCA:**
624
+ - ❌ Atualizar state.json manualmente
625
+ - ❌ Atualizar tasks.json (não existe mais em schema 3.0.0)
626
+ - ❌ Criar scripts PowerShell para atualizar tasks
627
+ - ❌ Pular tasks com dependencies não completadas
628
+ - ❌ Esquecer de chamar CLI após implementar task
629
+
630
+ **SEMPRE:**
631
+ - ✅ Chamar `npx morph-spec task done` após cada task
632
+ - ✅ Verificar output do CLI (validações, próxima task)
633
+ - ✅ Respeitar ordem de dependencies
634
+ - ✅ Deixar framework gerenciar checkpoints automaticamente
579
635
 
580
636
  Outputs:
581
- - Código implementado
582
- - Testes criados
583
- - .morph/project/outputs/{feature}/recap.md
637
+ - Código implementado (task por task)
638
+ - Testes criados (quando task requer)
639
+ - state.json atualizado automaticamente pelo CLI
640
+ - .morph/project/outputs/{feature}/recap.md (ao final)
584
641
  ```
585
642
 
586
643
  ### FASE 6: SYNC (condicional)
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;