musubi-sdd 6.2.2 → 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.
- package/README.ja.md +3 -3
- package/README.md +3 -3
- package/bin/musubi-dashboard.js +22 -13
- package/bin/musubi-design.js +3 -3
- package/bin/musubi-gaps.js +9 -9
- package/bin/musubi-init.js +14 -1310
- package/bin/musubi-requirements.js +1 -1
- package/bin/musubi-tasks.js +5 -5
- package/bin/musubi-trace.js +23 -23
- package/bin/musubi-upgrade.js +7 -2
- package/bin/musubi.js +1 -1
- package/package.json +2 -2
- package/src/analyzers/gap-detector.js +3 -3
- package/src/analyzers/traceability.js +17 -17
- package/src/cli/dashboard-cli.js +54 -60
- package/src/cli/init-generators.js +464 -0
- package/src/cli/init-helpers.js +884 -0
- package/src/constitutional/checker.js +67 -65
- package/src/constitutional/ci-reporter.js +50 -43
- package/src/constitutional/index.js +2 -2
- package/src/constitutional/phase-minus-one.js +22 -25
- package/src/constitutional/steering-sync.js +28 -39
- package/src/dashboard/index.js +2 -2
- package/src/dashboard/sprint-planner.js +17 -19
- package/src/dashboard/sprint-reporter.js +46 -37
- package/src/dashboard/transition-recorder.js +12 -18
- package/src/dashboard/workflow-dashboard.js +27 -38
- package/src/enterprise/error-recovery.js +109 -49
- package/src/enterprise/experiment-report.js +62 -36
- package/src/enterprise/index.js +5 -5
- package/src/enterprise/rollback-manager.js +28 -29
- package/src/enterprise/tech-article.js +41 -35
- package/src/generators/design.js +3 -3
- package/src/generators/requirements.js +5 -3
- package/src/generators/tasks.js +2 -2
- package/src/integrations/platforms.js +1 -1
- package/src/templates/agents/claude-code/CLAUDE.md +1 -1
- package/src/templates/agents/claude-code/skills/design-reviewer/SKILL.md +132 -113
- package/src/templates/agents/claude-code/skills/requirements-reviewer/SKILL.md +85 -56
- package/src/templates/agents/codex/AGENTS.md +2 -2
- package/src/templates/agents/cursor/AGENTS.md +2 -2
- package/src/templates/agents/gemini-cli/GEMINI.md +2 -2
- package/src/templates/agents/github-copilot/AGENTS.md +2 -2
- package/src/templates/agents/github-copilot/commands/sdd-requirements.prompt.md +23 -4
- package/src/templates/agents/qwen-code/QWEN.md +2 -2
- package/src/templates/agents/shared/AGENTS.md +1 -1
- package/src/templates/agents/windsurf/AGENTS.md +2 -2
- package/src/templates/skills/browser-agent.md +1 -1
- package/src/traceability/extractor.js +21 -20
- package/src/traceability/gap-detector.js +19 -17
- package/src/traceability/index.js +2 -2
- package/src/traceability/matrix-storage.js +20 -22
- package/src/validators/constitution.js +5 -2
- package/src/validators/critic-system.js +6 -6
- package/src/validators/traceability-validator.js +3 -3
|
@@ -1,8 +1,8 @@
|
|
|
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
|
*/
|
|
@@ -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 {
|
|
@@ -52,7 +52,7 @@ class PhaseMinusOneGate {
|
|
|
52
52
|
*/
|
|
53
53
|
async trigger(options) {
|
|
54
54
|
const gateId = `GATE-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
55
|
-
|
|
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
|
}
|
|
@@ -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';
|
package/src/dashboard/index.js
CHANGED
|
@@ -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 =
|
|
276
|
-
|
|
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
|
}
|