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.
Files changed (100) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +6 -6
  3. package/lib/README.md +178 -0
  4. package/lib/codebase-indexer.js +32 -23
  5. package/lib/colors.js +190 -12
  6. package/lib/consent.js +232 -0
  7. package/lib/correlation.js +277 -0
  8. package/lib/error-codes.js +46 -0
  9. package/lib/errors.js +48 -6
  10. package/lib/file-cache.js +182 -0
  11. package/lib/format-error.js +156 -0
  12. package/lib/path-resolver.js +155 -7
  13. package/lib/paths.js +212 -20
  14. package/lib/placeholder-registry.js +205 -0
  15. package/lib/registry-di.js +358 -0
  16. package/lib/result-schema.js +363 -0
  17. package/lib/result.js +210 -0
  18. package/lib/session-registry.js +13 -0
  19. package/lib/session-state-machine.js +465 -0
  20. package/lib/validate-commands.js +308 -0
  21. package/lib/validate.js +116 -52
  22. package/package.json +1 -1
  23. package/scripts/af +34 -0
  24. package/scripts/agent-loop.js +63 -9
  25. package/scripts/agileflow-configure.js +2 -2
  26. package/scripts/agileflow-welcome.js +491 -23
  27. package/scripts/archive-completed-stories.sh +57 -11
  28. package/scripts/claude-tmux.sh +102 -0
  29. package/scripts/damage-control-bash.js +3 -70
  30. package/scripts/damage-control-edit.js +3 -20
  31. package/scripts/damage-control-write.js +3 -20
  32. package/scripts/dependency-check.js +310 -0
  33. package/scripts/get-env.js +11 -4
  34. package/scripts/lib/configure-detect.js +23 -1
  35. package/scripts/lib/configure-features.js +50 -2
  36. package/scripts/lib/context-formatter.js +771 -0
  37. package/scripts/lib/context-loader.js +699 -0
  38. package/scripts/lib/damage-control-utils.js +107 -0
  39. package/scripts/lib/json-utils.sh +162 -0
  40. package/scripts/lib/state-migrator.js +353 -0
  41. package/scripts/lib/story-state-machine.js +437 -0
  42. package/scripts/obtain-context.js +80 -1248
  43. package/scripts/pre-push-check.sh +46 -0
  44. package/scripts/precompact-context.sh +23 -10
  45. package/scripts/query-codebase.js +127 -14
  46. package/scripts/ralph-loop.js +5 -5
  47. package/scripts/session-manager.js +408 -55
  48. package/scripts/spawn-parallel.js +666 -0
  49. package/scripts/tui/blessed/data/watcher.js +20 -15
  50. package/scripts/tui/blessed/index.js +2 -2
  51. package/scripts/tui/blessed/panels/output.js +14 -8
  52. package/scripts/tui/blessed/panels/sessions.js +22 -15
  53. package/scripts/tui/blessed/panels/trace.js +14 -8
  54. package/scripts/tui/blessed/ui/help.js +3 -3
  55. package/scripts/tui/blessed/ui/screen.js +4 -4
  56. package/scripts/tui/blessed/ui/statusbar.js +5 -9
  57. package/scripts/tui/blessed/ui/tabbar.js +11 -11
  58. package/scripts/validators/component-validator.js +41 -14
  59. package/scripts/validators/json-schema-validator.js +11 -4
  60. package/scripts/validators/markdown-validator.js +1 -2
  61. package/scripts/validators/migration-validator.js +17 -5
  62. package/scripts/validators/security-validator.js +137 -33
  63. package/scripts/validators/story-format-validator.js +31 -10
  64. package/scripts/validators/test-result-validator.js +19 -4
  65. package/scripts/validators/workflow-validator.js +12 -5
  66. package/src/core/agents/codebase-query.md +24 -0
  67. package/src/core/commands/adr.md +114 -0
  68. package/src/core/commands/agent.md +120 -0
  69. package/src/core/commands/assign.md +145 -0
  70. package/src/core/commands/babysit.md +32 -5
  71. package/src/core/commands/changelog.md +118 -0
  72. package/src/core/commands/configure.md +42 -6
  73. package/src/core/commands/diagnose.md +114 -0
  74. package/src/core/commands/epic.md +113 -0
  75. package/src/core/commands/handoff.md +128 -0
  76. package/src/core/commands/help.md +75 -0
  77. package/src/core/commands/pr.md +96 -0
  78. package/src/core/commands/roadmap/analyze.md +400 -0
  79. package/src/core/commands/session/new.md +132 -6
  80. package/src/core/commands/session/spawn.md +197 -0
  81. package/src/core/commands/sprint.md +22 -0
  82. package/src/core/commands/status.md +74 -0
  83. package/src/core/commands/story.md +143 -4
  84. package/src/core/templates/agileflow-metadata.json +55 -2
  85. package/src/core/templates/plan-template.md +125 -0
  86. package/src/core/templates/story-lifecycle.md +213 -0
  87. package/src/core/templates/story-template.md +4 -0
  88. package/src/core/templates/tdd-test-template.js +241 -0
  89. package/tools/cli/commands/setup.js +95 -0
  90. package/tools/cli/installers/core/installer.js +94 -0
  91. package/tools/cli/installers/ide/_base-ide.js +20 -11
  92. package/tools/cli/installers/ide/codex.js +29 -47
  93. package/tools/cli/installers/ide/windsurf.js +1 -1
  94. package/tools/cli/lib/config-manager.js +17 -2
  95. package/tools/cli/lib/content-transformer.js +271 -0
  96. package/tools/cli/lib/error-handler.js +14 -22
  97. package/tools/cli/lib/ide-error-factory.js +421 -0
  98. package/tools/cli/lib/ide-health-monitor.js +364 -0
  99. package/tools/cli/lib/ide-registry.js +113 -2
  100. 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
+ };