musubi-sdd 6.2.1 → 6.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.
Files changed (55) hide show
  1. package/README.ja.md +3 -3
  2. package/README.md +3 -3
  3. package/bin/musubi-dashboard.js +23 -14
  4. package/bin/musubi-design.js +3 -3
  5. package/bin/musubi-gaps.js +9 -9
  6. package/bin/musubi-init.js +14 -1310
  7. package/bin/musubi-requirements.js +1 -1
  8. package/bin/musubi-tasks.js +5 -5
  9. package/bin/musubi-trace.js +23 -23
  10. package/bin/musubi-upgrade.js +400 -0
  11. package/bin/musubi.js +16 -1
  12. package/package.json +4 -3
  13. package/src/analyzers/gap-detector.js +3 -3
  14. package/src/analyzers/traceability.js +17 -17
  15. package/src/cli/dashboard-cli.js +54 -60
  16. package/src/cli/init-generators.js +464 -0
  17. package/src/cli/init-helpers.js +884 -0
  18. package/src/constitutional/checker.js +67 -65
  19. package/src/constitutional/ci-reporter.js +58 -49
  20. package/src/constitutional/index.js +2 -2
  21. package/src/constitutional/phase-minus-one.js +24 -27
  22. package/src/constitutional/steering-sync.js +29 -40
  23. package/src/dashboard/index.js +2 -2
  24. package/src/dashboard/sprint-planner.js +17 -19
  25. package/src/dashboard/sprint-reporter.js +47 -38
  26. package/src/dashboard/transition-recorder.js +12 -18
  27. package/src/dashboard/workflow-dashboard.js +28 -39
  28. package/src/enterprise/error-recovery.js +109 -49
  29. package/src/enterprise/experiment-report.js +63 -37
  30. package/src/enterprise/index.js +5 -5
  31. package/src/enterprise/rollback-manager.js +28 -29
  32. package/src/enterprise/tech-article.js +41 -35
  33. package/src/generators/design.js +3 -3
  34. package/src/generators/requirements.js +5 -3
  35. package/src/generators/tasks.js +2 -2
  36. package/src/integrations/platforms.js +1 -1
  37. package/src/templates/agents/claude-code/CLAUDE.md +1 -1
  38. package/src/templates/agents/claude-code/skills/design-reviewer/SKILL.md +132 -113
  39. package/src/templates/agents/claude-code/skills/requirements-reviewer/SKILL.md +85 -56
  40. package/src/templates/agents/codex/AGENTS.md +2 -2
  41. package/src/templates/agents/cursor/AGENTS.md +2 -2
  42. package/src/templates/agents/gemini-cli/GEMINI.md +2 -2
  43. package/src/templates/agents/github-copilot/AGENTS.md +2 -2
  44. package/src/templates/agents/github-copilot/commands/sdd-requirements.prompt.md +23 -4
  45. package/src/templates/agents/qwen-code/QWEN.md +2 -2
  46. package/src/templates/agents/shared/AGENTS.md +1 -1
  47. package/src/templates/agents/windsurf/AGENTS.md +2 -2
  48. package/src/templates/skills/browser-agent.md +1 -1
  49. package/src/traceability/extractor.js +22 -21
  50. package/src/traceability/gap-detector.js +19 -17
  51. package/src/traceability/index.js +2 -2
  52. package/src/traceability/matrix-storage.js +20 -22
  53. package/src/validators/constitution.js +5 -2
  54. package/src/validators/critic-system.js +6 -6
  55. package/src/validators/traceability-validator.js +3 -3
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Phase -1 Gate
3
- *
3
+ *
4
4
  * Triggers Phase -1 Gate review for Article VII/VIII violations.
5
- *
5
+ *
6
6
  * Requirement: IMP-6.2-005-02
7
7
  * Design: Section 5.2
8
8
  */
9
9
 
10
10
  const fs = require('fs').promises;
11
11
  const path = require('path');
12
- const { ConstitutionalChecker, SEVERITY } = require('./checker');
12
+ const { ConstitutionalChecker } = require('./checker');
13
13
 
