agileflow 2.99.8 → 3.0.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 (65) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/lib/cache-provider.js +155 -0
  3. package/lib/codebase-indexer.js +1 -1
  4. package/lib/content-sanitizer.js +1 -0
  5. package/lib/dashboard-protocol.js +25 -0
  6. package/lib/dashboard-server.js +184 -133
  7. package/lib/errors.js +18 -0
  8. package/lib/file-cache.js +1 -1
  9. package/lib/flag-detection.js +11 -20
  10. package/lib/git-operations.js +15 -33
  11. package/lib/merge-operations.js +40 -34
  12. package/lib/process-executor.js +199 -0
  13. package/lib/registry-cache.js +13 -47
  14. package/lib/skill-loader.js +206 -0
  15. package/lib/smart-json-file.js +2 -4
  16. package/package.json +1 -1
  17. package/scripts/agileflow-configure.js +13 -12
  18. package/scripts/agileflow-statusline.sh +30 -0
  19. package/scripts/agileflow-welcome.js +181 -212
  20. package/scripts/auto-self-improve.js +3 -3
  21. package/scripts/claude-smart.sh +67 -0
  22. package/scripts/claude-tmux.sh +248 -161
  23. package/scripts/damage-control-multi-agent.js +227 -0
  24. package/scripts/lib/bus-utils.js +471 -0
  25. package/scripts/lib/configure-detect.js +5 -6
  26. package/scripts/lib/configure-features.js +44 -0
  27. package/scripts/lib/configure-repair.js +5 -6
  28. package/scripts/lib/configure-utils.js +2 -3
  29. package/scripts/lib/context-formatter.js +87 -8
  30. package/scripts/lib/damage-control-utils.js +37 -3
  31. package/scripts/lib/file-lock.js +392 -0
  32. package/scripts/lib/ideation-index.js +2 -5
  33. package/scripts/lib/lifecycle-detector.js +123 -0
  34. package/scripts/lib/process-cleanup.js +55 -81
  35. package/scripts/lib/scale-detector.js +357 -0
  36. package/scripts/lib/signal-detectors.js +779 -0
  37. package/scripts/lib/story-state-machine.js +1 -1
  38. package/scripts/lib/sync-ideation-status.js +2 -3
  39. package/scripts/lib/task-registry.js +7 -1
  40. package/scripts/lib/team-events.js +357 -0
  41. package/scripts/messaging-bridge.js +79 -36
  42. package/scripts/migrate-ideation-index.js +37 -14
  43. package/scripts/obtain-context.js +37 -19
  44. package/scripts/ralph-loop.js +3 -4
  45. package/scripts/smart-detect.js +390 -0
  46. package/scripts/team-manager.js +174 -30
  47. package/src/core/commands/audit.md +13 -11
  48. package/src/core/commands/babysit.md +162 -115
  49. package/src/core/commands/changelog.md +21 -4
  50. package/src/core/commands/configure.md +105 -2
  51. package/src/core/commands/debt.md +12 -2
  52. package/src/core/commands/feedback.md +7 -6
  53. package/src/core/commands/ideate/history.md +1 -1
  54. package/src/core/commands/ideate/new.md +5 -5
  55. package/src/core/commands/logic/audit.md +2 -2
  56. package/src/core/commands/pr.md +7 -6
  57. package/src/core/commands/research/analyze.md +28 -20
  58. package/src/core/commands/research/ask.md +43 -0
  59. package/src/core/commands/research/import.md +29 -21
  60. package/src/core/commands/research/list.md +8 -7
  61. package/src/core/commands/research/synthesize.md +356 -20
  62. package/src/core/commands/research/view.md +8 -5
  63. package/src/core/commands/review.md +24 -6
  64. package/src/core/commands/skill/create.md +34 -0
  65. package/tools/cli/lib/docs-setup.js +4 -0
@@ -54,6 +54,33 @@ function getFeatureFlags() {
54
54
  return _featureFlags;
55
55
  }
