@polymorphism-tech/morph-spec 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +314 -1673
- package/LICENSE +72 -72
- package/README.md +515 -516
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +358 -173
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/task-manager.js +429 -0
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/bin/validate.js +369 -0
- package/content/.azure/README.md +293 -293
- package/content/.azure/docs/azure-devops-setup.md +454 -454
- package/content/.azure/docs/branch-strategy.md +398 -398
- package/content/.azure/docs/local-development.md +515 -515
- package/content/.azure/pipelines/pipeline-variables.yml +34 -34
- package/content/.azure/pipelines/prod-pipeline.yml +319 -319
- package/content/.azure/pipelines/staging-pipeline.yml +234 -234
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
- package/content/.claude/commands/morph-apply.md +221 -158
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -0
- package/content/.claude/commands/morph-proposal.md +122 -101
- package/content/.claude/commands/morph-status.md +86 -86
- package/content/.claude/commands/morph-troubleshoot.md +122 -0
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/checklists/code-review.md +226 -0
- package/content/.claude/skills/checklists/morph-checklist.md +117 -0
- package/content/.claude/skills/checklists/simulation-checklist.md +77 -0
- package/content/.claude/skills/infra/bicep-architect.md +126 -419
- package/content/.claude/skills/infra/container-specialist.md +131 -437
- package/content/.claude/skills/infra/devops-engineer.md +119 -405
- package/content/.claude/skills/integrations/asaas-financial.md +130 -333
- package/content/.claude/skills/integrations/azure-identity.md +142 -309
- package/content/.claude/skills/integrations/clerk-auth.md +108 -290
- package/content/.claude/skills/integrations/resend-email.md +119 -0
- package/content/.claude/skills/specialists/ai-system-architect.md +192 -604
- package/content/.claude/skills/specialists/azure-architect.md +142 -142
- package/content/.claude/skills/specialists/code-analyzer.md +235 -0
- package/content/.claude/skills/specialists/dotnet-senior.md +287 -0
- package/content/.claude/skills/specialists/ef-modeler.md +113 -200
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +126 -245
- package/content/.claude/skills/specialists/ms-agent-expert.md +109 -263
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -197
- package/content/.claude/skills/specialists/standards-architect.md +156 -78
- package/content/.claude/skills/specialists/testing-specialist.md +126 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +191 -1060
- package/content/.claude/skills/stacks/dotnet-blazor.md +210 -588
- package/content/.claude/skills/stacks/dotnet-nextjs.md +154 -402
- package/content/.claude/skills/workflows/morph-replicate.md +213 -0
- package/content/.claude/{commands/morph-clarify.md → skills/workflows/phase-clarify.md} +5 -58
- package/content/.claude/{commands/morph-design.md → skills/workflows/phase-design.md} +16 -86
- package/content/.claude/{commands/morph-setup.md → skills/workflows/phase-setup.md} +9 -17
- package/content/.claude/skills/workflows/phase-tasks.md +164 -0
- package/content/.claude/{commands/morph-uiux.md → skills/workflows/phase-uiux.md} +15 -88
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +378 -242
- package/content/.morph/config/config.template.json +89 -108
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/design-impl.md +37 -0
- package/content/.morph/docs/workflows/fast-track.md +29 -0
- package/content/.morph/docs/workflows/full-morph.md +76 -0
- package/content/.morph/docs/workflows/standard.md +44 -0
- package/content/.morph/docs/workflows/ui-refresh.md +39 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -241
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
- package/content/.morph/examples/api-nextjs/spec.md +399 -399
- package/content/.morph/examples/api-nextjs/tasks.md +168 -168
- package/content/.morph/examples/micro-saas/README.md +125 -125
- package/content/.morph/examples/micro-saas/contracts.cs +358 -358
- package/content/.morph/examples/micro-saas/decisions.md +246 -246
- package/content/.morph/examples/micro-saas/spec.md +236 -236
- package/content/.morph/examples/micro-saas/tasks.md +150 -150
- package/content/.morph/examples/multi-agent/README.md +309 -309
- package/content/.morph/examples/multi-agent/contracts.cs +433 -433
- package/content/.morph/examples/multi-agent/spec.md +479 -479
- package/content/.morph/examples/multi-agent/tasks.md +185 -185
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -0
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -0
- package/content/.morph/examples/scheduled-reports/spec.md +267 -0
- package/content/.morph/examples/state-v3.json +188 -0
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +190 -239
- package/content/.morph/hooks/pre-commit-agents.sh +24 -24
- package/content/.morph/hooks/pre-commit-all.sh +48 -48
- package/content/.morph/hooks/pre-commit-specs.sh +49 -49
- package/content/.morph/hooks/pre-commit-tests.sh +60 -60
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -0
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-framework-blazor-ui.md +359 -0
- package/content/.morph/standards/agent-framework-production.md +410 -0
- package/content/.morph/standards/agent-framework-setup.md +413 -453
- package/content/.morph/standards/agent-framework-workflows.md +349 -0
- package/content/.morph/standards/architecture.md +325 -325
- package/content/.morph/standards/azure.md +605 -379
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/dotnet10-migration.md +520 -494
- package/content/.morph/standards/fluent-ui-setup.md +590 -590
- package/content/.morph/standards/migration-guide.md +514 -514
- package/content/.morph/standards/passkeys-auth.md +423 -423
- package/content/.morph/standards/vector-search-rag.md +536 -536
- package/content/.morph/state.json +17 -17
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/agent.cs +163 -172
- package/content/.morph/templates/clarify-questions.md +159 -0
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -0
- package/content/.morph/templates/contracts/Entities.cs +25 -0
- package/content/.morph/templates/contracts/Queries.cs +74 -0
- package/content/.morph/templates/contracts/README.md +74 -0
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/decisions.md +123 -106
- package/content/.morph/templates/design-system.css +226 -226
- package/content/.morph/templates/infra/.dockerignore.example +89 -89
- package/content/.morph/templates/infra/Dockerfile.example +82 -82
- package/content/.morph/templates/infra/README.md +286 -286
- package/content/.morph/templates/infra/app-insights.bicep +63 -63
- package/content/.morph/templates/infra/app-service.bicep +164 -164
- package/content/.morph/templates/infra/container-app-env.bicep +49 -49
- package/content/.morph/templates/infra/container-app.bicep +156 -156
- package/content/.morph/templates/infra/deploy-checklist.md +426 -0
- package/content/.morph/templates/infra/deploy.ps1 +229 -229
- package/content/.morph/templates/infra/deploy.sh +208 -208
- package/content/.morph/templates/infra/key-vault.bicep +91 -91
- package/content/.morph/templates/infra/main.bicep +189 -189
- package/content/.morph/templates/infra/parameters.dev.json +29 -29
- package/content/.morph/templates/infra/parameters.prod.json +29 -29
- package/content/.morph/templates/infra/parameters.staging.json +29 -29
- package/content/.morph/templates/infra/sql-database.bicep +103 -103
- package/content/.morph/templates/infra/storage.bicep +106 -106
- package/content/.morph/templates/integrations/asaas-client.cs +387 -387
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
- package/content/.morph/templates/integrations/clerk-config.cs +258 -258
- package/content/.morph/templates/job.cs +171 -171
- package/content/.morph/templates/migration.cs +83 -83
- package/content/.morph/templates/proposal.md +141 -155
- package/content/.morph/templates/recap.md +94 -105
- package/content/.morph/templates/repository.cs +141 -141
- package/content/.morph/templates/saas/subscription.cs +347 -347
- package/content/.morph/templates/saas/tenant.cs +338 -338
- package/content/.morph/templates/service.cs +139 -139
- package/content/.morph/templates/simulation.md +353 -0
- package/content/.morph/templates/spec.md +149 -148
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/state.template.json +222 -222
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/tasks.md +257 -235
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-components.md +362 -276
- package/content/.morph/templates/ui-design-system.md +286 -286
- package/content/.morph/templates/ui-flows.md +336 -336
- package/content/.morph/templates/ui-mockups.md +133 -133
- package/content/.morph/test-infra/example.bicep +59 -59
- package/content/CLAUDE.md +150 -442
- package/content/README.md +79 -79
- package/detectors/config-detector.js +223 -223
- package/detectors/conversation-analyzer.js +163 -163
- package/detectors/index.js +84 -84
- package/detectors/standards-generator.js +275 -275
- package/detectors/structure-detector.js +245 -250
- package/docs/README.md +144 -149
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/getting-started.md +301 -302
- package/docs/installation.md +361 -361
- package/docs/templates.md +418 -418
- package/docs/validation-checklist.md +265 -266
- package/package.json +80 -80
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +183 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -0
- package/src/commands/create-story.js +351 -351
- package/src/commands/detect-agents.js +139 -0
- package/src/commands/detect.js +104 -104
- package/src/commands/doctor.js +356 -280
- package/src/commands/generate.js +149 -149
- package/src/commands/init.js +258 -245
- package/src/commands/lint-fluent.js +352 -0
- package/src/commands/rollback-phase.js +185 -0
- package/src/commands/session-summary.js +291 -0
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/state.js +333 -333
- package/src/commands/sync.js +167 -167
- package/src/commands/task.js +78 -0
- package/src/commands/troubleshoot.js +222 -0
- package/src/commands/update.js +192 -159
- package/src/commands/validate-blazor-state.js +210 -0
- package/src/commands/validate-blazor.js +156 -0
- package/src/commands/validate-css.js +84 -0
- package/src/commands/validate-phase.js +221 -0
- package/src/lib/blazor-concurrency-analyzer.js +288 -0
- package/src/lib/blazor-state-validator.js +291 -0
- package/src/lib/blazor-validator.js +374 -0
- package/src/lib/complexity-analyzer.js +441 -292
- package/src/lib/continuous-validator.js +421 -0
- package/src/lib/css-validator.js +352 -0
- package/src/lib/decision-constraint-loader.js +109 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/learning-system.js +520 -0
- package/src/lib/mockup-generator.js +366 -0
- package/src/lib/recap-generator.js +205 -0
- package/src/lib/state-manager.js +397 -340
- package/src/lib/troubleshoot-grep.js +194 -0
- package/src/lib/troubleshoot-index.js +144 -0
- package/src/lib/ui-detector.js +350 -0
- package/src/lib/validation-runner.js +231 -0
- package/src/lib/validators/architecture-validator.js +387 -0
- package/src/lib/validators/contract-compliance-validator.js +273 -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 +179 -139
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- package/content/.claude/commands/morph-costs.md +0 -206
- package/content/.claude/commands/morph-tasks.md +0 -319
- package/content/.claude/skills/specialists/cost-guardian.md +0 -110
- package/content/.claude/skills/stacks/shopify.md +0 -445
- package/content/.morph/config/azure-pricing.json +0 -70
- package/content/.morph/config/azure-pricing.schema.json +0 -50
- package/content/.morph/hooks/pre-commit-costs.sh +0 -91
- package/docs/api/cost-calculator.js.html +0 -513
- package/docs/api/design-system-generator.js.html +0 -382
- package/docs/api/global.html +0 -5263
- package/docs/api/index.html +0 -96
- package/docs/api/state-manager.js.html +0 -423
- package/src/commands/cost.js +0 -181
- package/src/commands/update-pricing.js +0 -206
- package/src/lib/cost-calculator.js +0 -429
|
@@ -0,0 +1,429 @@
|
|
|
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 (runs validation first)
|
|
44
|
+
* @param {string} featureName - Feature name
|
|
45
|
+
* @param {string[]} taskIds - Task IDs to complete
|
|
46
|
+
* @param {Object} options - Options
|
|
47
|
+
* @param {boolean} options.skipValidation - Skip code validation
|
|
48
|
+
*/
|
|
49
|
+
async completeTasks(featureName, taskIds, options = {}) {
|
|
50
|
+
const state = await this.loadState();
|
|
51
|
+
const feature = state.features[featureName];
|
|
52
|
+
|
|
53
|
+
if (!feature) {
|
|
54
|
+
throw new Error(`Feature '${featureName}' not found in state.json`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const results = [];
|
|
58
|
+
const tasksToComplete = [];
|
|
59
|
+
|
|
60
|
+
for (const taskId of taskIds) {
|
|
61
|
+
const task = feature.tasks.find(t => t.id === taskId);
|
|
62
|
+
|
|
63
|
+
if (!task) {
|
|
64
|
+
console.error(chalk.red(`❌ Task ${taskId} not found`));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (task.status === 'completed') {
|
|
69
|
+
console.log(chalk.yellow(`⚠️ Task ${taskId} already completed`));
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Validate dependencies
|
|
74
|
+
const missingDeps = this.checkDependencies(task, feature.tasks);
|
|
75
|
+
if (missingDeps.length > 0) {
|
|
76
|
+
console.error(chalk.red(`❌ Cannot complete ${taskId}: missing dependencies: ${missingDeps.join(', ')}`));
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
tasksToComplete.push(task);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Run validation BEFORE marking tasks as complete
|
|
84
|
+
if (tasksToComplete.length > 0 && !options.skipValidation) {
|
|
85
|
+
const validationPassed = await this.runValidation(featureName);
|
|
86
|
+
if (!validationPassed) {
|
|
87
|
+
console.error(chalk.red('\n❌ Validation failed — tasks NOT marked as complete'));
|
|
88
|
+
console.log(chalk.gray(' Fix the issues above, then run task done again'));
|
|
89
|
+
console.log(chalk.gray(' Or use --skip-validation to bypass (not recommended)\n'));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Mark tasks as completed (only after validation passes)
|
|
95
|
+
for (const task of tasksToComplete) {
|
|
96
|
+
task.status = 'completed';
|
|
97
|
+
task.completedAt = new Date().toISOString();
|
|
98
|
+
task.completedBy = 'claude';
|
|
99
|
+
|
|
100
|
+
results.push(task);
|
|
101
|
+
|
|
102
|
+
console.log(chalk.green(`✅ Task ${task.id} completed!`));
|
|
103
|
+
|
|
104
|
+
// Register checkpoint if task has one
|
|
105
|
+
if (task.checkpoint) {
|
|
106
|
+
this.registerCheckpoint(feature, task);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Update progress
|
|
111
|
+
feature.progress = this.calculateProgress(feature.tasks);
|
|
112
|
+
|
|
113
|
+
// Auto-checkpoint every 3 tasks
|
|
114
|
+
const recentCompleted = this.getRecentCompleted(feature.tasks, 3);
|
|
115
|
+
if (recentCompleted.length === 3) {
|
|
116
|
+
const lastCheckpoint = feature.checkpoints[feature.checkpoints.length - 1];
|
|
117
|
+
const shouldAutoCheckpoint = !lastCheckpoint ||
|
|
118
|
+
!recentCompleted.every(t =>
|
|
119
|
+
lastCheckpoint.tasksCompleted.includes(t.id)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
if (shouldAutoCheckpoint) {
|
|
123
|
+
await this.autoCheckpoint(feature, recentCompleted, featureName);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Save state
|
|
128
|
+
await this.saveState(state);
|
|
129
|
+
|
|
130
|
+
// Display progress
|
|
131
|
+
this.displayProgress(feature);
|
|
132
|
+
|
|
133
|
+
// Suggest next task
|
|
134
|
+
const nextTask = this.getNextTask(feature.tasks);
|
|
135
|
+
if (nextTask) {
|
|
136
|
+
console.log(chalk.cyan(`\n⏭️ Next: ${nextTask.id} - ${nextTask.title}`));
|
|
137
|
+
if (nextTask.dependencies && nextTask.dependencies.length > 0) {
|
|
138
|
+
console.log(chalk.gray(` Dependencies: ${nextTask.dependencies.join(', ')}`));
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
console.log(chalk.green('\n🎉 All tasks completed!'));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return results;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Run validation for a feature using the ValidationRunner (ESM dynamic import)
|
|
149
|
+
* @param {string} featureName - Feature name
|
|
150
|
+
* @returns {boolean} True if validation passed
|
|
151
|
+
*/
|
|
152
|
+
async runValidation(featureName) {
|
|
153
|
+
try {
|
|
154
|
+
console.log(chalk.cyan('\n🔍 Running code validation...'));
|
|
155
|
+
|
|
156
|
+
const { runValidation, formatValidationResults } = await import('../src/lib/validation-runner.js');
|
|
157
|
+
const result = await runValidation('.', featureName, { verbose: true });
|
|
158
|
+
|
|
159
|
+
formatValidationResults(result);
|
|
160
|
+
return result.passed;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
// If validation runner fails to load, warn but don't block
|
|
163
|
+
console.log(chalk.yellow(`\n⚠️ Validation skipped: ${error.message}`));
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Check if task dependencies are met
|
|
170
|
+
*/
|
|
171
|
+
checkDependencies(task, allTasks) {
|
|
172
|
+
if (!task.dependencies || task.dependencies.length === 0) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return task.dependencies.filter(depId => {
|
|
177
|
+
const dep = allTasks.find(t => t.id === depId);
|
|
178
|
+
return !dep || dep.status !== 'completed';
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Calculate progress
|
|
184
|
+
*/
|
|
185
|
+
calculateProgress(tasks) {
|
|
186
|
+
const total = tasks.length;
|
|
187
|
+
const completed = tasks.filter(t => t.status === 'completed').length;
|
|
188
|
+
const inProgress = tasks.filter(t => t.status === 'in_progress').length;
|
|
189
|
+
const pending = tasks.filter(t => t.status === 'pending').length;
|
|
190
|
+
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
total,
|
|
194
|
+
completed,
|
|
195
|
+
inProgress,
|
|
196
|
+
pending,
|
|
197
|
+
percentage
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Register checkpoint (from task flag)
|
|
203
|
+
*/
|
|
204
|
+
registerCheckpoint(feature, task) {
|
|
205
|
+
const checkpoint = {
|
|
206
|
+
id: task.checkpoint,
|
|
207
|
+
timestamp: new Date().toISOString(),
|
|
208
|
+
tasksCompleted: [task.id],
|
|
209
|
+
note: `Checkpoint: ${task.title}`
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
feature.checkpoints = feature.checkpoints || [];
|
|
213
|
+
feature.checkpoints.push(checkpoint);
|
|
214
|
+
|
|
215
|
+
console.log(chalk.magenta(`\n🎯 ${task.checkpoint} reached!`));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Auto-checkpoint every 3 tasks (includes validation summary)
|
|
220
|
+
*/
|
|
221
|
+
async autoCheckpoint(feature, tasks, featureName) {
|
|
222
|
+
let validationNote = '';
|
|
223
|
+
|
|
224
|
+
// Run validation and include summary in checkpoint
|
|
225
|
+
if (featureName) {
|
|
226
|
+
try {
|
|
227
|
+
const { runValidation } = await import('../src/lib/validation-runner.js');
|
|
228
|
+
const result = await runValidation('.', featureName, { verbose: false });
|
|
229
|
+
validationNote = result.passed
|
|
230
|
+
? ' | Validation: PASSED'
|
|
231
|
+
: ` | Validation: ${result.errors.length} errors, ${result.warnings.length} warnings`;
|
|
232
|
+
} catch {
|
|
233
|
+
// Validation not available, skip
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const checkpoint = {
|
|
238
|
+
id: `CHECKPOINT_AUTO_${Date.now()}`,
|
|
239
|
+
timestamp: new Date().toISOString(),
|
|
240
|
+
tasksCompleted: tasks.map(t => t.id),
|
|
241
|
+
note: `Auto-checkpoint: ${tasks.length} tasks completed${validationNote}`
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
feature.checkpoints = feature.checkpoints || [];
|
|
245
|
+
feature.checkpoints.push(checkpoint);
|
|
246
|
+
|
|
247
|
+
console.log(chalk.magenta(`\n🎉 Auto-checkpoint reached! ${tasks.length} tasks completed`));
|
|
248
|
+
if (validationNote) {
|
|
249
|
+
console.log(chalk.gray(` ${validationNote.trim().replace(/^ \| /, '')}`));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get recently completed tasks (last N)
|
|
255
|
+
*/
|
|
256
|
+
getRecentCompleted(tasks, count) {
|
|
257
|
+
const completed = tasks
|
|
258
|
+
.filter(t => t.status === 'completed' && t.completedAt)
|
|
259
|
+
.sort((a, b) => new Date(b.completedAt) - new Date(a.completedAt));
|
|
260
|
+
|
|
261
|
+
return completed.slice(0, count);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get next pending task (based on dependencies)
|
|
266
|
+
*/
|
|
267
|
+
getNextTask(tasks) {
|
|
268
|
+
const pending = tasks.filter(t => t.status === 'pending');
|
|
269
|
+
|
|
270
|
+
// Find first task with all dependencies completed
|
|
271
|
+
for (const task of pending) {
|
|
272
|
+
const missingDeps = this.checkDependencies(task, tasks);
|
|
273
|
+
if (missingDeps.length === 0) {
|
|
274
|
+
return task;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Display progress
|
|
283
|
+
*/
|
|
284
|
+
displayProgress(feature) {
|
|
285
|
+
const { total, completed, inProgress, pending, percentage } = feature.progress;
|
|
286
|
+
|
|
287
|
+
console.log(chalk.bold(`\n📊 Progress: ${percentage}% (${completed}/${total})`));
|
|
288
|
+
console.log(chalk.gray(` Completed: ${completed} | In Progress: ${inProgress} | Pending: ${pending}`));
|
|
289
|
+
|
|
290
|
+
// Progress bar
|
|
291
|
+
const barLength = 30;
|
|
292
|
+
const filledLength = Math.round((percentage / 100) * barLength);
|
|
293
|
+
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
|
|
294
|
+
console.log(chalk.cyan(` [${bar}] ${percentage}%`));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Start a task (mark as in_progress)
|
|
299
|
+
*/
|
|
300
|
+
async startTask(featureName, taskId) {
|
|
301
|
+
const state = await this.loadState();
|
|
302
|
+
const feature = state.features[featureName];
|
|
303
|
+
|
|
304
|
+
if (!feature) {
|
|
305
|
+
throw new Error(`Feature '${featureName}' not found`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const task = feature.tasks.find(t => t.id === taskId);
|
|
309
|
+
|
|
310
|
+
if (!task) {
|
|
311
|
+
throw new Error(`Task ${taskId} not found`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (task.status === 'completed') {
|
|
315
|
+
console.log(chalk.yellow(`⚠️ Task ${taskId} already completed`));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Validate dependencies
|
|
320
|
+
const missingDeps = this.checkDependencies(task, feature.tasks);
|
|
321
|
+
if (missingDeps.length > 0) {
|
|
322
|
+
throw new Error(`Cannot start ${taskId}: missing dependencies: ${missingDeps.join(', ')}`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
task.status = 'in_progress';
|
|
326
|
+
task.startedAt = new Date().toISOString();
|
|
327
|
+
|
|
328
|
+
await this.saveState(state);
|
|
329
|
+
|
|
330
|
+
console.log(chalk.blue(`▶️ Task ${taskId} started: ${task.title}`));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Get next task suggestion
|
|
335
|
+
*/
|
|
336
|
+
async getNext(featureName) {
|
|
337
|
+
const state = await this.loadState();
|
|
338
|
+
const feature = state.features[featureName];
|
|
339
|
+
|
|
340
|
+
if (!feature) {
|
|
341
|
+
throw new Error(`Feature '${featureName}' not found`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const nextTask = this.getNextTask(feature.tasks);
|
|
345
|
+
|
|
346
|
+
if (nextTask) {
|
|
347
|
+
console.log(chalk.cyan(`\n⏭️ Next task: ${nextTask.id} - ${nextTask.title}`));
|
|
348
|
+
if (nextTask.dependencies && nextTask.dependencies.length > 0) {
|
|
349
|
+
console.log(chalk.gray(` Dependencies: ${nextTask.dependencies.join(', ')}`));
|
|
350
|
+
}
|
|
351
|
+
if (nextTask.files && nextTask.files.length > 0) {
|
|
352
|
+
console.log(chalk.gray(` Files:`));
|
|
353
|
+
nextTask.files.forEach(f => console.log(chalk.gray(` - ${f}`)));
|
|
354
|
+
}
|
|
355
|
+
} else {
|
|
356
|
+
console.log(chalk.green('🎉 All tasks completed!'));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// CLI
|
|
362
|
+
async function main() {
|
|
363
|
+
const args = process.argv.slice(2);
|
|
364
|
+
const command = args[0];
|
|
365
|
+
|
|
366
|
+
const manager = new TaskManager();
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
switch (command) {
|
|
370
|
+
case 'done':
|
|
371
|
+
case 'complete': {
|
|
372
|
+
const skipValidation = args.includes('--skip-validation');
|
|
373
|
+
const filteredArgs = args.filter(a => a !== '--skip-validation');
|
|
374
|
+
const featureName = filteredArgs[1];
|
|
375
|
+
const taskIds = filteredArgs.slice(2);
|
|
376
|
+
|
|
377
|
+
if (!featureName || taskIds.length === 0) {
|
|
378
|
+
console.error(chalk.red('Usage: npx morph-spec task done <feature> <task-id> [task-id...] [--skip-validation]'));
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
await manager.completeTasks(featureName, taskIds, { skipValidation });
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
case 'start': {
|
|
387
|
+
const featureName = args[1];
|
|
388
|
+
const taskId = args[2];
|
|
389
|
+
|
|
390
|
+
if (!featureName || !taskId) {
|
|
391
|
+
console.error(chalk.red('Usage: npx morph-spec task start <feature> <task-id>'));
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
await manager.startTask(featureName, taskId);
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
case 'next': {
|
|
400
|
+
const featureName = args[1];
|
|
401
|
+
|
|
402
|
+
if (!featureName) {
|
|
403
|
+
console.error(chalk.red('Usage: npx morph-spec task next <feature>'));
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
await manager.getNext(featureName);
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
default:
|
|
412
|
+
console.error(chalk.red(`Unknown command: ${command}`));
|
|
413
|
+
console.log(chalk.gray('\nAvailable commands:'));
|
|
414
|
+
console.log(chalk.gray(' done <feature> <task-id...> - Mark tasks as completed'));
|
|
415
|
+
console.log(chalk.gray(' start <feature> <task-id> - Start a task (mark as in_progress)'));
|
|
416
|
+
console.log(chalk.gray(' next <feature> - Show next suggested task'));
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.error(chalk.red(`\n❌ Error: ${error.message}`));
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (require.main === module) {
|
|
426
|
+
main();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
module.exports = TaskManager;
|