14
14
  /**
15
15
  * Default configuration
@@ -18,7 +18,7 @@ const DEFAULT_CONFIG = {
18
18
  storageDir: 'storage/phase-minus-one',
19
19
  requiredReviewers: ['system-architect'],
20
20
  optionalReviewers: ['project-manager'],
21
- autoNotify: true
21
+ autoNotify: true,
22
22
  };
23
23
 
24
24
  /**
@@ -28,12 +28,12 @@ const GATE_STATUS = {
28
28
  PENDING: 'pending',
29
29
  APPROVED: 'approved',
30
30
  REJECTED: 'rejected',
31
- WAIVED: 'waived'
31
+ WAIVED: 'waived',
32
32
  };
33
33
 
34
34
  /**
35
35
  * PhaseMinusOneGate
36
- *
36
+ *
37
37
  * Manages Phase -1 Gate review process.
38
38
  */
39
39
  class PhaseMinusOneGate {
@@ -51,8 +51,8 @@ class PhaseMinusOneGate {
51
51
  * @returns {Promise<Object>} Gate record
52
52
  */
53
53
  async trigger(options) {
54
- const gateId = `GATE-${Date.now()}`;
55
-
54
+ const gateId = `GATE-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
55
+
56
56
  const gate = {
57
57
  id: gateId,
58
58
  featureId: options.featureId,
@@ -65,7 +65,7 @@ class PhaseMinusOneGate {
65
65
  optionalReviewers: this.config.optionalReviewers,
66
66
  reviews: [],
67
67
  resolution: null,
68
- resolvedAt: null
68
+ resolvedAt: null,
69
69
  };
70
70
 
71
71
  await this.saveGate(gate);
@@ -97,14 +97,14 @@ class PhaseMinusOneGate {
97
97
  featureId: options.featureId,
98
98
  triggeredBy: options.triggeredBy || 'auto-analysis',
99
99
  violations,
100
- affectedFiles: filePaths
100
+ affectedFiles: filePaths,
101
101
  });
102
102
 
103
103
  return {
104
104
  triggered: true,
105
105
  gate,
106
106
  checkResults,
107
- blockDecision
107
+ blockDecision,
108
108
  };
109
109
  }
110
110
 
@@ -112,7 +112,7 @@ class PhaseMinusOneGate {
112
112
  triggered: false,
113
113
  gate: null,
114
114
  checkResults,
115
- blockDecision
115
+ blockDecision,
116
116
  };
117
117
  }
118
118
 
@@ -133,7 +133,7 @@ class PhaseMinusOneGate {
133
133
  reviewer: review.reviewer,
134
134
  decision: review.decision, // 'approve', 'reject', 'request-changes'
135
135
  comments: review.comments || '',
136
- submittedAt: new Date().toISOString()
136
+ submittedAt: new Date().toISOString(),
137
137
  };
138
138
 
139
139
  gate.reviews.push(reviewEntry);
@@ -143,9 +143,7 @@ class PhaseMinusOneGate {
143
143
  .filter(r => r.decision === 'approve')
144
144
  .map(r => r.reviewer);
145
145
 
146
- const allRequiredApproved = gate.requiredReviewers.every(
147
- r => approvedReviewers.includes(r)
148
- );
146
+ const allRequiredApproved = gate.requiredReviewers.every(r => approvedReviewers.includes(r));
149
147
 
150
148
  const hasRejection = gate.reviews.some(r => r.decision === 'reject');
151
149
 
@@ -181,7 +179,7 @@ class PhaseMinusOneGate {
181
179
  gate.waiver = {
182
180
  waivedBy: waiver.waivedBy,
183
181
  justification: waiver.justification,
184
- waivedAt: new Date().toISOString()
182
+ waivedAt: new Date().toISOString(),
185
183
  };
186
184
  gate.resolvedAt = new Date().toISOString();
187
185
 
@@ -232,12 +230,12 @@ class PhaseMinusOneGate {
232
230
  */
233
231
  async getPendingForReviewer(reviewer) {
234
232
  const pendingGates = await this.listGates(GATE_STATUS.PENDING);
235
-
233
+
236
234
  return pendingGates.filter(gate => {
237
235
  const hasReviewed = gate.reviews.some(r => r.reviewer === reviewer);
238
236
  const isRequired = gate.requiredReviewers.includes(reviewer);
239
237
  const isOptional = gate.optionalReviewers.includes(reviewer);
240
-
238
+
241
239
  return !hasReviewed && (isRequired || isOptional);
242
240
  });
243
241
  }
@@ -258,7 +256,7 @@ class PhaseMinusOneGate {
258
256
  gateId: gate.id,
259
257
  message: `Phase -1 Gate review required for ${gate.featureId || 'unknown feature'}`,
260
258
  violations: gate.violations.length,
261
- createdAt: new Date().toISOString()
259
+ createdAt: new Date().toISOString(),
262
260
  });
263
261
  }
264
262
 
@@ -270,7 +268,7 @@ class PhaseMinusOneGate {
270
268
  gateId: gate.id,
271
269
  message: `Phase -1 Gate review available for ${gate.featureId || 'unknown feature'}`,
272
270
  violations: gate.violations.length,
273
- createdAt: new Date().toISOString()
271
+ createdAt: new Date().toISOString(),
274
272
  });
275
273
  }
276
274
 
@@ -326,8 +324,7 @@ class PhaseMinusOneGate {
326
324
  lines.push('| Reviewer | Decision | Date |');
327
325
  lines.push('|----------|----------|------|');
328
326
  for (const r of gate.reviews) {
329
- const emoji = r.decision === 'approve' ? '✅' :
330
- r.decision === 'reject' ? '❌' : '🔄';
327
+ const emoji = r.decision === 'approve' ? '✅' : r.decision === 'reject' ? '❌' : '🔄';
331
328
  lines.push(`| ${r.reviewer} | ${emoji} ${r.decision} | ${r.submittedAt} |`);
332
329
  }
333
330
  lines.push('');
@@ -356,7 +353,7 @@ class PhaseMinusOneGate {
356
353
  [GATE_STATUS.PENDING]: '⏳',
357
354
  [GATE_STATUS.APPROVED]: '✅',
358
355
  [GATE_STATUS.REJECTED]: '❌',
359
- [GATE_STATUS.WAIVED]: '⚠️'
356
+ [GATE_STATUS.WAIVED]: '⚠️',
360
357
  };
361
358
  return emojis[status] || '❓';
362
359
  }
@@ -398,7 +395,7 @@ class PhaseMinusOneGate {
398
395
  }
399
396
  }
400
397
 
401
- module.exports = {
402
- PhaseMinusOneGate,
403
- GATE_STATUS
398
+ module.exports = {
399
+ PhaseMinusOneGate,
400
+ GATE_STATUS,
404
401
  };
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Steering Sync
3
- *
3
+ *
4
4
  * Automatically synchronizes steering files.
5
- *
5
+ *
6
6
  * Requirement: IMP-6.2-007-01, IMP-6.2-007-02
7
7
  * Design: Section 5.3
8
8
  */
@@ -17,12 +17,12 @@ const DEFAULT_CONFIG = {
17
17
  steeringDir: 'steering',
18
18
  steeringFiles: ['tech.md', 'structure.md', 'product.md'],
19
19
  projectFile: 'project.yml',
20
- backupDir: 'steering/backups'
20
+ backupDir: 'steering/backups',
21
21
  };
22
22
 
23
23
  /**
24
24
  * SteeringSync
25
- *
25
+ *
26
26
  * Manages steering file synchronization.
27
27
  */
28
28
  class SteeringSync {
@@ -68,7 +68,7 @@ class SteeringSync {
68
68
  version: versionInfo.version,
69
69
  updatedAt: new Date().toISOString(),
70
70
  updates,
71
- filesUpdated: updates.length
71
+ filesUpdated: updates.length,
72
72
  };
73
73
  }
74
74
 
@@ -88,13 +88,13 @@ class SteeringSync {
88
88
  // Check version consistency
89
89
  if (project) {
90
90
  const versionPattern = new RegExp(`v?${project.version?.replace('.', '\\.')}`, 'i');
91
-
91
+
92
92
  if (product && !versionPattern.test(product)) {
93
93
  issues.push({
94
94
  type: 'version-mismatch',
95
95
  file: 'product.md',
96
96
  message: `product.mdのバージョン(${project.version})が不整合です`,
97
- suggestion: `product.mdを更新してバージョン${project.version}を反映してください`
97
+ suggestion: `product.mdを更新してバージョン${project.version}を反映してください`,
98
98
  });
99
99
  }
100
100
  }
@@ -102,7 +102,7 @@ class SteeringSync {
102
102
  // Check for orphaned references
103
103
  if (structure) {
104
104
  // Check if directories mentioned in structure.md exist
105
- const dirPattern = /`([a-z\-]+\/)`/g;
105
+ const dirPattern = /`([a-z-]+\/)`/g;
106
106
  let match;
107
107
  while ((match = dirPattern.exec(structure)) !== null) {
108
108
  const dirName = match[1].replace('/', '');
@@ -113,7 +113,7 @@ class SteeringSync {
113
113
  type: 'missing-directory',
114
114
  file: 'structure.md',
115
115
  message: `structure.mdで参照されているディレクトリ "${dirName}" が存在しません`,
116
- suggestion: `ディレクトリを作成するか、structure.mdから参照を削除してください`
116
+ suggestion: `ディレクトリを作成するか、structure.mdから参照を削除してください`,
117
117
  });
118
118
  }
119
119
  }
@@ -127,7 +127,7 @@ class SteeringSync {
127
127
  type: 'tech-mismatch',
128
128
  file: 'tech.md',
129
129
  message: `project.ymlの技術スタック "${techItem}" がtech.mdに記載されていません`,
130
- suggestion: `tech.mdに "${techItem}" を追加してください`
130
+ suggestion: `tech.mdに "${techItem}" を追加してください`,
131
131
  });
132
132
  }
133
133
  }
@@ -136,7 +136,7 @@ class SteeringSync {
136
136
  return {
137
137
  consistent: issues.length === 0,
138
138
  issues,
139
- checkedAt: new Date().toISOString()
139
+ checkedAt: new Date().toISOString(),
140
140
  };
141
141
  }
142
142
 
@@ -156,7 +156,7 @@ class SteeringSync {
156
156
  // Version updates require manual review
157
157
  failed.push({
158
158
  issue,
159
- reason: 'バージョン更新は手動レビューが必要です'
159
+ reason: 'バージョン更新は手動レビューが必要です',
160
160
  });
161
161
  break;
162
162
 
@@ -169,13 +169,13 @@ class SteeringSync {
169
169
  default:
170
170
  failed.push({
171
171
  issue,
172
- reason: '自動修正がサポートされていません'
172
+ reason: '自動修正がサポートされていません',
173
173
  });
174
174
  }
175
175
  } catch (error) {
176
176
  failed.push({
177
177
  issue,
178
- reason: error.message
178
+ reason: error.message,
179
179
  });
180
180
  }
181
181
  }
@@ -184,7 +184,7 @@ class SteeringSync {
184
184
  fixed: fixed.length,
185
185
  failed: failed.length,
186
186
  details: { fixed, failed },
187
- fixedAt: new Date().toISOString()
187
+ fixedAt: new Date().toISOString(),
188
188
  };
189
189
  }
190
190
 
@@ -195,10 +195,10 @@ class SteeringSync {
195
195
  */
196
196
  async updateProjectFile(versionInfo) {
197
197
  const filePath = path.join(this.config.steeringDir, this.config.projectFile);
198
-
198
+
199
199
  try {
200
200
  let content = await fs.readFile(filePath, 'utf-8');
201
-
201
+
202
202
  // Update version
203
203
  if (versionInfo.version) {
204
204
  content = content.replace(
@@ -209,17 +209,14 @@ class SteeringSync {
209
209
 
210
210
  // Update status if provided
211
211
  if (versionInfo.status) {
212
- content = content.replace(
213
- /status:\s*\w+/,
214
- `status: ${versionInfo.status}`
215
- );
212
+ content = content.replace(/status:\s*\w+/, `status: ${versionInfo.status}`);
216
213
  }
217
214
 
218
215
  await fs.writeFile(filePath, content, 'utf-8');
219
216
 
220
217
  return {
221
218
  file: this.config.projectFile,
222
- changes: ['version', 'status'].filter(k => versionInfo[k])
219
+ changes: ['version', 'status'].filter(k => versionInfo[k]),
223
220
  };
224
221
  } catch {
225
222
  return null;
@@ -233,7 +230,7 @@ class SteeringSync {
233
230
  */
234
231
  async updateProductFile(versionInfo) {
235
232
  const filePath = path.join(this.config.steeringDir, 'product.md');
236
-
233
+
237
234
  try {
238
235
  let content = await fs.readFile(filePath, 'utf-8');
239
236
  const changes = [];
@@ -251,14 +248,9 @@ class SteeringSync {
251
248
  if (versionInfo.features && versionInfo.features.length > 0) {
252
249
  const changelogMarker = '## Changelog';
253
250
  if (content.includes(changelogMarker)) {
254
- const featureList = versionInfo.features
255
- .map(f => `- ${f}`)
256
- .join('\n');
251
+ const featureList = versionInfo.features.map(f => `- ${f}`).join('\n');
257
252
  const changelogEntry = `\n### v${versionInfo.version}\n${featureList}\n`;
258
- content = content.replace(
259
- changelogMarker,
260
- `${changelogMarker}${changelogEntry}`
261
- );
253
+ content = content.replace(changelogMarker, `${changelogMarker}${changelogEntry}`);
262
254
  changes.push('changelog');
263
255
  }
264
256
  }
@@ -281,7 +273,7 @@ class SteeringSync {
281
273
  */
282
274
  async updateTechFile(versionInfo) {
283
275
  const filePath = path.join(this.config.steeringDir, 'tech.md');
284
-
276
+
285
277
  try {
286
278
  let content = await fs.readFile(filePath, 'utf-8');
287
279
  const changes = [];
@@ -294,10 +286,7 @@ class SteeringSync {
294
286
  const depsSection = '## Dependencies';
295
287
  if (content.includes(depsSection)) {
296
288
  const depEntry = `\n- **${dep.name}**: ${dep.description || dep.version || 'Added'}`;
297
- content = content.replace(
298
- depsSection,
299
- `${depsSection}${depEntry}`
300
- );
289
+ content = content.replace(depsSection, `${depsSection}${depEntry}`);
301
290
  changes.push(`added-dep:${dep.name}`);
302
291
  }
303
292
  }
@@ -322,7 +311,7 @@ class SteeringSync {
322
311
  */
323
312
  async updateStructureFile(versionInfo) {
324
313
  const filePath = path.join(this.config.steeringDir, 'structure.md');
325
-
314
+
326
315
  try {
327
316
  let content = await fs.readFile(filePath, 'utf-8');
328
317
  const changes = [];
@@ -364,7 +353,7 @@ class SteeringSync {
364
353
  for (const file of this.config.steeringFiles) {
365
354
  const sourcePath = path.join(this.config.steeringDir, file);
366
355
  const destPath = path.join(backupPath, file);
367
-
356
+
368
357
  try {
369
358
  const content = await fs.readFile(sourcePath, 'utf-8');
370
359
  await fs.writeFile(destPath, content, 'utf-8');
@@ -397,12 +386,12 @@ class SteeringSync {
397
386
  try {
398
387
  const filePath = path.join(this.config.steeringDir, this.config.projectFile);
399
388
  const content = await fs.readFile(filePath, 'utf-8');
400
-
389
+
401
390
  // Simple YAML parsing for common fields
402
391
  const version = content.match(/version:\s*['"]?([\d.]+)['"]?/)?.[1];
403
392
  const name = content.match(/name:\s*['"]?([^'\n]+)['"]?/)?.[1];
404
393
  const status = content.match(/status:\s*(\w+)/)?.[1];
405
-
394
+
406
395
  return { version, name, status };
407
396
  } catch {
408
397
  return null;
@@ -458,7 +447,7 @@ class SteeringSync {
458
447
  lines.push('');
459
448
  lines.push('| File | Status |');
460
449
  lines.push('|------|--------|');
461
-
450
+
462
451
  for (const file of this.config.steeringFiles) {
463
452
  const content = await this.loadSteeringFile(file);
464
453
  const status = content ? '✅ Present' : '❌ Missing';
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Dashboard Module
3
- *
3
+ *
4
4
  * Requirement: IMP-6.2-003
5
5
  */
6
6
 
@@ -16,5 +16,5 @@ module.exports = {
16
16
  SprintReporter,
17
17
  WORKFLOW_STAGES,
18
18
  STAGE_STATUS,
19
- PRIORITY
19
+ PRIORITY,
20
20
  };
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * SprintPlanner Implementation
3
- *
3
+ *
4
4
  * Generates sprint planning templates based on tasks.
5
- *
5
+ *
6
6
  * Requirement: IMP-6.2-003-03
7
7
  * Design: Section 4.3
8
8
  */
@@ -16,7 +16,7 @@ const path = require('path');
16
16
  const DEFAULT_CONFIG = {
17
17
  storageDir: 'storage/sprints',
18
18
  defaultSprintDuration: 14, // days
19
- defaultVelocity: 20 // story points
19
+ defaultVelocity: 20, // story points
20
20
  };
21
21
 
22
22
  /**
@@ -26,12 +26,12 @@ const PRIORITY = {
26
26
  CRITICAL: 'critical',
27
27
  HIGH: 'high',
28
28
  MEDIUM: 'medium',
29
- LOW: 'low'
29
+ LOW: 'low',
30
30
  };
31
31
 
32
32
  /**
33
33
  * SprintPlanner
34
- *
34
+ *
35
35
  * Creates and manages sprint plans.
36
36
  */
37
37
  class SprintPlanner {
@@ -49,7 +49,7 @@ class SprintPlanner {
49
49
  */
50
50
  async createSprint(options) {
51
51
  const sprintId = options.sprintId || `SPRINT-${Date.now()}`;
52
-
52
+
53
53
  const sprint = {
54
54
  id: sprintId,
55
55
  name: options.name || `Sprint ${sprintId}`,
@@ -62,7 +62,7 @@ class SprintPlanner {
62
62
  tasks: [],
63
63
  status: 'planning',
64
64
  createdAt: new Date().toISOString(),
65
- updatedAt: new Date().toISOString()
65
+ updatedAt: new Date().toISOString(),
66
66
  };
67
67
 
68
68
  await this.saveSprint(sprint);
@@ -94,7 +94,7 @@ class SprintPlanner {
94
94
  status: 'todo',
95
95
  dependencies: task.dependencies || [],
96
96
  acceptanceCriteria: task.acceptanceCriteria || [],
97
- addedAt: new Date().toISOString()
97
+ addedAt: new Date().toISOString(),
98
98
  };
99
99
 
100
100
  sprint.tasks.push(sprintTask);
@@ -218,11 +218,9 @@ class SprintPlanner {
218
218
  completedPoints,
219
219
  inProgressPoints,
220
220
  remainingPoints: totalPoints - completedPoints,
221
- completionPercentage: totalPoints > 0
222
- ? Math.round((completedPoints / totalPoints) * 100)
223
- : 0,
221
+ completionPercentage: totalPoints > 0 ? Math.round((completedPoints / totalPoints) * 100) : 0,
224
222
  velocity: sprint.velocity,
225
- overCapacity: totalPoints > sprint.velocity
223
+ overCapacity: totalPoints > sprint.velocity,
226
224
  };
227
225
  }
228
226
 
@@ -264,16 +262,16 @@ class SprintPlanner {
264
262
  lines.push('');
265
263
 
266
264
  const priorityOrder = ['critical', 'high', 'medium', 'low'];
267
-
265
+
268
266
  for (const priority of priorityOrder) {
269
267
  const priorityTasks = sprint.tasks.filter(t => t.priority === priority);
270
268
  if (priorityTasks.length > 0) {
271
269
  lines.push(`### ${priority.charAt(0).toUpperCase() + priority.slice(1)} Priority`);
272
270
  lines.push('');
273
-
271
+
274
272
  for (const task of priorityTasks) {
275
- const status = task.status === 'done' ? '✅' :
276
- task.status === 'in-progress' ? '🔄' : '⬜';
273
+ const status =
274
+ task.status === 'done' ? '✅' : task.status === 'in-progress' ? '🔄' : '⬜';
277
275
  lines.push(`- ${status} **${task.id}**: ${task.title} (${task.storyPoints}pt)`);
278
276
  if (task.requirementId) {
279
277
  lines.push(` - Requirement: ${task.requirementId}`);
@@ -296,14 +294,14 @@ class SprintPlanner {
296
294
  critical: 4,
297
295
  high: 3,
298
296
  medium: 2,
299
- low: 1
297
+ low: 1,
300
298
  };
301
299
 
302
300
  // Sort by priority weight (descending) then by dependency count (ascending)
303
301
  return [...tasks].sort((a, b) => {
304
302
  const priorityDiff = priorityWeight[b.priority] - priorityWeight[a.priority];
305
303
  if (priorityDiff !== 0) return priorityDiff;
306
-
304
+
307
305
  return (a.dependencies?.length || 0) - (b.dependencies?.length || 0);
308
306
  });
309
307
  }
@@ -326,7 +324,7 @@ class SprintPlanner {
326
324
  */
327
325
  async saveSprint(sprint) {
328
326
  await this.ensureStorageDir();
329
-
327
+
330
328
  const filePath = path.join(this.config.storageDir, `${sprint.id}.json`);
331
329
  await fs.writeFile(filePath, JSON.stringify(sprint, null, 2), 'utf-8');
332
330
  }