56
56
 
57
+ // Lazy-load file-lock for atomic writes
58
+ let _fileLock;
59
+ function getFileLock() {
60
+ if (!_fileLock) {
61
+ try {
62
+ _fileLock = require('./lib/file-lock');
63
+ } catch (e) {
64
+ // Fall back to direct writes
65
+ _fileLock = null;
66
+ }
67
+ }
68
+ return _fileLock;
69
+ }
70
+
71
+ // Lazy-load messaging bridge for event logging
72
+ let _messagingBridge;
73
+ function getMessagingBridge() {
74
+ if (!_messagingBridge) {
75
+ try {
76
+ _messagingBridge = require('./messaging-bridge');
77
+ } catch (e) {
78
+ _messagingBridge = null;
79
+ }
80
+ }
81
+ return _messagingBridge;
82
+ }
83
+
57
84
  /**
58
85
  * Find the teams directory
59
86
  */
@@ -124,7 +151,33 @@ function getTemplate(rootDir, name) {
124
151
  }
125
152
 
126
153
  /**
127
- * Start a team from a template
154
+ * Build the native TeamCreate payload from an AgileFlow template.
155
+ * This structures the template data in the format expected by Claude Code's
156
+ * experimental Agent Teams TeamCreate tool.
157
+ *
158
+ * @param {object} template - Parsed team template
159
+ * @param {string} templateName - Template name
160
+ * @returns {object} Native TeamCreate payload
161
+ */
162
+ function buildNativeTeamPayload(template, templateName) {
163
+ return {
164
+ name: template.name || templateName,
165
+ description: template.description || `AgileFlow team: ${templateName}`,
166
+ teammates: (template.teammates || []).map(t => ({
167
+ name: t.agent,
168
+ role: t.role || t.domain,
169
+ instructions: t.instructions || `${t.role} agent for ${t.domain}`,
170
+ })),
171
+ delegate_mode: template.delegate_mode !== false,
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Start a team from a template.
177
+ * When native Agent Teams is enabled, builds a TeamCreate-compatible payload.
178
+ * When disabled, falls back to subagent orchestration mode.
179
+ *
180
+ * Uses atomic writes for session-state.json to prevent concurrent write conflicts.
128
181
  */
129
182
  function startTeam(rootDir, templateName) {
130
183
  const ff = getFeatureFlags();
@@ -136,51 +189,100 @@ function startTeam(rootDir, templateName) {
136
189
 
137
190
  const template = result.template;
138
191
 
139
- // Record active team in session-state.json
192
+ // Build native payload when in native mode
193
+ let nativePayload = null;
194
+ if (mode === 'native') {
195
+ nativePayload = buildNativeTeamPayload(template, templateName);
196
+ }
197
+
198
+ // Generate trace ID for correlating events across the team lifecycle
199
+ const traceId = `trace-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
200
+
201
+ // Record active team in session-state.json using atomic writes
140
202
  try {
141
203
  const paths = getPaths();
142
204
  const sessionStatePath = paths.getSessionStatePath(rootDir);
143
- let state = {};
144
- if (fs.existsSync(sessionStatePath)) {
145
- state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
146
- }
205
+ const fileLock = getFileLock();
206
+
207
+ const updateState = (state) => {
208
+ state.active_team = {
209
+ template: templateName,
210
+ mode,
211
+ trace_id: traceId,
212
+ native_payload: nativePayload,
213
+ lead: template.lead,
214
+ teammates: template.teammates.map(t => ({
215
+ agent: t.agent,
216
+ role: t.role,
217
+ domain: t.domain,
218
+ status: 'pending',
219
+ })),
220
+ quality_gates: template.quality_gates,
221
+ started_at: new Date().toISOString(),
222
+ };
147
223
 
148
- state.active_team = {
149
- template: templateName,
150
- mode,
151
- lead: template.lead,
152
- teammates: template.teammates.map(t => ({
153
- agent: t.agent,
154
- role: t.role,
155
- domain: t.domain,
156
- status: 'pending',
157
- })),
158
- quality_gates: template.quality_gates,
159
- started_at: new Date().toISOString(),
160
- };
224
+ state.team_metrics = {
225
+ started_at: new Date().toISOString(),
226
+ template: templateName,
227
+ mode,
228
+ trace_id: traceId,
229
+ teammate_count: template.teammates.length,
230
+ tasks_assigned: 0,
231
+ tasks_completed: 0,
232
+ messages_sent: 0,
233
+ gate_runs: [],
234
+ };
161
235
 
162
- state.team_metrics = {
163
- started_at: new Date().toISOString(),
164
- template: templateName,
165
- teammate_count: template.teammates.length,
166
- tasks_assigned: 0,
167
- tasks_completed: 0,
168
- messages_sent: 0,
169
- gate_runs: [],
236
+ return state;
170
237
  };
171
238
 
172
- fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
239
+ if (fileLock) {
240
+ // Use atomic read-modify-write when available
241
+ if (fs.existsSync(sessionStatePath)) {
242
+ fileLock.atomicReadModifyWrite(sessionStatePath, updateState);
243
+ } else {
244
+ fileLock.atomicWriteJSON(sessionStatePath, updateState({}));
245
+ }
246
+ } else {
247
+ // Fallback to direct write
248
+ let state = {};
249
+ if (fs.existsSync(sessionStatePath)) {
250
+ state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
251
+ }
252
+ state = updateState(state);
253
+ fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
254
+ }
173
255
  } catch (e) {
174
256
  // Non-critical - team can still function without state tracking
175
257
  }
176
258
 
259
+ // Log team_created event to bus
260
+ try {
261
+ const bridge = getMessagingBridge();
262
+ if (bridge) {
263
+ bridge.sendMessage(rootDir, {
264
+ from: 'team-manager',
265
+ to: 'team-lead',
266
+ type: 'team_created',
267
+ template: templateName,
268
+ mode,
269
+ trace_id: traceId,
270
+ teammate_count: template.teammates.length,
271
+ });
272
+ }
273
+ } catch (e) {
274
+ // Non-critical
275
+ }
276
+
177
277
  return {
178
278
  ok: true,
179
279
  mode,
280
+ trace_id: traceId,
180
281
  template: templateName,
181
282
  lead: template.lead,
182
283
  teammates: template.teammates,
183
284
  quality_gates: template.quality_gates,
285
+ native_payload: nativePayload,
184
286
  };
185
287
  }
186
288
 
@@ -212,7 +314,9 @@ function getTeamStatus(rootDir) {
212
314
  }
213
315
 
214
316
  /**
215
- * Stop active team
317
+ * Stop active team.
318
+ * Uses atomic writes for session-state.json.
319
+ * Logs team_stopped event to bus.
216
320
  */
217
321
  function stopTeam(rootDir) {
218
322
  try {
@@ -238,7 +342,46 @@ function stopTeam(rootDir) {
238
342
 
239
343
  // Clear active team
240
344
  delete state.active_team;
241
- fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
345
+
346
+ // Write atomically
347
+ const fileLock = getFileLock();
348
+ if (fileLock) {
349
+ fileLock.atomicWriteJSON(sessionStatePath, state);
350
+ } else {
351
+ fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
352
+ }
353
+
354
+ // Log team_stopped event
355
+ try {
356
+ const bridge = getMessagingBridge();
357
+ if (bridge) {
358
+ bridge.sendMessage(rootDir, {
359
+ from: 'team-manager',
360
+ to: 'system',
361
+ type: 'team_stopped',
362
+ template: team.template,
363
+ mode: team.mode,
364
+ trace_id: team.trace_id,
365
+ duration_ms: duration,
366
+ tasks_completed: state.team_metrics ? state.team_metrics.tasks_completed : 0,
367
+ });
368
+ }
369
+ } catch (e) {
370
+ // Non-critical
371
+ }
372
+
373
+ // Aggregate and save team metrics by trace_id
374
+ try {
375
+ if (team.trace_id) {
376
+ const teamEvents = require('./lib/team-events');
377
+ const metrics = teamEvents.aggregateTeamMetrics(rootDir, team.trace_id);
378
+ if (metrics.ok) {
379
+ teamEvents.saveAggregatedMetrics(rootDir, metrics);
380
+ }
381
+ }
382
+ } catch (e) {
383
+ // Non-critical - metrics aggregation is best-effort
384
+ }
242
385
 
243
386
  return {
244
387
  ok: true,
@@ -314,6 +457,7 @@ module.exports = {
314
457
  getTeamStatus,
315
458
  stopTeam,
316
459
  getTeamsDir,
460
+ buildNativeTeamPayload,
317
461
  };
318
462
 
319
463
  // Run CLI if invoked directly
@@ -312,18 +312,19 @@ NEXT STEPS:
312
312
 
313
313
  ### If PASS
314
314
 
315
- Offer to mark story complete:
315
+ Offer to mark story complete with full context:
316
316
 
317
317
  ```xml
318
318
  <invoke name="AskUserQuestion">
319
319
  <parameter name="questions">[{
320
- "question": "Story US-0129 passed audit! What would you like to do?",
321
- "header": "Next Steps",
320
+ "question": "[STORY] passed audit: [test_count] tests passing, [ac_count]/[ac_total] AC verified. What next?",
321
+ "header": "Complete",
322
322
  "multiSelect": false,
323
323
  "options": [
324
- {"label": "Mark complete (Recommended)", "description": "Update status to done"},
325
- {"label": "View epic progress", "description": "Check EP-0022 status"},
326
- {"label": "Done", "description": "Exit without changes"}
324
+ {"label": "Mark [STORY] complete (Recommended)", "description": "Update status to done - all AC verified, tests passing"},
325
+ {"label": "Run tests once more before completing", "description": "Re-run test suite to double-check [test_count] tests"},
326
+ {"label": "View [EPIC] progress", "description": "Check epic completion: [completed]/[total] stories done ([percent]%)"},
327
+ {"label": "Done without marking complete", "description": "Audit passed but don't update status yet"}
327
328
  ]
328
329
  }]</parameter>
329
330
  </invoke>
@@ -336,18 +337,19 @@ If "Mark complete":
336
337
 
337
338
  ### If FAIL
338
339
 
339
- Offer to help fix issues:
340
+ Offer to help fix issues with specific context:
340
341
 
341
342
  ```xml
342
343
  <invoke name="AskUserQuestion">
343
344
  <parameter name="questions">[{
344
- "question": "Story US-0129 failed audit. What would you like to do?",
345
+ "question": "[STORY] failed audit: [fail_count] tests failing, [unverified] AC unverified. How to proceed?",
345
346
  "header": "Fix Issues",
346
347
  "multiSelect": false,
347
348
  "options": [
348
- {"label": "Fix failing tests", "description": "I'll help debug test failures"},
349
- {"label": "Review AC requirements", "description": "Check what's missing"},
350
- {"label": "Done", "description": "Exit and fix later"}
349
+ {"label": "Fix [fail_count] failing test(s) (Recommended)", "description": "Debug: [first_failing_test_name] and [fail_count-1] other(s)"},
350
+ {"label": "Implement [unverified] missing AC item(s)", "description": "AC not met: [first_unverified_ac_text]"},
351
+ {"label": "Re-run audit after manual fixes", "description": "I'll fix issues myself, then re-audit"},
352
+ {"label": "Done for now", "description": "Audit failed - [STORY] stays in_progress"}
351
353
  ]
352
354
  }]</parameter>
353
355
  </invoke>