agileflow 2.92.1 → 2.94.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/CHANGELOG.md +10 -0
- package/README.md +3 -3
- package/package.json +1 -1
- package/scripts/agileflow-statusline.sh +106 -0
- package/scripts/agileflow-welcome.js +54 -0
- package/scripts/document-repl.js +793 -0
- package/scripts/session-manager.js +230 -16
- package/scripts/spawn-parallel.js +53 -14
- package/src/core/agents/accessibility.md +19 -125
- package/src/core/agents/adr-writer.md +18 -1
- package/src/core/agents/analytics.md +19 -125
- package/src/core/agents/api.md +5 -130
- package/src/core/agents/ci.md +26 -131
- package/src/core/agents/compliance.md +21 -125
- package/src/core/agents/database.md +20 -125
- package/src/core/agents/datamigration.md +20 -125
- package/src/core/agents/design.md +19 -125
- package/src/core/agents/devops.md +12 -129
- package/src/core/agents/documentation.md +18 -1
- package/src/core/agents/epic-planner.md +31 -10
- package/src/core/agents/integrations.md +19 -125
- package/src/core/agents/mobile.md +19 -125
- package/src/core/agents/monitoring.md +19 -125
- package/src/core/agents/performance.md +19 -125
- package/src/core/agents/product.md +18 -1
- package/src/core/agents/qa.md +21 -125
- package/src/core/agents/readme-updater.md +18 -1
- package/src/core/agents/refactor.md +19 -125
- package/src/core/agents/research.md +3 -1
- package/src/core/agents/rlm-subcore.md +202 -0
- package/src/core/agents/security.md +7 -125
- package/src/core/agents/testing.md +20 -125
- package/src/core/agents/ui.md +14 -135
- package/src/core/commands/adr/list.md +20 -0
- package/src/core/commands/adr/update.md +24 -1
- package/src/core/commands/adr/view.md +23 -1
- package/src/core/commands/adr.md +2 -2
- package/src/core/commands/agent.md +11 -1
- package/src/core/commands/assign.md +15 -6
- package/src/core/commands/auto.md +11 -1
- package/src/core/commands/babysit.md +15 -4
- package/src/core/commands/baseline.md +11 -1
- package/src/core/commands/batch.md +11 -1
- package/src/core/commands/blockers.md +11 -1
- package/src/core/commands/board.md +11 -1
- package/src/core/commands/changelog.md +11 -0
- package/src/core/commands/choose.md +16 -1
- package/src/core/commands/ci.md +11 -1
- package/src/core/commands/configure.md +73 -2
- package/src/core/commands/context/export.md +8 -0
- package/src/core/commands/context/full.md +8 -0
- package/src/core/commands/context/note.md +8 -0
- package/src/core/commands/debt.md +11 -0
- package/src/core/commands/deploy.md +10 -0
- package/src/core/commands/deps.md +11 -1
- package/src/core/commands/diagnose.md +10 -0
- package/src/core/commands/docs.md +12 -2
- package/src/core/commands/epic/list.md +20 -0
- package/src/core/commands/epic/view.md +25 -0
- package/src/core/commands/epic.md +5 -6
- package/src/core/commands/feedback.md +11 -0
- package/src/core/commands/handoff.md +12 -2
- package/src/core/commands/help.md +10 -0
- package/src/core/commands/ideate.md +10 -0
- package/src/core/commands/impact.md +11 -1
- package/src/core/commands/metrics.md +11 -1
- package/src/core/commands/multi-expert.md +11 -1
- package/src/core/commands/packages.md +11 -0
- package/src/core/commands/pr.md +10 -0
- package/src/core/commands/readme-sync.md +10 -5
- package/src/core/commands/research/analyze.md +60 -3
- package/src/core/commands/research/ask.md +9 -1
- package/src/core/commands/research/import.md +8 -0
- package/src/core/commands/research/list.md +8 -0
- package/src/core/commands/research/synthesize.md +9 -1
- package/src/core/commands/research/view.md +8 -0
- package/src/core/commands/retro.md +12 -2
- package/src/core/commands/review.md +11 -1
- package/src/core/commands/rlm.md +363 -0
- package/src/core/commands/roadmap/analyze.md +1 -1
- package/src/core/commands/rpi.md +9 -1
- package/src/core/commands/session/cleanup.md +250 -0
- package/src/core/commands/session/end.md +10 -0
- package/src/core/commands/session/history.md +11 -1
- package/src/core/commands/session/init.md +10 -0
- package/src/core/commands/session/new.md +113 -13
- package/src/core/commands/session/resume.md +10 -0
- package/src/core/commands/session/spawn.md +8 -0
- package/src/core/commands/session/status.md +10 -0
- package/src/core/commands/skill/create.md +1 -1
- package/src/core/commands/skill/delete.md +11 -1
- package/src/core/commands/skill/edit.md +11 -1
- package/src/core/commands/skill/test.md +11 -1
- package/src/core/commands/skill/upgrade.md +11 -1
- package/src/core/commands/sprint.md +14 -3
- package/src/core/commands/status.md +15 -6
- package/src/core/commands/story/list.md +23 -0
- package/src/core/commands/story/view.md +24 -0
- package/src/core/commands/story.md +4 -5
- package/src/core/commands/template.md +10 -0
- package/src/core/commands/tests.md +10 -0
- package/src/core/commands/update.md +10 -0
- package/src/core/commands/validate-expertise.md +10 -1
- package/src/core/commands/velocity.md +11 -1
- package/src/core/commands/verify.md +13 -1
- package/src/core/commands/whats-new.md +8 -0
- package/src/core/commands/workflow.md +16 -1
- package/src/core/templates/agent-coordination-pattern.md +38 -0
- package/src/core/templates/agileflow-metadata.json +25 -0
- package/src/core/templates/preserve-rules-common.md +107 -0
- package/src/core/templates/preserve-rules.json +42 -0
- package/src/core/templates/proactive-action-spec.md +29 -0
- package/src/core/templates/quality-gate-priorities.md +34 -0
- package/src/core/templates/session-harness-protocol.md +128 -0
- package/tools/cli/lib/content-injector.js +338 -0
|
@@ -231,6 +231,161 @@ async function cleanupStaleLocksAsync(registry, options = {}) {
|
|
|
231
231
|
return { count: cleanedSessions.length, sessions: cleanedSessions };
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Get detailed file information for a session's changes
|
|
236
|
+
* @param {string} sessionPath - Path to session worktree
|
|
237
|
+
* @param {string[]} changes - Array of git status lines
|
|
238
|
+
* @returns {Object[]} Array of file details with analysis
|
|
239
|
+
*/
|
|
240
|
+
function getFileDetails(sessionPath, changes) {
|
|
241
|
+
return changes.map(change => {
|
|
242
|
+
const status = change.substring(0, 2).trim();
|
|
243
|
+
const file = change.substring(3);
|
|
244
|
+
|
|
245
|
+
const detail = { status, file, trivial: false, existsInMain: false, diffLines: 0 };
|
|
246
|
+
|
|
247
|
+
// For modified files, get diff stats
|
|
248
|
+
if (status === 'M') {
|
|
249
|
+
try {
|
|
250
|
+
const diffStat = spawnSync('git', ['diff', '--numstat', file], {
|
|
251
|
+
cwd: sessionPath,
|
|
252
|
+
encoding: 'utf8',
|
|
253
|
+
timeout: 3000,
|
|
254
|
+
});
|
|
255
|
+
if (diffStat.stdout) {
|
|
256
|
+
const parts = diffStat.stdout.trim().split('\t');
|
|
257
|
+
const added = parseInt(parts[0], 10) || 0;
|
|
258
|
+
const removed = parseInt(parts[1], 10) || 0;
|
|
259
|
+
detail.diffLines = added + removed;
|
|
260
|
+
// Trivial if only 1-2 lines changed (likely whitespace)
|
|
261
|
+
detail.trivial = detail.diffLines <= 2;
|
|
262
|
+
}
|
|
263
|
+
} catch (e) {
|
|
264
|
+
// Can't get diff, assume not trivial
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// For untracked files, check if exists in main
|
|
269
|
+
if (status === '??') {
|
|
270
|
+
detail.existsInMain = fs.existsSync(path.join(ROOT, file));
|
|
271
|
+
// Trivial if it's a duplicate
|
|
272
|
+
detail.trivial = detail.existsInMain;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Config/cache files are trivial
|
|
276
|
+
if (file.includes('.claude/') || file.includes('.agileflow/cache')) {
|
|
277
|
+
detail.trivial = true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return detail;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get health status for all sessions
|
|
286
|
+
* Detects: stale sessions, uncommitted changes, orphaned entries
|
|
287
|
+
* @param {Object} options - { staleDays: 7, detailed: false }
|
|
288
|
+
* @returns {Object} Health report
|
|
289
|
+
*/
|
|
290
|
+
function getSessionsHealth(options = {}) {
|
|
291
|
+
const { staleDays = 7, detailed = false } = options;
|
|
292
|
+
const registry = loadRegistry();
|
|
293
|
+
const now = Date.now();
|
|
294
|
+
const staleThreshold = staleDays * 24 * 60 * 60 * 1000;
|
|
295
|
+
|
|
296
|
+
const health = {
|
|
297
|
+
stale: [], // Sessions with no activity > staleDays
|
|
298
|
+
uncommitted: [], // Sessions with uncommitted git changes
|
|
299
|
+
orphanedRegistry: [], // Registry entries where path doesn't exist
|
|
300
|
+
orphanedWorktrees: [], // Worktrees not in registry
|
|
301
|
+
healthy: 0,
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// Check each registered session
|
|
305
|
+
for (const [id, session] of Object.entries(registry.sessions)) {
|
|
306
|
+
if (session.is_main) continue; // Skip main session
|
|
307
|
+
|
|
308
|
+
const age = now - new Date(session.last_active).getTime();
|
|
309
|
+
const pathExists = fs.existsSync(session.path);
|
|
310
|
+
|
|
311
|
+
// Check for orphaned registry entry (path missing)
|
|
312
|
+
if (!pathExists) {
|
|
313
|
+
health.orphanedRegistry.push({ id, ...session, reason: 'path_missing' });
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Check for stale session
|
|
318
|
+
if (age > staleThreshold) {
|
|
319
|
+
health.stale.push({
|
|
320
|
+
id,
|
|
321
|
+
...session,
|
|
322
|
+
ageDays: Math.floor(age / (24 * 60 * 60 * 1000)),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Check for uncommitted changes
|
|
327
|
+
try {
|
|
328
|
+
const result = spawnSync('git', ['status', '--porcelain'], {
|
|
329
|
+
cwd: session.path,
|
|
330
|
+
encoding: 'utf8',
|
|
331
|
+
timeout: 5000,
|
|
332
|
+
});
|
|
333
|
+
if (result.stdout && result.stdout.trim()) {
|
|
334
|
+
// Don't use trim() on the whole string - it removes leading space from first status
|
|
335
|
+
// Split by newline and filter empty lines instead
|
|
336
|
+
const changes = result.stdout.split('\n').filter(line => line.length > 0);
|
|
337
|
+
const sessionData = {
|
|
338
|
+
id,
|
|
339
|
+
...session,
|
|
340
|
+
changeCount: changes.length,
|
|
341
|
+
changes: detailed ? changes : changes.slice(0, 5), // All or first 5
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// Add detailed file analysis if requested
|
|
345
|
+
if (detailed) {
|
|
346
|
+
sessionData.fileDetails = getFileDetails(session.path, changes);
|
|
347
|
+
// Calculate if session is safe to delete (all changes trivial)
|
|
348
|
+
sessionData.allTrivial = sessionData.fileDetails.every(f => f.trivial);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
health.uncommitted.push(sessionData);
|
|
352
|
+
} else {
|
|
353
|
+
health.healthy++;
|
|
354
|
+
}
|
|
355
|
+
} catch (e) {
|
|
356
|
+
// Can't check, skip
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Check for orphaned worktrees (directories not in registry)
|
|
361
|
+
try {
|
|
362
|
+
const worktreeList = spawnSync('git', ['worktree', 'list', '--porcelain'], {
|
|
363
|
+
encoding: 'utf8',
|
|
364
|
+
});
|
|
365
|
+
if (worktreeList.stdout) {
|
|
366
|
+
const worktrees = worktreeList.stdout
|
|
367
|
+
.split('\n')
|
|
368
|
+
.filter(line => line.startsWith('worktree '))
|
|
369
|
+
.map(line => line.replace('worktree ', ''));
|
|
370
|
+
|
|
371
|
+
const mainPath = ROOT;
|
|
372
|
+
for (const wtPath of worktrees) {
|
|
373
|
+
const inRegistry = Object.values(registry.sessions).some(s => s.path === wtPath);
|
|
374
|
+
if (!inRegistry && wtPath !== mainPath) {
|
|
375
|
+
// Check if it's an AgileFlow worktree (has .agileflow folder)
|
|
376
|
+
if (fs.existsSync(path.join(wtPath, '.agileflow'))) {
|
|
377
|
+
health.orphanedWorktrees.push({ path: wtPath });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} catch (e) {
|
|
383
|
+
// Can't list worktrees, skip
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return health;
|
|
387
|
+
}
|
|
388
|
+
|
|
234
389
|
// Git command cache (10 second TTL to avoid stale data)
|
|
235
390
|
const gitCache = {
|
|
236
391
|
data: new Map(),
|
|
@@ -432,7 +587,11 @@ function progressIndicator(message) {
|
|
|
432
587
|
* @param {number} timeoutMs - Timeout in milliseconds
|
|
433
588
|
* @returns {Promise<{stdout: string, stderr: string}>}
|
|
434
589
|
*/
|
|
435
|
-
function createWorktreeWithTimeout(
|
|
590
|
+
function createWorktreeWithTimeout(
|
|
591
|
+
worktreePath,
|
|
592
|
+
branchName,
|
|
593
|
+
timeoutMs = DEFAULT_WORKTREE_TIMEOUT_MS
|
|
594
|
+
) {
|
|
436
595
|
return new Promise((resolve, reject) => {
|
|
437
596
|
let stdout = '';
|
|
438
597
|
let stderr = '';
|
|
@@ -455,15 +614,15 @@ function createWorktreeWithTimeout(worktreePath, branchName, timeoutMs = DEFAULT
|
|
|
455
614
|
}, 1000);
|
|
456
615
|
}, timeoutMs);
|
|
457
616
|
|
|
458
|
-
proc.stdout.on('data',
|
|
617
|
+
proc.stdout.on('data', data => {
|
|
459
618
|
stdout += data.toString();
|
|
460
619
|
});
|
|
461
620
|
|
|
462
|
-
proc.stderr.on('data',
|
|
621
|
+
proc.stderr.on('data', data => {
|
|
463
622
|
stderr += data.toString();
|
|
464
623
|
});
|
|
465
624
|
|
|
466
|
-
proc.on('error',
|
|
625
|
+
proc.on('error', err => {
|
|
467
626
|
clearTimeout(timer);
|
|
468
627
|
reject(new Error(`Failed to spawn git: ${err.message}`));
|
|
469
628
|
});
|
|
@@ -472,7 +631,11 @@ function createWorktreeWithTimeout(worktreePath, branchName, timeoutMs = DEFAULT
|
|
|
472
631
|
clearTimeout(timer);
|
|
473
632
|
|
|
474
633
|
if (timedOut) {
|
|
475
|
-
reject(
|
|
634
|
+
reject(
|
|
635
|
+
new Error(
|
|
636
|
+
`Worktree creation timed out after ${timeoutMs / 1000}s. Try increasing timeout or check disk space.`
|
|
637
|
+
)
|
|
638
|
+
);
|
|
476
639
|
return;
|
|
477
640
|
}
|
|
478
641
|
|
|
@@ -601,7 +764,9 @@ async function createSession(options = {}) {
|
|
|
601
764
|
const timeoutMs = options.timeout || DEFAULT_WORKTREE_TIMEOUT_MS;
|
|
602
765
|
|
|
603
766
|
// Create worktree with timeout and progress feedback
|
|
604
|
-
const stopProgress = progressIndicator(
|
|
767
|
+
const stopProgress = progressIndicator(
|
|
768
|
+
'Creating worktree (this may take a while for large repos)'
|
|
769
|
+
);
|
|
605
770
|
try {
|
|
606
771
|
await createWorktreeWithTimeout(worktreePath, branchName, timeoutMs);
|
|
607
772
|
stopProgress();
|
|
@@ -633,12 +798,11 @@ async function createSession(options = {}) {
|
|
|
633
798
|
}
|
|
634
799
|
}
|
|
635
800
|
|
|
636
|
-
// Copy Claude Code
|
|
801
|
+
// Copy Claude Code and AgileFlow config folders (gitignored contents won't copy with worktree)
|
|
637
802
|
// Note: The folder may exist with some tracked files, but gitignored subfolders (commands/, agents/) won't be there
|
|
638
|
-
|
|
639
|
-
const configFolders = ['.claude', '.agileflow', 'docs'];
|
|
803
|
+
const configFoldersToCopy = ['.claude', '.agileflow'];
|
|
640
804
|
const copiedFolders = [];
|
|
641
|
-
for (const folder of
|
|
805
|
+
for (const folder of configFoldersToCopy) {
|
|
642
806
|
const src = path.join(ROOT, folder);
|
|
643
807
|
const dest = path.join(worktreePath, folder);
|
|
644
808
|
if (fs.existsSync(src)) {
|
|
@@ -653,6 +817,37 @@ async function createSession(options = {}) {
|
|
|
653
817
|
}
|
|
654
818
|
}
|
|
655
819
|
|
|
820
|
+
// Symlink docs/ to main project docs (shared state: status.json, session-state.json, bus/)
|
|
821
|
+
// This enables story claiming, status bus, and session coordination across worktrees
|
|
822
|
+
const foldersToSymlink = ['docs'];
|
|
823
|
+
const symlinkedFolders = [];
|
|
824
|
+
for (const folder of foldersToSymlink) {
|
|
825
|
+
const src = path.join(ROOT, folder);
|
|
826
|
+
const dest = path.join(worktreePath, folder);
|
|
827
|
+
if (fs.existsSync(src)) {
|
|
828
|
+
try {
|
|
829
|
+
// Remove if exists (worktree may have empty/partial tracked folder)
|
|
830
|
+
if (fs.existsSync(dest)) {
|
|
831
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Create relative symlink (works across project moves)
|
|
835
|
+
const relPath = path.relative(worktreePath, src);
|
|
836
|
+
fs.symlinkSync(relPath, dest, 'dir');
|
|
837
|
+
symlinkedFolders.push(folder);
|
|
838
|
+
} catch (e) {
|
|
839
|
+
// Fallback to copy if symlink fails (e.g., Windows without dev mode)
|
|
840
|
+
console.warn(`Warning: Could not symlink ${folder}, copying instead: ${e.message}`);
|
|
841
|
+
try {
|
|
842
|
+
fs.cpSync(src, dest, { recursive: true, force: true });
|
|
843
|
+
copiedFolders.push(folder);
|
|
844
|
+
} catch (copyErr) {
|
|
845
|
+
console.warn(`Warning: Could not copy ${folder}: ${copyErr.message}`);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
656
851
|
// Register session - worktree sessions are always parallel threads
|
|
657
852
|
registry.next_id++;
|
|
658
853
|
registry.sessions[sessionId] = {
|
|
@@ -677,6 +872,7 @@ async function createSession(options = {}) {
|
|
|
677
872
|
command: `cd "${worktreePath}" && claude`,
|
|
678
873
|
envFilesCopied: copiedEnvFiles,
|
|
679
874
|
foldersCopied: copiedFolders,
|
|
875
|
+
foldersSymlinked: symlinkedFolders,
|
|
680
876
|
};
|
|
681
877
|
}
|
|
682
878
|
|
|
@@ -1300,16 +1496,23 @@ function main() {
|
|
|
1300
1496
|
if (options.timeout) {
|
|
1301
1497
|
options.timeout = parseInt(options.timeout, 10);
|
|
1302
1498
|
if (isNaN(options.timeout) || options.timeout < 1000) {
|
|
1303
|
-
console.log(
|
|
1499
|
+
console.log(
|
|
1500
|
+
JSON.stringify({
|
|
1501
|
+
success: false,
|
|
1502
|
+
error: 'Timeout must be a number >= 1000 (milliseconds)',
|
|
1503
|
+
})
|
|
1504
|
+
);
|
|
1304
1505
|
return;
|
|
1305
1506
|
}
|
|
1306
1507
|
}
|
|
1307
1508
|
// Handle async createSession
|
|
1308
|
-
createSession(options)
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1509
|
+
createSession(options)
|
|
1510
|
+
.then(result => {
|
|
1511
|
+
console.log(JSON.stringify(result));
|
|
1512
|
+
})
|
|
1513
|
+
.catch(err => {
|
|
1514
|
+
console.log(JSON.stringify({ success: false, error: err.message }));
|
|
1515
|
+
});
|
|
1313
1516
|
break;
|
|
1314
1517
|
}
|
|
1315
1518
|
|
|
@@ -1361,6 +1564,17 @@ function main() {
|
|
|
1361
1564
|
break;
|
|
1362
1565
|
}
|
|
1363
1566
|
|
|
1567
|
+
case 'health': {
|
|
1568
|
+
// Get health status for all sessions
|
|
1569
|
+
// Usage: health [staleDays] [--detailed]
|
|
1570
|
+
const staleDaysArg = args.find(a => /^\d+$/.test(a));
|
|
1571
|
+
const staleDays = staleDaysArg ? parseInt(staleDaysArg, 10) : 7;
|
|
1572
|
+
const detailed = args.includes('--detailed');
|
|
1573
|
+
const health = getSessionsHealth({ staleDays, detailed });
|
|
1574
|
+
console.log(JSON.stringify(health));
|
|
1575
|
+
break;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1364
1578
|
case 'get': {
|
|
1365
1579
|
const sessionId = args[1];
|
|
1366
1580
|
if (!sessionId) {
|
|
@@ -64,16 +64,30 @@ function hasScreen() {
|
|
|
64
64
|
* Build the Claude command for a session
|
|
65
65
|
*/
|
|
66
66
|
function buildClaudeCommand(sessionPath, options = {}) {
|
|
67
|
-
const {
|
|
67
|
+
const {
|
|
68
|
+
init = false,
|
|
69
|
+
dangerous = false,
|
|
70
|
+
prompt = null,
|
|
71
|
+
claudeArgs = null,
|
|
72
|
+
noClaude = false,
|
|
73
|
+
} = options;
|
|
68
74
|
const parts = [`cd "${sessionPath}"`];
|
|
69
75
|
|
|
70
76
|
if (init) {
|
|
71
77
|
parts.push('claude init --yes 2>/dev/null || true');
|
|
72
78
|
}
|
|
73
79
|
|
|
80
|
+
// If noClaude is true, just return cd command (no claude startup)
|
|
81
|
+
if (noClaude) {
|
|
82
|
+
return parts.join(' && ');
|
|
83
|
+
}
|
|
84
|
+
|
|
74
85
|
let claudeCmd = 'claude';
|
|
75
86
|
if (dangerous) {
|
|
76
|
-
claudeCmd = 'claude --
|
|
87
|
+
claudeCmd = 'claude --dangerously-skip-permissions';
|
|
88
|
+
} else if (claudeArgs) {
|
|
89
|
+
// Custom claude arguments (e.g., --permission-mode acceptEdits)
|
|
90
|
+
claudeCmd = `claude ${claudeArgs}`;
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
if (prompt) {
|
|
@@ -215,7 +229,7 @@ function getReadyStoriesFromEpic(epicId) {
|
|
|
215
229
|
/**
|
|
216
230
|
* Main spawn command
|
|
217
231
|
*/
|
|
218
|
-
function spawn(args) {
|
|
232
|
+
async function spawn(args) {
|
|
219
233
|
const count = args.count ? parseInt(args.count, 10) : null;
|
|
220
234
|
const branches = args.branches ? args.branches.split(',').map(b => b.trim()) : null;
|
|
221
235
|
const fromEpic = args['from-epic'] || args.fromEpic;
|
|
@@ -223,6 +237,8 @@ function spawn(args) {
|
|
|
223
237
|
const init = args.init || false;
|
|
224
238
|
const dangerous = args.dangerous || false;
|
|
225
239
|
const prompt = args.prompt || null;
|
|
240
|
+
const claudeArgs = args['claude-args'] || args.claudeArgs || null;
|
|
241
|
+
const noClaude = args['no-claude'] || args.noClaude || false;
|
|
226
242
|
|
|
227
243
|
// Determine what to create
|
|
228
244
|
let sessionsToCreate = [];
|
|
@@ -276,7 +292,7 @@ function spawn(args) {
|
|
|
276
292
|
|
|
277
293
|
const createdSessions = [];
|
|
278
294
|
for (const sessionSpec of sessionsToCreate) {
|
|
279
|
-
const result = sessionManager.createSession({
|
|
295
|
+
const result = await sessionManager.createSession({
|
|
280
296
|
nickname: sessionSpec.nickname,
|
|
281
297
|
branch: sessionSpec.branch,
|
|
282
298
|
});
|
|
@@ -311,10 +327,16 @@ function spawn(args) {
|
|
|
311
327
|
// Spawn in tmux or output commands
|
|
312
328
|
if (noTmux) {
|
|
313
329
|
// User explicitly requested manual mode
|
|
314
|
-
outputCommands(createdSessions, { init, dangerous, prompt });
|
|
330
|
+
outputCommands(createdSessions, { init, dangerous, prompt, claudeArgs, noClaude });
|
|
315
331
|
} else if (hasTmux()) {
|
|
316
332
|
// Tmux available - use it
|
|
317
|
-
const tmuxResult = spawnInTmux(createdSessions, {
|
|
333
|
+
const tmuxResult = spawnInTmux(createdSessions, {
|
|
334
|
+
init,
|
|
335
|
+
dangerous,
|
|
336
|
+
prompt,
|
|
337
|
+
claudeArgs,
|
|
338
|
+
noClaude,
|
|
339
|
+
});
|
|
318
340
|
|
|
319
341
|
if (tmuxResult.success) {
|
|
320
342
|
console.log(success(`\n✅ Tmux session created: ${tmuxResult.sessionName}`));
|
|
@@ -326,7 +348,7 @@ function spawn(args) {
|
|
|
326
348
|
console.log('');
|
|
327
349
|
} else {
|
|
328
350
|
console.error(error(`Failed to create tmux session: ${tmuxResult.error}`));
|
|
329
|
-
outputCommands(createdSessions, { init, dangerous, prompt });
|
|
351
|
+
outputCommands(createdSessions, { init, dangerous, prompt, claudeArgs, noClaude });
|
|
330
352
|
}
|
|
331
353
|
} else {
|
|
332
354
|
// Tmux NOT available - require it or use --no-tmux
|
|
@@ -392,9 +414,12 @@ function list() {
|
|
|
392
414
|
/**
|
|
393
415
|
* Add a new window to an existing tmux session
|
|
394
416
|
*/
|
|
395
|
-
function addWindow(args) {
|
|
417
|
+
async function addWindow(args) {
|
|
396
418
|
const nickname = args.nickname || args.name || null;
|
|
397
419
|
const branch = args.branch || null;
|
|
420
|
+
const dangerous = args.dangerous || false;
|
|
421
|
+
const claudeArgs = args['claude-args'] || args.claudeArgs || null;
|
|
422
|
+
const noClaude = args['no-claude'] || args.noClaude || false;
|
|
398
423
|
|
|
399
424
|
// Check if we're inside a tmux session
|
|
400
425
|
const tmuxEnv = process.env.TMUX;
|
|
@@ -427,7 +452,7 @@ function addWindow(args) {
|
|
|
427
452
|
branch: branch || `parallel-${Date.now()}`,
|
|
428
453
|
};
|
|
429
454
|
|
|
430
|
-
const result = sessionManager.createSession({
|
|
455
|
+
const result = await sessionManager.createSession({
|
|
431
456
|
nickname: sessionSpec.nickname,
|
|
432
457
|
branch: sessionSpec.branch,
|
|
433
458
|
});
|
|
@@ -438,7 +463,7 @@ function addWindow(args) {
|
|
|
438
463
|
}
|
|
439
464
|
|
|
440
465
|
const windowName = sessionSpec.nickname;
|
|
441
|
-
const cmd = buildClaudeCommand(result.path, {});
|
|
466
|
+
const cmd = buildClaudeCommand(result.path, { dangerous, claudeArgs, noClaude });
|
|
442
467
|
|
|
443
468
|
// Create new window in current tmux session
|
|
444
469
|
const newWindowResult = spawnSync(
|
|
@@ -551,7 +576,9 @@ ${c.cyan}SPAWN OPTIONS:${c.reset}
|
|
|
551
576
|
--branches "a,b,c" Create worktrees for specific branch names
|
|
552
577
|
--from-epic EP-XXX Create worktrees for ready stories in epic
|
|
553
578
|
--init Run 'claude init' in each worktree
|
|
554
|
-
--dangerous Use --
|
|
579
|
+
--dangerous Use --dangerously-skip-permissions
|
|
580
|
+
--claude-args "..." Custom arguments for claude command
|
|
581
|
+
--no-claude Create worktree but don't start claude
|
|
555
582
|
--no-tmux Output commands without spawning in tmux
|
|
556
583
|
--prompt "TEXT" Initial prompt to send to each Claude instance
|
|
557
584
|
|
|
@@ -575,6 +602,9 @@ ${c.cyan}ADD-WINDOW OPTIONS:${c.reset}
|
|
|
575
602
|
--name NAME Name for the new session/window
|
|
576
603
|
--nickname NAME Alias for --name
|
|
577
604
|
--branch BRANCH Use specific branch name
|
|
605
|
+
--dangerous Use --dangerously-skip-permissions
|
|
606
|
+
--claude-args "..." Custom arguments for claude command
|
|
607
|
+
--no-claude Create worktree but don't start claude
|
|
578
608
|
|
|
579
609
|
${c.cyan}ADD-WINDOW EXAMPLES:${c.reset}
|
|
580
610
|
${dim('# Add window with auto-generated name (when in tmux)')}
|
|
@@ -582,6 +612,15 @@ ${c.cyan}ADD-WINDOW EXAMPLES:${c.reset}
|
|
|
582
612
|
|
|
583
613
|
${dim('# Add named window')}
|
|
584
614
|
node scripts/spawn-parallel.js add-window --name auth
|
|
615
|
+
|
|
616
|
+
${dim('# Add window without starting claude')}
|
|
617
|
+
node scripts/spawn-parallel.js add-window --name research --no-claude
|
|
618
|
+
|
|
619
|
+
${dim('# Add window with skip permissions')}
|
|
620
|
+
node scripts/spawn-parallel.js add-window --name trusted --dangerous
|
|
621
|
+
|
|
622
|
+
${dim('# Add window with custom claude args')}
|
|
623
|
+
node scripts/spawn-parallel.js add-window --name safe --claude-args "--permission-mode acceptEdits"
|
|
585
624
|
`);
|
|
586
625
|
}
|
|
587
626
|
|
|
@@ -616,16 +655,16 @@ function parseArgs(argv) {
|
|
|
616
655
|
/**
|
|
617
656
|
* Main entry point
|
|
618
657
|
*/
|
|
619
|
-
function main() {
|
|
658
|
+
async function main() {
|
|
620
659
|
const { command, args } = parseArgs(process.argv.slice(2));
|
|
621
660
|
|
|
622
661
|
switch (command) {
|
|
623
662
|
case 'spawn':
|
|
624
|
-
spawn(args);
|
|
663
|
+
await spawn(args);
|
|
625
664
|
break;
|
|
626
665
|
case 'add-window':
|
|
627
666
|
case 'add':
|
|
628
|
-
addWindow(args);
|
|
667
|
+
await addWindow(args);
|
|
629
668
|
break;
|
|
630
669
|
case 'list':
|
|
631
670
|
list();
|
|
@@ -189,131 +189,8 @@ BOUNDARIES
|
|
|
189
189
|
- Always prioritize real user needs over technical convenience
|
|
190
190
|
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
<!-- {{SESSION_HARNESS}} -->
|
|
193
193
|
|
|
194
|
-
**CRITICAL**: Session Harness System prevents agents from breaking functionality, claiming work is done when tests fail, or losing context between sessions.
|
|
195
|
-
|
|
196
|
-
**PRE-IMPLEMENTATION VERIFICATION**
|
|
197
|
-
|
|
198
|
-
Before starting work on ANY story:
|
|
199
|
-
|
|
200
|
-
1. **Check Session Harness**:
|
|
201
|
-
- Look for `docs/00-meta/environment.json`
|
|
202
|
-
- If exists → Session harness is active ✅
|
|
203
|
-
- If missing → Suggest `/agileflow:session:init` to user
|
|
204
|
-
|
|
205
|
-
2. **Test Baseline Check**:
|
|
206
|
-
- Read `test_status` from story in `docs/09-agents/status.json`
|
|
207
|
-
- If `"passing"` → Proceed with implementation ✅
|
|
208
|
-
- If `"failing"` → STOP. Cannot start new work with failing baseline ⚠️
|
|
209
|
-
- If `"not_run"` → Run `/agileflow:verify` first to establish baseline
|
|
210
|
-
- If `"skipped"` → Check why tests are skipped, document override decision
|
|
211
|
-
|
|
212
|
-
3. **Environment Verification** (if session harness active):
|
|
213
|
-
- Run `/agileflow:session:resume` to verify environment and load context
|
|
214
|
-
- Check for regressions (tests were passing, now failing)
|
|
215
|
-
- If regression detected → Fix before proceeding with new story
|
|
216
|
-
|
|
217
|
-
**DURING IMPLEMENTATION**
|
|
218
|
-
|
|
219
|
-
1. **Incremental Testing**:
|
|
220
|
-
- Run tests frequently during development (not just at end)
|
|
221
|
-
- Fix test failures immediately (don't accumulate debt)
|
|
222
|
-
- Use `/agileflow:verify US-XXXX` to check specific story tests
|
|
223
|
-
|
|
224
|
-
2. **Real-time Status Updates**:
|
|
225
|
-
- Update `test_status` in status.json as tests are written/fixed
|
|
226
|
-
- Append bus messages when tests pass milestone checkpoints
|
|
227
|
-
|
|
228
|
-
**POST-IMPLEMENTATION VERIFICATION**
|
|
229
|
-
|
|
230
|
-
After completing ANY changes:
|
|
231
|
-
|
|
232
|
-
1. **Run Full Test Suite**:
|
|
233
|
-
- Execute `/agileflow:verify US-XXXX` to run tests for the story
|
|
234
|
-
- Check exit code (0 = success required for completion)
|
|
235
|
-
- Review test output for warnings or flaky tests
|
|
236
|
-
|
|
237
|
-
2. **Update Test Status**:
|
|
238
|
-
- `/agileflow:verify` automatically updates `test_status` in status.json
|
|
239
|
-
- Verify the update was successful
|
|
240
|
-
- Expected: `test_status: "passing"` with test results metadata
|
|
241
|
-
|
|
242
|
-
3. **Regression Check**:
|
|
243
|
-
- Compare test results to baseline (initial test status)
|
|
244
|
-
- If new failures introduced → Fix before marking complete
|
|
245
|
-
- If test count decreased → Investigate deleted tests
|
|
246
|
-
|
|
247
|
-
4. **Story Completion Requirements**:
|
|
248
|
-
- Story can ONLY be marked `"in-review"` if `test_status: "passing"` ✅
|
|
249
|
-
- If tests failing → Story remains `"in-progress"` until fixed ⚠️
|
|
250
|
-
- No exceptions unless documented override (see below)
|
|
251
|
-
|
|
252
|
-
**OVERRIDE PROTOCOL** (Use with extreme caution)
|
|
253
|
-
|
|
254
|
-
If tests are failing but you need to proceed:
|
|
255
|
-
|
|
256
|
-
1. **Document Override Decision**:
|
|
257
|
-
- Append bus message with full explanation (include agent ID, story ID, reason, tracking issue)
|
|
258
|
-
|
|
259
|
-
2. **Update Story Dev Agent Record**:
|
|
260
|
-
- Add note to "Issues Encountered" section explaining override
|
|
261
|
-
- Link to tracking issue for the failing test
|
|
262
|
-
- Document risk and mitigation plan
|
|
263
|
-
|
|
264
|
-
3. **Create Follow-up Story**:
|
|
265
|
-
- If test failure is real but out of scope → Create new story
|
|
266
|
-
- Link dependency in status.json
|
|
267
|
-
- Notify user of the override and follow-up story
|
|
268
|
-
|
|
269
|
-
**BASELINE MANAGEMENT**
|
|
270
|
-
|
|
271
|
-
After completing major milestones (epic complete, sprint end):
|
|
272
|
-
|
|
273
|
-
1. **Establish Baseline**:
|
|
274
|
-
- Suggest `/agileflow:baseline "Epic EP-XXXX complete"` to user
|
|
275
|
-
- Requires: All tests passing, git working tree clean
|
|
276
|
-
- Creates git tag + metadata for reset point
|
|
277
|
-
|
|
278
|
-
2. **Baseline Benefits**:
|
|
279
|
-
- Known-good state to reset to if needed
|
|
280
|
-
- Regression detection reference point
|
|
281
|
-
- Deployment readiness checkpoint
|
|
282
|
-
- Sprint/epic completion marker
|
|
283
|
-
|
|
284
|
-
**INTEGRATION WITH WORKFLOW**
|
|
285
|
-
|
|
286
|
-
The verification protocol integrates into the standard workflow:
|
|
287
|
-
|
|
288
|
-
1. **Before creating feature branch**: Run pre-implementation verification
|
|
289
|
-
2. **Before marking in-review**: Run post-implementation verification
|
|
290
|
-
3. **After merge**: Verify baseline is still passing
|
|
291
|
-
|
|
292
|
-
**ERROR HANDLING**
|
|
293
|
-
|
|
294
|
-
If `/agileflow:verify` fails:
|
|
295
|
-
- Read error output carefully
|
|
296
|
-
- Check if test command is configured in `docs/00-meta/environment.json`
|
|
297
|
-
- Verify test dependencies are installed
|
|
298
|
-
- If project has no tests → Suggest `/agileflow:session:init` to set up testing
|
|
299
|
-
- If tests are misconfigured → Coordinate with AG-CI
|
|
300
|
-
|
|
301
|
-
**SESSION RESUME PROTOCOL**
|
|
302
|
-
|
|
303
|
-
When resuming work after context loss:
|
|
304
|
-
|
|
305
|
-
1. **Run Resume Command**: `/agileflow:session:resume` loads context automatically
|
|
306
|
-
2. **Check Session State**: Review `docs/09-agents/session-state.json`
|
|
307
|
-
3. **Verify Test Status**: Ensure no regressions occurred
|
|
308
|
-
4. **Load Previous Insights**: Check Dev Agent Record from previous stories
|
|
309
|
-
|
|
310
|
-
**KEY PRINCIPLES**
|
|
311
|
-
|
|
312
|
-
- **Tests are the contract**: Passing tests = feature works as specified
|
|
313
|
-
- **Fail fast**: Catch regressions immediately, not at PR review
|
|
314
|
-
- **Context preservation**: Session harness maintains progress across context windows
|
|
315
|
-
- **Transparency**: Document all override decisions fully
|
|
316
|
-
- **Accountability**: test_status field creates audit trail
|
|
317
194
|
|
|
318
195
|
WCAG 2.1 STANDARDS
|
|
319
196
|
|
|
@@ -554,7 +431,9 @@ WORKFLOW
|
|
|
554
431
|
|
|
555
432
|
10. Sync externally if enabled
|
|
556
433
|
|
|
557
|
-
|
|
434
|
+
<!-- {{QUALITY_GATE_PRIORITIES}} -->
|
|
435
|
+
|
|
436
|
+
QUALITY CHECKLIST (AG-ACCESSIBILITY Specific)
|
|
558
437
|
|
|
559
438
|
Before approval:
|
|
560
439
|
- [ ] WCAG 2.1 Level AA compliance verified
|
|
@@ -568,6 +447,21 @@ Before approval:
|
|
|
568
447
|
- [ ] Motion respects prefers-reduced-motion
|
|
569
448
|
- [ ] Accessibility documentation complete
|
|
570
449
|
|
|
450
|
+
AGENT COORDINATION
|
|
451
|
+
|
|
452
|
+
**Coordinates with**:
|
|
453
|
+
- **AG-UI**: Accessibility in UI components (send a11y findings, receive implementation questions)
|
|
454
|
+
- **AG-DESIGN**: Design system accessibility (send contrast/focus requirements, coordinate on design tokens)
|
|
455
|
+
- **AG-QA**: Accessibility testing (send test criteria, receive test results)
|
|
456
|
+
|
|
457
|
+
**Bus Messages** (append to `docs/09-agents/bus/log.jsonl`):
|
|
458
|
+
```jsonl
|
|
459
|
+
{"ts":"<ISO>","from":"AG-ACCESSIBILITY","type":"finding","story":"<US-ID>","text":"Finding: Component [X] missing keyboard navigation"}
|
|
460
|
+
{"ts":"<ISO>","from":"AG-ACCESSIBILITY","type":"status","story":"<US-ID>","text":"A11y audit complete: WCAG AA compliance verified"}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**On invocation**: Check bus for AG-UI requests for accessibility review.
|
|
464
|
+
|
|
571
465
|
FIRST ACTION
|
|
572
466
|
|
|
573
467
|
**CRITICAL: Load Expertise First (Agent Expert Protocol)**
|
|
@@ -362,7 +362,9 @@ ADR TEMPLATE STRUCTURE
|
|
|
362
362
|
- [Title](URL) - Description
|
|
363
363
|
```
|
|
364
364
|
|
|
365
|
-
|
|
365
|
+
<!-- {{QUALITY_GATE_PRIORITIES}} -->
|
|
366
|
+
|
|
367
|
+
QUALITY CHECKLIST (AG-ADR-WRITER Specific)
|
|
366
368
|
Before creating ADR:
|
|
367
369
|
- [ ] Context explains why decision is needed now
|
|
368
370
|
- [ ] At least 2 alternatives documented
|
|
@@ -388,6 +390,21 @@ TONE
|
|
|
388
390
|
- Avoid advocacy (document, don't persuade)
|
|
389
391
|
- Focus on context and reasoning, not implementation details
|
|
390
392
|
|
|
393
|
+
AGENT COORDINATION
|
|
394
|
+
|
|
395
|
+
**Coordinates with**:
|
|
396
|
+
- **AG-RESEARCH**: Technical research (receive research findings, document decisions)
|
|
397
|
+
- **AG-PRODUCT**: Product decisions (receive feature requirements, document trade-offs)
|
|
398
|
+
- **AG-EPIC-PLANNER**: Epic planning (send architectural constraints, receive epic context)
|
|
399
|
+
|
|
400
|
+
**Bus Messages** (append to `docs/09-agents/bus/log.jsonl`):
|
|
401
|
+
```jsonl
|
|
402
|
+
{"ts":"<ISO>","from":"AG-ADR-WRITER","type":"status","story":"<US-ID>","text":"ADR-XXXX created: [decision title]"}
|
|
403
|
+
{"ts":"<ISO>","from":"AG-ADR-WRITER","type":"finding","story":"<US-ID>","text":"Finding: Existing ADR-YYYY conflicts with proposed approach"}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**On invocation**: Check bus for architectural decisions that need documentation.
|
|
407
|
+
|
|
391
408
|
FIRST ACTION
|
|
392
409
|
|
|
393
410
|
**CRITICAL: Load Expertise First (Agent Expert Protocol)**
|