agileflow 2.91.0 → 2.92.1
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/CHANGELOG.md +10 -0
- package/README.md +6 -6
- package/lib/README.md +178 -0
- package/lib/codebase-indexer.js +32 -23
- package/lib/colors.js +190 -12
- package/lib/consent.js +232 -0
- package/lib/correlation.js +277 -0
- package/lib/error-codes.js +46 -0
- package/lib/errors.js +48 -6
- package/lib/file-cache.js +182 -0
- package/lib/format-error.js +156 -0
- package/lib/path-resolver.js +155 -7
- package/lib/paths.js +212 -20
- package/lib/placeholder-registry.js +205 -0
- package/lib/registry-di.js +358 -0
- package/lib/result-schema.js +363 -0
- package/lib/result.js +210 -0
- package/lib/session-registry.js +13 -0
- package/lib/session-state-machine.js +465 -0
- package/lib/validate-commands.js +308 -0
- package/lib/validate.js +116 -52
- package/package.json +1 -1
- package/scripts/af +34 -0
- package/scripts/agent-loop.js +63 -9
- package/scripts/agileflow-configure.js +2 -2
- package/scripts/agileflow-welcome.js +491 -23
- package/scripts/archive-completed-stories.sh +57 -11
- package/scripts/claude-tmux.sh +102 -0
- package/scripts/damage-control-bash.js +3 -70
- package/scripts/damage-control-edit.js +3 -20
- package/scripts/damage-control-write.js +3 -20
- package/scripts/dependency-check.js +310 -0
- package/scripts/get-env.js +11 -4
- package/scripts/lib/configure-detect.js +23 -1
- package/scripts/lib/configure-features.js +50 -2
- package/scripts/lib/context-formatter.js +771 -0
- package/scripts/lib/context-loader.js +699 -0
- package/scripts/lib/damage-control-utils.js +107 -0
- package/scripts/lib/json-utils.sh +162 -0
- package/scripts/lib/state-migrator.js +353 -0
- package/scripts/lib/story-state-machine.js +437 -0
- package/scripts/obtain-context.js +80 -1248
- package/scripts/pre-push-check.sh +46 -0
- package/scripts/precompact-context.sh +23 -10
- package/scripts/query-codebase.js +127 -14
- package/scripts/ralph-loop.js +5 -5
- package/scripts/session-manager.js +408 -55
- package/scripts/spawn-parallel.js +666 -0
- package/scripts/tui/blessed/data/watcher.js +20 -15
- package/scripts/tui/blessed/index.js +2 -2
- package/scripts/tui/blessed/panels/output.js +14 -8
- package/scripts/tui/blessed/panels/sessions.js +22 -15
- package/scripts/tui/blessed/panels/trace.js +14 -8
- package/scripts/tui/blessed/ui/help.js +3 -3
- package/scripts/tui/blessed/ui/screen.js +4 -4
- package/scripts/tui/blessed/ui/statusbar.js +5 -9
- package/scripts/tui/blessed/ui/tabbar.js +11 -11
- package/scripts/validators/component-validator.js +41 -14
- package/scripts/validators/json-schema-validator.js +11 -4
- package/scripts/validators/markdown-validator.js +1 -2
- package/scripts/validators/migration-validator.js +17 -5
- package/scripts/validators/security-validator.js +137 -33
- package/scripts/validators/story-format-validator.js +31 -10
- package/scripts/validators/test-result-validator.js +19 -4
- package/scripts/validators/workflow-validator.js +12 -5
- package/src/core/agents/codebase-query.md +24 -0
- package/src/core/commands/adr.md +114 -0
- package/src/core/commands/agent.md +120 -0
- package/src/core/commands/assign.md +145 -0
- package/src/core/commands/babysit.md +32 -5
- package/src/core/commands/changelog.md +118 -0
- package/src/core/commands/configure.md +42 -6
- package/src/core/commands/diagnose.md +114 -0
- package/src/core/commands/epic.md +113 -0
- package/src/core/commands/handoff.md +128 -0
- package/src/core/commands/help.md +75 -0
- package/src/core/commands/pr.md +96 -0
- package/src/core/commands/roadmap/analyze.md +400 -0
- package/src/core/commands/session/new.md +132 -6
- package/src/core/commands/session/spawn.md +197 -0
- package/src/core/commands/sprint.md +22 -0
- package/src/core/commands/status.md +74 -0
- package/src/core/commands/story.md +143 -4
- package/src/core/templates/agileflow-metadata.json +55 -2
- package/src/core/templates/plan-template.md +125 -0
- package/src/core/templates/story-lifecycle.md +213 -0
- package/src/core/templates/story-template.md +4 -0
- package/src/core/templates/tdd-test-template.js +241 -0
- package/tools/cli/commands/setup.js +95 -0
- package/tools/cli/installers/core/installer.js +94 -0
- package/tools/cli/installers/ide/_base-ide.js +20 -11
- package/tools/cli/installers/ide/codex.js +29 -47
- package/tools/cli/installers/ide/windsurf.js +1 -1
- package/tools/cli/lib/config-manager.js +17 -2
- package/tools/cli/lib/content-transformer.js +271 -0
- package/tools/cli/lib/error-handler.js +14 -22
- package/tools/cli/lib/ide-error-factory.js +421 -0
- package/tools/cli/lib/ide-health-monitor.js +364 -0
- package/tools/cli/lib/ide-registry.js +113 -2
- package/tools/cli/lib/ui.js +15 -25
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgileFlow CLI - Story State Machine
|
|
3
|
+
*
|
|
4
|
+
* Enforces valid status transitions for user stories and maintains an audit trail.
|
|
5
|
+
* Prevents invalid state changes and provides clear error messages.
|
|
6
|
+
*
|
|
7
|
+
* Valid Status Values:
|
|
8
|
+
* - ready: Story is defined and ready to be worked on
|
|
9
|
+
* - in_progress: Story is actively being worked on
|
|
10
|
+
* - in_review: Story implementation complete, awaiting review
|
|
11
|
+
* - blocked: Story cannot proceed due to external dependency
|
|
12
|
+
* - completed: Story is done and verified
|
|
13
|
+
* - archived: Story has been archived (historical)
|
|
14
|
+
*
|
|
15
|
+
* Valid Transitions:
|
|
16
|
+
* - ready → in_progress, blocked
|
|
17
|
+
* - in_progress → in_review, blocked, ready
|
|
18
|
+
* - in_review → completed, in_progress, blocked
|
|
19
|
+
* - blocked → ready, in_progress, in_review
|
|
20
|
+
* - completed → archived, in_progress (reopened)
|
|
21
|
+
* - archived → (terminal - no transitions out)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// Valid story statuses - SINGLE SOURCE OF TRUTH
|
|
25
|
+
// All validators should import these constants instead of hardcoding
|
|
26
|
+
const VALID_STATUSES = ['ready', 'in_progress', 'in_review', 'blocked', 'completed', 'archived'];
|
|
27
|
+
|
|
28
|
+
// Statuses that indicate a story is "done" (for metrics and epic completion checks)
|
|
29
|
+
const COMPLETED_STATUSES = ['completed', 'archived'];
|
|
30
|
+
|
|
31
|
+
// Define valid state transitions
|
|
32
|
+
// Key = from state, Value = array of valid "to" states
|
|
33
|
+
const TRANSITIONS = {
|
|
34
|
+
ready: ['in_progress', 'blocked'],
|
|
35
|
+
in_progress: ['in_review', 'blocked', 'ready'],
|
|
36
|
+
in_review: ['completed', 'in_progress', 'blocked'],
|
|
37
|
+
blocked: ['ready', 'in_progress', 'in_review'],
|
|
38
|
+
completed: ['archived', 'in_progress'],
|
|
39
|
+
archived: [], // Terminal state
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Audit trail storage
|
|
43
|
+
let auditTrail = [];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if a status is valid
|
|
47
|
+
* @param {string} status - Status to check
|
|
48
|
+
* @returns {boolean} True if valid
|
|
49
|
+
*/
|
|
50
|
+
function isValidStatus(status) {
|
|
51
|
+
return VALID_STATUSES.includes(status);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if a transition is valid
|
|
56
|
+
* @param {string} fromStatus - Current status
|
|
57
|
+
* @param {string} toStatus - Target status
|
|
58
|
+
* @returns {boolean} True if transition is valid
|
|
59
|
+
*/
|
|
60
|
+
function isValidTransition(fromStatus, toStatus) {
|
|
61
|
+
// Same status is always valid (no-op)
|
|
62
|
+
if (fromStatus === toStatus) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if fromStatus has defined transitions
|
|
67
|
+
if (!TRANSITIONS[fromStatus]) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check if toStatus is in the valid transitions list
|
|
72
|
+
return TRANSITIONS[fromStatus].includes(toStatus);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get valid transitions from a status
|
|
77
|
+
* @param {string} fromStatus - Current status
|
|
78
|
+
* @returns {Array<string>} Array of valid target statuses
|
|
79
|
+
*/
|
|
80
|
+
function getValidTransitions(fromStatus) {
|
|
81
|
+
return TRANSITIONS[fromStatus] || [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create an audit entry
|
|
86
|
+
* @param {string} storyId - Story identifier
|
|
87
|
+
* @param {string} fromStatus - Previous status
|
|
88
|
+
* @param {string} toStatus - New status
|
|
89
|
+
* @param {Object} [metadata] - Additional metadata
|
|
90
|
+
* @returns {Object} Audit entry
|
|
91
|
+
*/
|
|
92
|
+
function createAuditEntry(storyId, fromStatus, toStatus, metadata = {}) {
|
|
93
|
+
return {
|
|
94
|
+
story_id: storyId,
|
|
95
|
+
from_status: fromStatus,
|
|
96
|
+
to_status: toStatus,
|
|
97
|
+
transitioned_at: new Date().toISOString(),
|
|
98
|
+
transitioned_by: metadata.actor || 'system',
|
|
99
|
+
reason: metadata.reason || null,
|
|
100
|
+
metadata: metadata.extra || {},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Log transition to audit trail
|
|
106
|
+
* @param {Object} entry - Audit entry
|
|
107
|
+
*/
|
|
108
|
+
function logAuditEntry(entry) {
|
|
109
|
+
auditTrail.push(entry);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get audit trail
|
|
114
|
+
* @param {Object} [filter] - Optional filter
|
|
115
|
+
* @param {string} [filter.storyId] - Filter by story ID
|
|
116
|
+
* @param {string} [filter.fromStatus] - Filter by from status
|
|
117
|
+
* @param {string} [filter.toStatus] - Filter by to status
|
|
118
|
+
* @returns {Array} Filtered audit entries
|
|
119
|
+
*/
|
|
120
|
+
function getAuditTrail(filter = {}) {
|
|
121
|
+
let entries = [...auditTrail];
|
|
122
|
+
|
|
123
|
+
if (filter.storyId || filter.story_id) {
|
|
124
|
+
const id = filter.storyId || filter.story_id;
|
|
125
|
+
entries = entries.filter(e => e.story_id === id);
|
|
126
|
+
}
|
|
127
|
+
if (filter.fromStatus || filter.from_status) {
|
|
128
|
+
const status = filter.fromStatus || filter.from_status;
|
|
129
|
+
entries = entries.filter(e => e.from_status === status);
|
|
130
|
+
}
|
|
131
|
+
if (filter.toStatus || filter.to_status) {
|
|
132
|
+
const status = filter.toStatus || filter.to_status;
|
|
133
|
+
entries = entries.filter(e => e.to_status === status);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return entries;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Clear audit trail (for testing)
|
|
141
|
+
*/
|
|
142
|
+
function clearAuditTrail() {
|
|
143
|
+
auditTrail = [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Transition a story to a new status
|
|
148
|
+
* @param {Object} story - Story object with at least { id, status }
|
|
149
|
+
* @param {string} toStatus - Target status
|
|
150
|
+
* @param {Object} [options] - Transition options
|
|
151
|
+
* @param {string} [options.actor] - Who is making the transition
|
|
152
|
+
* @param {string} [options.reason] - Reason for transition
|
|
153
|
+
* @param {boolean} [options.force=false] - Force transition even if invalid
|
|
154
|
+
* @returns {{ success: boolean, story: Object, error: string | null, auditEntry: Object | null }}
|
|
155
|
+
*/
|
|
156
|
+
function transition(story, toStatus, options = {}) {
|
|
157
|
+
const { actor = 'system', reason = null, force = false } = options;
|
|
158
|
+
|
|
159
|
+
// Validate inputs
|
|
160
|
+
if (!story || typeof story !== 'object') {
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
story: null,
|
|
164
|
+
error: 'Invalid story object',
|
|
165
|
+
auditEntry: null,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const storyId = story.id || story.storyId || 'unknown';
|
|
170
|
+
const fromStatus = story.status || 'ready';
|
|
171
|
+
|
|
172
|
+
// Validate target status
|
|
173
|
+
if (!isValidStatus(toStatus)) {
|
|
174
|
+
return {
|
|
175
|
+
success: false,
|
|
176
|
+
story,
|
|
177
|
+
error: `Invalid status: "${toStatus}". Valid statuses are: ${VALID_STATUSES.join(', ')}`,
|
|
178
|
+
auditEntry: null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check transition validity
|
|
183
|
+
if (!force && !isValidTransition(fromStatus, toStatus)) {
|
|
184
|
+
const validTargets = getValidTransitions(fromStatus);
|
|
185
|
+
return {
|
|
186
|
+
success: false,
|
|
187
|
+
story,
|
|
188
|
+
error: `Invalid transition: ${fromStatus} → ${toStatus}. Valid transitions from "${fromStatus}" are: ${validTargets.join(', ') || 'none'}`,
|
|
189
|
+
auditEntry: null,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Same status is a no-op
|
|
194
|
+
if (fromStatus === toStatus) {
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
story,
|
|
198
|
+
error: null,
|
|
199
|
+
auditEntry: null,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Create audit entry
|
|
204
|
+
const auditEntry = createAuditEntry(storyId, fromStatus, toStatus, {
|
|
205
|
+
actor,
|
|
206
|
+
reason,
|
|
207
|
+
extra: { forced: force && !isValidTransition(fromStatus, toStatus) },
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Log to audit trail
|
|
211
|
+
logAuditEntry(auditEntry);
|
|
212
|
+
|
|
213
|
+
// Update story
|
|
214
|
+
const updatedStory = {
|
|
215
|
+
...story,
|
|
216
|
+
status: toStatus,
|
|
217
|
+
transitioned_at: auditEntry.transitioned_at,
|
|
218
|
+
transitioned_by: auditEntry.transitioned_by,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Add history entry if story has history array
|
|
222
|
+
if (Array.isArray(story.history)) {
|
|
223
|
+
updatedStory.history = [
|
|
224
|
+
...story.history,
|
|
225
|
+
{
|
|
226
|
+
from: fromStatus,
|
|
227
|
+
to: toStatus,
|
|
228
|
+
at: auditEntry.transitioned_at,
|
|
229
|
+
by: auditEntry.transitioned_by,
|
|
230
|
+
reason,
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
success: true,
|
|
237
|
+
story: updatedStory,
|
|
238
|
+
error: null,
|
|
239
|
+
auditEntry,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Batch transition multiple stories
|
|
245
|
+
* @param {Array<Object>} stories - Array of story objects
|
|
246
|
+
* @param {string} toStatus - Target status
|
|
247
|
+
* @param {Object} [options] - Transition options
|
|
248
|
+
* @returns {{ success: boolean, results: Array, errors: Array }}
|
|
249
|
+
*/
|
|
250
|
+
function batchTransition(stories, toStatus, options = {}) {
|
|
251
|
+
const results = [];
|
|
252
|
+
const errors = [];
|
|
253
|
+
|
|
254
|
+
for (const story of stories) {
|
|
255
|
+
const result = transition(story, toStatus, options);
|
|
256
|
+
results.push(result);
|
|
257
|
+
if (!result.success) {
|
|
258
|
+
errors.push({ story_id: story.id || 'unknown', error: result.error });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
success: errors.length === 0,
|
|
264
|
+
results,
|
|
265
|
+
errors,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get status workflow documentation
|
|
271
|
+
* @returns {Object} Workflow documentation
|
|
272
|
+
*/
|
|
273
|
+
function getWorkflowDoc() {
|
|
274
|
+
return {
|
|
275
|
+
statuses: VALID_STATUSES,
|
|
276
|
+
transitions: TRANSITIONS,
|
|
277
|
+
description: {
|
|
278
|
+
ready: 'Story is defined and ready to be worked on',
|
|
279
|
+
in_progress: 'Story is actively being worked on',
|
|
280
|
+
in_review: 'Story implementation complete, awaiting review',
|
|
281
|
+
blocked: 'Story cannot proceed due to external dependency',
|
|
282
|
+
completed: 'Story is done and verified',
|
|
283
|
+
archived: 'Story has been archived (historical)',
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Validate a story object has required fields
|
|
290
|
+
* @param {Object} story - Story to validate
|
|
291
|
+
* @returns {{ valid: boolean, errors: Array<string> }}
|
|
292
|
+
*/
|
|
293
|
+
function validateStory(story) {
|
|
294
|
+
const errors = [];
|
|
295
|
+
|
|
296
|
+
if (!story) {
|
|
297
|
+
errors.push('Story is null or undefined');
|
|
298
|
+
return { valid: false, errors };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (!story.id && !story.storyId) {
|
|
302
|
+
errors.push('Story must have an id or storyId field');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (story.status && !isValidStatus(story.status)) {
|
|
306
|
+
errors.push(`Invalid status: ${story.status}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
valid: errors.length === 0,
|
|
311
|
+
errors,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Check if all stories in an epic are complete
|
|
317
|
+
* @param {Object} statusData - Full status.json data
|
|
318
|
+
* @param {string} epicId - Epic ID to check
|
|
319
|
+
* @returns {{ allComplete: boolean, total: number, completed: number, remaining: Array }}
|
|
320
|
+
*/
|
|
321
|
+
function checkEpicCompletion(statusData, epicId) {
|
|
322
|
+
const epic = statusData.epics?.[epicId];
|
|
323
|
+
if (!epic) {
|
|
324
|
+
return { allComplete: false, total: 0, completed: 0, remaining: [] };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const storyIds = epic.stories || [];
|
|
328
|
+
const completedStatuses = ['completed', 'done', 'archived'];
|
|
329
|
+
const completed = [];
|
|
330
|
+
const remaining = [];
|
|
331
|
+
|
|
332
|
+
for (const storyId of storyIds) {
|
|
333
|
+
const story = statusData.stories?.[storyId];
|
|
334
|
+
if (story && completedStatuses.includes(story.status)) {
|
|
335
|
+
completed.push(storyId);
|
|
336
|
+
} else {
|
|
337
|
+
remaining.push(storyId);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
allComplete: remaining.length === 0 && storyIds.length > 0,
|
|
343
|
+
total: storyIds.length,
|
|
344
|
+
completed: completed.length,
|
|
345
|
+
remaining,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Auto-complete an epic if all its stories are done
|
|
351
|
+
* @param {Object} statusData - Full status.json data (will be mutated)
|
|
352
|
+
* @param {string} epicId - Epic ID to check and potentially complete
|
|
353
|
+
* @returns {{ updated: boolean, epic: Object | null, message: string }}
|
|
354
|
+
*/
|
|
355
|
+
function autoCompleteEpic(statusData, epicId) {
|
|
356
|
+
const epic = statusData.epics?.[epicId];
|
|
357
|
+
if (!epic) {
|
|
358
|
+
return { updated: false, epic: null, message: `Epic ${epicId} not found` };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Already complete
|
|
362
|
+
if (epic.status === 'complete' || epic.status === 'completed') {
|
|
363
|
+
return { updated: false, epic, message: `Epic ${epicId} already complete` };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const { allComplete, total, completed } = checkEpicCompletion(statusData, epicId);
|
|
367
|
+
|
|
368
|
+
if (allComplete) {
|
|
369
|
+
epic.status = 'complete';
|
|
370
|
+
epic.completed_at = new Date().toISOString(); // Use full ISO 8601 format
|
|
371
|
+
statusData.updated_at = new Date().toISOString();
|
|
372
|
+
return {
|
|
373
|
+
updated: true,
|
|
374
|
+
epic,
|
|
375
|
+
message: `Epic ${epicId} auto-completed (${completed}/${total} stories done)`,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
updated: false,
|
|
381
|
+
epic,
|
|
382
|
+
message: `Epic ${epicId} not complete yet (${completed}/${total} stories done)`,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Find epics that should be marked complete but aren't
|
|
388
|
+
* @param {Object} statusData - Full status.json data
|
|
389
|
+
* @returns {Array<{ epicId: string, completed: number, total: number }>}
|
|
390
|
+
*/
|
|
391
|
+
function findIncompleteEpics(statusData) {
|
|
392
|
+
const incompleteEpics = [];
|
|
393
|
+
|
|
394
|
+
for (const [epicId, epic] of Object.entries(statusData.epics || {})) {
|
|
395
|
+
if (epic.status === 'complete' || epic.status === 'completed') {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const { allComplete, total, completed } = checkEpicCompletion(statusData, epicId);
|
|
400
|
+
if (allComplete) {
|
|
401
|
+
incompleteEpics.push({ epicId, completed, total });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return incompleteEpics;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
module.exports = {
|
|
409
|
+
// Constants
|
|
410
|
+
VALID_STATUSES,
|
|
411
|
+
COMPLETED_STATUSES,
|
|
412
|
+
TRANSITIONS,
|
|
413
|
+
|
|
414
|
+
// Validation
|
|
415
|
+
isValidStatus,
|
|
416
|
+
isValidTransition,
|
|
417
|
+
getValidTransitions,
|
|
418
|
+
validateStory,
|
|
419
|
+
|
|
420
|
+
// Transitions
|
|
421
|
+
transition,
|
|
422
|
+
batchTransition,
|
|
423
|
+
|
|
424
|
+
// Audit trail
|
|
425
|
+
createAuditEntry,
|
|
426
|
+
logAuditEntry,
|
|
427
|
+
getAuditTrail,
|
|
428
|
+
clearAuditTrail,
|
|
429
|
+
|
|
430
|
+
// Documentation
|
|
431
|
+
getWorkflowDoc,
|
|
432
|
+
|
|
433
|
+
// Epic completion
|
|
434
|
+
checkEpicCompletion,
|
|
435
|
+
autoCompleteEpic,
|
|
436
|
+
findIncompleteEpics,
|
|
437
|
+
};
|