agileflow 3.4.0 → 3.4.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 (112) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +4 -4
  3. package/package.json +1 -1
  4. package/scripts/agileflow-welcome.js +79 -0
  5. package/scripts/claude-tmux.sh +12 -36
  6. package/scripts/lib/ac-test-matcher.js +452 -0
  7. package/scripts/lib/audit-registry.js +58 -2
  8. package/scripts/lib/configure-features.js +35 -0
  9. package/scripts/lib/model-profiles.js +25 -5
  10. package/scripts/lib/quality-gates.js +163 -0
  11. package/scripts/lib/signal-detectors.js +43 -0
  12. package/scripts/lib/status-writer.js +255 -0
  13. package/scripts/lib/story-claiming.js +128 -45
  14. package/scripts/lib/task-sync.js +32 -38
  15. package/scripts/lib/tmux-audit-monitor.js +611 -0
  16. package/scripts/lib/tool-registry.yaml +241 -0
  17. package/scripts/lib/tool-shed.js +441 -0
  18. package/scripts/native-team-observer.js +219 -0
  19. package/scripts/obtain-context.js +14 -0
  20. package/scripts/ralph-loop.js +30 -5
  21. package/scripts/smart-detect.js +21 -0
  22. package/scripts/spawn-audit-sessions.js +372 -44
  23. package/scripts/team-manager.js +19 -0
  24. package/src/core/agents/a11y-analyzer-aria.md +155 -0
  25. package/src/core/agents/a11y-analyzer-forms.md +162 -0
  26. package/src/core/agents/a11y-analyzer-keyboard.md +175 -0
  27. package/src/core/agents/a11y-analyzer-semantic.md +153 -0
  28. package/src/core/agents/a11y-analyzer-visual.md +158 -0
  29. package/src/core/agents/a11y-consensus.md +248 -0
  30. package/src/core/agents/ads-consensus.md +74 -0
  31. package/src/core/agents/ads-generate.md +145 -0
  32. package/src/core/agents/ads-performance-tracker.md +197 -0
  33. package/src/core/agents/api-quality-analyzer-conventions.md +148 -0
  34. package/src/core/agents/api-quality-analyzer-docs.md +176 -0
  35. package/src/core/agents/api-quality-analyzer-errors.md +183 -0
  36. package/src/core/agents/api-quality-analyzer-pagination.md +171 -0
  37. package/src/core/agents/api-quality-analyzer-versioning.md +143 -0
  38. package/src/core/agents/api-quality-consensus.md +214 -0
  39. package/src/core/agents/arch-analyzer-circular.md +148 -0
  40. package/src/core/agents/arch-analyzer-complexity.md +171 -0
  41. package/src/core/agents/arch-analyzer-coupling.md +146 -0
  42. package/src/core/agents/arch-analyzer-layering.md +151 -0
  43. package/src/core/agents/arch-analyzer-patterns.md +162 -0
  44. package/src/core/agents/arch-consensus.md +227 -0
  45. package/src/core/commands/adr.md +1 -0
  46. package/src/core/commands/ads/generate.md +238 -0
  47. package/src/core/commands/ads/health.md +327 -0
  48. package/src/core/commands/ads/test-plan.md +317 -0
  49. package/src/core/commands/ads/track.md +288 -0
  50. package/src/core/commands/ads.md +28 -16
  51. package/src/core/commands/assign.md +1 -0
  52. package/src/core/commands/audit.md +43 -6
  53. package/src/core/commands/babysit.md +90 -6
  54. package/src/core/commands/baseline.md +1 -0
  55. package/src/core/commands/blockers.md +1 -0
  56. package/src/core/commands/board.md +1 -0
  57. package/src/core/commands/changelog.md +1 -0
  58. package/src/core/commands/choose.md +1 -0
  59. package/src/core/commands/ci.md +1 -0
  60. package/src/core/commands/code/accessibility.md +347 -0
  61. package/src/core/commands/code/api.md +297 -0
  62. package/src/core/commands/code/architecture.md +297 -0
  63. package/src/core/commands/code/completeness.md +43 -6
  64. package/src/core/commands/code/legal.md +43 -6
  65. package/src/core/commands/code/logic.md +43 -6
  66. package/src/core/commands/code/performance.md +43 -6
  67. package/src/core/commands/code/security.md +43 -6
  68. package/src/core/commands/code/test.md +43 -6
  69. package/src/core/commands/configure.md +1 -0
  70. package/src/core/commands/council.md +1 -0
  71. package/src/core/commands/deploy.md +1 -0
  72. package/src/core/commands/diagnose.md +1 -0
  73. package/src/core/commands/docs.md +1 -0
  74. package/src/core/commands/epic/edit.md +213 -0
  75. package/src/core/commands/epic.md +1 -0
  76. package/src/core/commands/export.md +238 -0
  77. package/src/core/commands/help.md +16 -1
  78. package/src/core/commands/ideate/discover.md +7 -3
  79. package/src/core/commands/ideate/features.md +65 -4
  80. package/src/core/commands/ideate/new.md +158 -124
  81. package/src/core/commands/impact.md +1 -0
  82. package/src/core/commands/learn/explain.md +118 -0
  83. package/src/core/commands/learn/glossary.md +135 -0
  84. package/src/core/commands/learn/patterns.md +138 -0
  85. package/src/core/commands/learn/tour.md +126 -0
  86. package/src/core/commands/migrate/codemods.md +151 -0
  87. package/src/core/commands/migrate/plan.md +131 -0
  88. package/src/core/commands/migrate/scan.md +114 -0
  89. package/src/core/commands/migrate/validate.md +119 -0
  90. package/src/core/commands/multi-expert.md +1 -0
  91. package/src/core/commands/pr.md +1 -0
  92. package/src/core/commands/review.md +1 -0
  93. package/src/core/commands/sprint.md +1 -0
  94. package/src/core/commands/status/undo.md +191 -0
  95. package/src/core/commands/status.md +1 -0
  96. package/src/core/commands/story/edit.md +204 -0
  97. package/src/core/commands/story/view.md +29 -7
  98. package/src/core/commands/story-validate.md +1 -0
  99. package/src/core/commands/story.md +1 -0
  100. package/src/core/commands/tdd.md +1 -0
  101. package/src/core/commands/team/start.md +10 -6
  102. package/src/core/commands/tests.md +1 -0
  103. package/src/core/commands/verify.md +27 -1
  104. package/src/core/commands/workflow.md +2 -0
  105. package/src/core/teams/backend.json +41 -0
  106. package/src/core/teams/frontend.json +41 -0
  107. package/src/core/teams/qa.json +41 -0
  108. package/src/core/teams/solo.json +35 -0
  109. package/src/core/templates/agileflow-metadata.json +5 -0
  110. package/tools/cli/commands/setup.js +85 -3
  111. package/tools/cli/commands/update.js +42 -0
  112. package/tools/cli/installers/ide/claude-code.js +68 -0
@@ -70,8 +70,11 @@ function parseArgs() {
70
70
  traceId: null,
71
71
  timeout: 30,
72
72
  dryRun: false,
73
+ json: false,
73
74
  stagger: null,
74
75
  concurrency: null,
76
+ depth: null,
77
+ partitions: null,
75
78
  };
76
79
 
77
80
  for (const arg of args) {
@@ -89,7 +92,11 @@ function parseArgs() {
89
92
  } else if (arg.startsWith('--concurrency=')) {
90
93
  const parsed = parseInt(arg.split('=')[1], 10);
91
94
  options.concurrency = isNaN(parsed) ? null : parsed;
92
- } else if (arg === '--dry-run') options.dryRun = true;
95
+ } else if (arg.startsWith('--depth=')) options.depth = arg.split('=')[1];
96
+ else if (arg.startsWith('--partitions='))
97
+ options.partitions = arg.split('=')[1].split(',').filter(Boolean);
98
+ else if (arg === '--dry-run') options.dryRun = true;
99
+ else if (arg === '--json') options.json = true;
93
100
  }
94
101
 
95
102
  if (!options.traceId) {
@@ -133,7 +140,7 @@ function createSentinelDir(rootDir, traceId) {
133
140
  * @param {number} [staggerMs] - Stagger delay in milliseconds
134
141
  * @param {number} [maxConcurrent] - Max concurrent sessions (0 = unlimited)
135
142
  */
136
- function writeStatusFile(sentinelDir, auditType, analyzers, staggerMs, maxConcurrent) {
143
+ function writeStatusFile(sentinelDir, auditType, analyzers, staggerMs, maxConcurrent, extra) {
137
144
  const status = {
138
145
  started_at: new Date().toISOString(),
139
146
  audit_type: auditType,
@@ -143,6 +150,12 @@ function writeStatusFile(sentinelDir, auditType, analyzers, staggerMs, maxConcur
143
150
  stagger_ms: staggerMs != null ? staggerMs : null,
144
151
  max_concurrent: maxConcurrent || null,
145
152
  };
153
+ // Store extra fields for retry support
154
+ if (extra) {
155
+ if (extra.target != null) status.target = extra.target;
156
+ if (extra.model != null) status.model = extra.model;
157
+ if (extra.timeout_minutes != null) status.timeout_minutes = extra.timeout_minutes;
158
+ }
146
159
  fs.writeFileSync(path.join(sentinelDir, '_status.json'), JSON.stringify(status, null, 2) + '\n');
147
160
  }
148
161
 
@@ -153,32 +166,47 @@ function writeStatusFile(sentinelDir, auditType, analyzers, staggerMs, maxConcur
153
166
  * @param {string} traceId - Trace ID
154
167
  * @param {string} sentinelDir - Sentinel directory for output
155
168
  * @param {string} auditType - Audit type key
169
+ * @param {string} [model] - Resolved model name for sub-agent
156
170
  * @returns {string} Prompt text
157
171
  */
158
- function buildAnalyzerPrompt(analyzer, target, traceId, sentinelDir, auditType) {
172
+ function buildAnalyzerPrompt(analyzer, target, traceId, sentinelDir, auditType, model) {
159
173
  const findingsFile = path.join(sentinelDir, `${analyzer.key}.findings.json`);
160
174
 
161
- return `You are the ${analyzer.label} analyzer for an ULTRADEEP ${auditType} audit.
175
+ // Sanitize fields that get interpolated into double-quoted prompt sections
176
+ const safeLabel = String(analyzer.label || '').replace(/["\\]/g, '');
177
+ const safeTarget = String(target || '').replace(/["\\]/g, '');
178
+ const safeSubagentType = String(analyzer.subagent_type || '').replace(/["\\]/g, '');
179
+
180
+ return `You are an ULTRADEEP audit session coordinator.
181
+
182
+ ## Task
162
183
 
163
- TARGET: ${target}
164
- TRACE_ID: ${traceId}
165
- ANALYZER: ${analyzer.key} (${analyzer.label})
184
+ 1. Use the Agent tool to spawn a sub-agent for analysis
185
+ 2. After the sub-agent completes, parse its output and write findings as JSON to the sentinel file
166
186
 
167
- ## Instructions
187
+ ## Agent Configuration
168
188
 
169
- 1. Analyze the target path thoroughly for ${analyzer.label}-related issues
170
- 2. Search all relevant files recursively
171
- 3. Document every finding with: file path, line number, severity (P0-P3), description, and evidence
172
- 4. When complete, write your findings as JSON to: ${findingsFile}
189
+ Use the Agent tool with these parameters:
190
+ - subagent_type: "${safeSubagentType}"
191
+ - description: "${safeLabel} analysis of ${safeTarget}"${model ? `\n- model: "${model}"` : ''}
192
+ - prompt: |
193
+ Analyze the target path ${safeTarget} thoroughly for ${safeLabel}-related issues.
194
+ Search all relevant files recursively. Be thorough.
195
+ Return a JSON object with this structure:
196
+ {"findings": [{"id": "${analyzer.key}-NNN", "severity": "P0|P1|P2|P3", "title": "Short description", "file": "path/to/file.js", "line": 42, "description": "Detailed explanation", "evidence": "Code snippet or reasoning", "recommendation": "How to fix"}], "summary": {"files_scanned": 0, "total_findings": 0, "by_severity": {"P0": 0, "P1": 0, "P2": 0, "P3": 0}}}
197
+ TRACE_ID: ${traceId}
198
+ ANALYZER: ${analyzer.key}
173
199
 
174
- ## Output Format
200
+ ## Output
175
201
 
176
- Write a JSON file with this structure:
202
+ After the Agent tool returns its analysis, write a JSON file to: ${findingsFile}
203
+
204
+ Use this structure:
177
205
  {
178
206
  "analyzer": "${analyzer.key}",
179
207
  "audit_type": "${auditType}",
180
208
  "trace_id": "${traceId}",
181
- "target": "${target}",
209
+ "target": "${safeTarget}",
182
210
  "completed_at": "<ISO timestamp>",
183
211
  "findings": [
184
212
  {
@@ -200,7 +228,107 @@ Write a JSON file with this structure:
200
228
  }
201
229
 
202
230
  IMPORTANT: You MUST write the findings JSON file when complete. This is how the orchestrator knows you're done.
203
- Start analyzing now.`;
231
+ If the Agent tool is unavailable or returns an error, perform the analysis directly using Read, Glob, and Grep tools.
232
+ Start by spawning the Agent now.`;
233
+ }
234
+
235
+ /**
236
+ * Create a slug from a partition path for use in window names and filenames.
237
+ * Only allows alphanumeric, hyphens, and underscores — safe for tmux window
238
+ * names, shell interpolation, and filesystem paths.
239
+ * @param {string} partition - Partition path (e.g. 'src/auth')
240
+ * @returns {string} Slug (e.g. 'src-auth')
241
+ */
242
+ function partitionSlug(partition) {
243
+ return (
244
+ partition
245
+ .replace(/^\.?\/?/, '')
246
+ .replace(/\/+$/g, '')
247
+ .replace(/[/\\]/g, '-')
248
+ .replace(/[^a-zA-Z0-9_-]/g, '_') || 'root'
249
+ );
250
+ }
251
+
252
+ /**
253
+ * Build the prompt for an extreme-mode partition coordinator session.
254
+ * This coordinator runs ALL analyzers on a single partition using the Agent tool.
255
+ * @param {string} partition - Partition path to analyze
256
+ * @param {Array<{ key: string, subagent_type: string, label: string }>} analyzers - All analyzers to run
257
+ * @param {string} traceId - Trace ID
258
+ * @param {string} sentinelDir - Sentinel directory for output
259
+ * @param {string} auditType - Audit type key
260
+ * @param {string} [model] - Resolved model name for sub-agents
261
+ * @returns {string} Prompt text
262
+ */
263
+ function buildExtremePrompt(partition, analyzers, traceId, sentinelDir, auditType, model) {
264
+ const slug = partitionSlug(partition);
265
+ const findingsFile = path.join(sentinelDir, `${slug}.findings.json`);
266
+
267
+ const safePartition = String(partition || '').replace(/["\\]/g, '');
268
+
269
+ const analyzerList = analyzers
270
+ .map((a, i) => `${i + 1}. subagent_type: "${a.subagent_type}" — ${a.label} (key: ${a.key})`)
271
+ .join('\n');
272
+
273
+ const modelLine = model ? `\n- model: "${model}"` : '';
274
+
275
+ return `You are an EXTREME audit session coordinator for partition: ${safePartition}
276
+
277
+ ## Task
278
+
279
+ Run ALL of the following analyzers on your partition using the Agent tool.
280
+ Deploy them in parallel (multiple Agent calls in one message) where possible.
281
+
282
+ ## Analyzers to Run
283
+ ${analyzerList}
284
+
285
+ ## Agent Configuration for Each Analyzer
286
+
287
+ Use the Agent tool with these parameters for each analyzer:
288
+ - subagent_type: (from the list above)
289
+ - description: "[Analyzer label] analysis of ${safePartition}"${modelLine}
290
+ - prompt: |
291
+ Analyze the target path ${safePartition} thoroughly for issues relevant to your analyzer domain.
292
+ Search all relevant files recursively. Be thorough.
293
+ Use the analyzer key from your subagent_type as the ID prefix (e.g., for "security-analyzer-injection" use "injection").
294
+ Return a JSON object with this structure:
295
+ {"findings": [{"id": "<analyzer-key>-NNN", "severity": "P0|P1|P2|P3", "title": "Short description", "file": "path/to/file.js", "line": 42, "description": "Detailed explanation", "evidence": "Code snippet or reasoning", "recommendation": "How to fix"}], "summary": {"files_scanned": 0, "total_findings": 0, "by_severity": {"P0": 0, "P1": 0, "P2": 0, "P3": 0}}}
296
+ TRACE_ID: ${traceId}
297
+
298
+ ## After All Analyzers Complete
299
+
300
+ Combine ALL findings from ALL analyzers into a single JSON file: ${findingsFile}
301
+
302
+ Use this structure:
303
+ {
304
+ "partition": "${safePartition}",
305
+ "audit_type": "${auditType}",
306
+ "trace_id": "${traceId}",
307
+ "completed_at": "<ISO timestamp>",
308
+ "analyzer_count": ${analyzers.length},
309
+ "analyzers_run": [${analyzers.map(a => `"${a.key}"`).join(', ')}],
310
+ "findings": [
311
+ {
312
+ "id": "<analyzer_key>-001",
313
+ "analyzer": "<analyzer_key>",
314
+ "severity": "P0|P1|P2|P3",
315
+ "title": "Short description",
316
+ "file": "path/to/file.js",
317
+ "line": 42,
318
+ "description": "Detailed explanation",
319
+ "evidence": "Code snippet or reasoning",
320
+ "recommendation": "How to fix"
321
+ }
322
+ ],
323
+ "summary": {
324
+ "files_scanned": 0,
325
+ "total_findings": 0,
326
+ "by_severity": { "P0": 0, "P1": 0, "P2": 0, "P3": 0 }
327
+ }
328
+ }
329
+
330
+ IMPORTANT: You MUST write the findings JSON file when complete. This is how the orchestrator knows you're done.
331
+ Start by spawning ALL ${analyzers.length} analyzers now, in parallel.`;
204
332
  }
205
333
 
206
334
  /**
@@ -225,7 +353,8 @@ function spawnOneSession({
225
353
  options.target,
226
354
  options.traceId,
227
355
  sentinelDir,
228
- options.audit
356
+ options.audit,
357
+ model
229
358
  );
230
359
  const escapedPrompt = prompt.replace(/'/g, "'\\''");
231
360
 
@@ -248,7 +377,7 @@ function spawnOneSession({
248
377
  { stdio: 'pipe' }
249
378
  );
250
379
 
251
- const claudeCmd = `echo '${escapedPrompt}' | claude --model ${model} --allowedTools 'Read Glob Grep Write' 2>&1; echo "AUDIT_COMPLETE: ${analyzer.key}"`;
380
+ const claudeCmd = `echo '${escapedPrompt}' | claude --model ${model} --allowedTools 'Read Glob Grep Write Agent' 2>&1; echo "AUDIT_COMPLETE: ${analyzer.key}"`;
252
381
  execFileSync('tmux', ['send-keys', '-t', `${sessionName}:${windowName}`, claudeCmd, 'Enter'], {
253
382
  stdio: 'pipe',
254
383
  });
@@ -295,44 +424,202 @@ async function spawnAuditInTmux(options) {
295
424
  process.exit(1);
296
425
  }
297
426
 
298
- const result = getAnalyzersForAudit(options.audit, 'ultradeep', options.focus);
427
+ const isExtreme = options.depth === 'extreme';
428
+ const depthForRegistry = isExtreme ? 'extreme' : 'ultradeep';
429
+ const result = getAnalyzersForAudit(options.audit, depthForRegistry, options.focus);
299
430
  if (!result || result.analyzers.length === 0) {
300
431
  console.error(`No analyzers found for ${options.audit} with focus: ${options.focus.join(',')}`);
301
432
  process.exit(1);
302
433
  }
303
434
 
435
+ const sentinelDir = createSentinelDir(rootDir, options.traceId);
436
+
437
+ // Use stderr for human output when --json mode is active
438
+ const log = options.json ? console.error : console.log;
439
+
440
+ // --- EXTREME MODE: partition-based multi-agent ---
441
+ if (isExtreme) {
442
+ if (!options.partitions || options.partitions.length === 0) {
443
+ console.error('EXTREME mode requires --partitions=dir1,dir2,...');
444
+ process.exit(1);
445
+ }
446
+
447
+ const partitions = options.partitions;
448
+ const partitionSlugs = partitions.map(p => partitionSlug(p));
449
+
450
+ // Write status file with partition info
451
+ const statusData = {
452
+ started_at: new Date().toISOString(),
453
+ audit_type: options.audit,
454
+ mode: 'extreme',
455
+ partitions: partitions,
456
+ analyzers: partitionSlugs,
457
+ analyzers_per_partition: result.analyzers.map(a => a.key),
458
+ completed: [],
459
+ failed: [],
460
+ target: options.target,
461
+ model: options.model,
462
+ timeout_minutes: options.timeout,
463
+ };
464
+ fs.writeFileSync(
465
+ path.join(sentinelDir, '_status.json'),
466
+ JSON.stringify(statusData, null, 2) + '\n'
467
+ );
468
+
469
+ const groupColor = getColorForAudit(options.audit);
470
+ const totalSessions = partitions.length * result.analyzers.length;
471
+
472
+ if (options.dryRun) {
473
+ log(`\nDry run - EXTREME mode would spawn ${partitions.length} partition coordinators:`);
474
+ log(` Partitions: ${partitions.join(', ')}`);
475
+ log(` Analyzers per partition: ${result.analyzers.length}`);
476
+ log(` Total agent sessions: ${totalSessions}`);
477
+ const model = resolveModel(options.model, 'haiku');
478
+ for (const p of partitions) {
479
+ const slug = partitionSlug(p);
480
+ log(
481
+ ` ${auditType.prefix}:${slug} (${model}) → ALL ${result.analyzers.length} analyzers on ${p}`
482
+ );
483
+ }
484
+ log(`\nSentinel dir: ${sentinelDir}`);
485
+ log(`Group color: ${groupColor}`);
486
+ return {
487
+ ok: true,
488
+ traceId: options.traceId,
489
+ sentinelDir,
490
+ sessions: [],
491
+ dryRun: true,
492
+ mode: 'extreme',
493
+ partitions,
494
+ };
495
+ }
496
+
497
+ const tmux = checkTmux();
498
+ if (!tmux.available) {
499
+ console.error('tmux is not available. EXTREME mode requires tmux.');
500
+ console.error('Falling back to DEPTH=deep mode.');
501
+ return { ok: false, traceId: options.traceId, sentinelDir, sessions: [], fallback: 'deep' };
502
+ }
503
+
504
+ const sessionName = `audit-${options.audit}-${options.traceId.slice(0, 8)}`;
505
+ const sessions = [];
506
+ const config = getUltradeepConfig();
507
+ const staggerMs =
508
+ ((options.stagger != null ? options.stagger : config.stagger_seconds) || 0) * 1000;
509
+
510
+ for (let i = 0; i < partitions.length; i++) {
511
+ if (i > 0 && staggerMs > 0) await sleep(staggerMs);
512
+
513
+ const partition = partitions[i];
514
+ const slug = partitionSlug(partition);
515
+ const windowName = `${auditType.prefix}:${slug}`;
516
+ const model = resolveModel(options.model, 'haiku');
517
+ const prompt = buildExtremePrompt(
518
+ partition,
519
+ result.analyzers,
520
+ options.traceId,
521
+ sentinelDir,
522
+ options.audit,
523
+ model
524
+ );
525
+ const escapedPrompt = prompt.replace(/'/g, "'\\''");
526
+
527
+ try {
528
+ if (i === 0) {
529
+ execFileSync(
530
+ 'tmux',
531
+ ['new-session', '-d', '-s', sessionName, '-n', windowName, '-c', rootDir],
532
+ { stdio: 'pipe' }
533
+ );
534
+ } else {
535
+ execFileSync('tmux', ['new-window', '-t', sessionName, '-n', windowName, '-c', rootDir], {
536
+ stdio: 'pipe',
537
+ });
538
+ }
539
+
540
+ execFileSync(
541
+ 'tmux',
542
+ ['set-option', '-w', '-t', `${sessionName}:${windowName}`, '@group_color', groupColor],
543
+ { stdio: 'pipe' }
544
+ );
545
+
546
+ const claudeCmd = `echo '${escapedPrompt}' | claude --model ${model} --allowedTools 'Read Glob Grep Write Agent' 2>&1; echo "AUDIT_COMPLETE: ${slug}"`;
547
+ execFileSync(
548
+ 'tmux',
549
+ ['send-keys', '-t', `${sessionName}:${windowName}`, claudeCmd, 'Enter'],
550
+ {
551
+ stdio: 'pipe',
552
+ }
553
+ );
554
+
555
+ sessions.push(windowName);
556
+ } catch (err) {
557
+ console.error(`Failed to spawn ${windowName}: ${err.message}`);
558
+ }
559
+ }
560
+
561
+ // Apply status bar theme
562
+ try {
563
+ const tmuxScript = path.join(__dirname, 'claude-tmux.sh');
564
+ execFileSync(tmuxScript, [`--configure-session=${sessionName}`], { stdio: 'pipe' });
565
+ } catch (_) {
566
+ // Non-critical
567
+ }
568
+
569
+ log(`\nSpawned ${sessions.length} partition coordinators in tmux session: ${sessionName}`);
570
+ log(` Partitions: ${partitions.join(', ')}`);
571
+ log(` Analyzers per partition: ${result.analyzers.length}`);
572
+ log(` Total agent sessions: ${totalSessions}`);
573
+ log(`Sentinel dir: ${sentinelDir}`);
574
+ log(`Attach with: tmux attach -t ${sessionName}`);
575
+
576
+ return {
577
+ ok: true,
578
+ traceId: options.traceId,
579
+ sentinelDir,
580
+ sessions,
581
+ sessionName,
582
+ mode: 'extreme',
583
+ partitions,
584
+ };
585
+ }
586
+
587
+ // --- ULTRADEEP MODE (existing behavior) ---
588
+
304
589
  // Enforce session limit
305
590
  if (result.analyzers.length > 20) {
306
591
  console.error(`Too many analyzers (${result.analyzers.length}). Maximum is 20.`);
307
592
  process.exit(1);
308
593
  }
309
594
 
310
- const sentinelDir = createSentinelDir(rootDir, options.traceId);
311
-
312
595
  // Resolve stagger and concurrency from CLI flags or config
313
596
  const config = getUltradeepConfig();
314
597
  const staggerMs =
315
598
  ((options.stagger != null ? options.stagger : config.stagger_seconds) || 0) * 1000;
316
599
  const maxConcurrent = options.concurrency != null ? options.concurrency : config.max_concurrent;
317
600
 
318
- writeStatusFile(sentinelDir, options.audit, result.analyzers, staggerMs, maxConcurrent);
601
+ writeStatusFile(sentinelDir, options.audit, result.analyzers, staggerMs, maxConcurrent, {
602
+ target: options.target,
603
+ model: options.model,
604
+ timeout_minutes: options.timeout,
605
+ });
319
606
 
320
607
  const groupColor = getColorForAudit(options.audit);
321
608
  const sessions = [];
322
609
 
323
610
  if (options.dryRun) {
324
- console.log(`\nDry run - would spawn ${result.analyzers.length} sessions:`);
325
- console.log(` Stagger: ${staggerMs / 1000}s between launches`);
611
+ log(`\nDry run - would spawn ${result.analyzers.length} sessions:`);
612
+ log(` Stagger: ${staggerMs / 1000}s between launches`);
326
613
  if (maxConcurrent > 0) {
327
614
  const waveCount = Math.ceil(result.analyzers.length / maxConcurrent);
328
- console.log(` Concurrency: ${maxConcurrent}/wave (${waveCount} waves)`);
615
+ log(` Concurrency: ${maxConcurrent}/wave (${waveCount} waves)`);
329
616
  }
330
617
  for (const analyzer of result.analyzers) {
331
618
  const model = resolveModel(options.model, 'haiku');
332
- console.log(` ${auditType.prefix}:${analyzer.key} (${model}) → ${analyzer.label}`);
619
+ log(` ${auditType.prefix}:${analyzer.key} (${model}) → ${analyzer.label}`);
333
620
  }
334
- console.log(`\nSentinel dir: ${sentinelDir}`);
335
- console.log(`Group color: ${groupColor}`);
621
+ log(`\nSentinel dir: ${sentinelDir}`);
622
+ log(`Group color: ${groupColor}`);
336
623
  return { ok: true, traceId: options.traceId, sentinelDir, sessions: [], dryRun: true };
337
624
  }
338
625
 
@@ -400,9 +687,9 @@ async function spawnAuditInTmux(options) {
400
687
  // Non-critical styling failure — audit session still works with default theme
401
688
  }
402
689
 
403
- console.log(`\nSpawned ${sessions.length} analyzer sessions in tmux session: ${sessionName}`);
404
- console.log(`Sentinel dir: ${sentinelDir}`);
405
- console.log(`Attach with: tmux attach -t ${sessionName}`);
690
+ log(`\nSpawned ${sessions.length} analyzer sessions in tmux session: ${sessionName}`);
691
+ log(`Sentinel dir: ${sentinelDir}`);
692
+ log(`Attach with: tmux attach -t ${sessionName}`);
406
693
 
407
694
  return { ok: true, traceId: options.traceId, sentinelDir, sessions, sessionName };
408
695
  }
@@ -460,7 +747,9 @@ async function pollForCompletion(sentinelDir, expected, timeoutMinutes) {
460
747
  /**
461
748
  * Collect all findings from sentinel directory.
462
749
  * @param {string} sentinelDir - Sentinel directory path
463
- * @param {string[]} expected - Expected analyzer keys
750
+ * @param {string[]} expected - Expected keys. In ultradeep mode these are analyzer keys
751
+ * (e.g. 'injection', 'auth'). In extreme mode these are partition slugs
752
+ * (e.g. 'src-auth', 'src-api') — matches the `analyzers` array in `_status.json`.
464
753
  * @returns {object[]} Array of parsed findings
465
754
  */
466
755
  function collectResults(sentinelDir, expected) {
@@ -490,18 +779,34 @@ function collectResults(sentinelDir, expected) {
490
779
  * @param {string} auditType - Audit type key
491
780
  * @param {number} analyzerCount - Number of analyzers to spawn
492
781
  * @param {string} [model] - Explicit model override
782
+ * @param {object} [opts] - Options
783
+ * @param {boolean} [opts.json] - If true, route output to stderr to keep stdout clean for JSON
784
+ * @param {number} [opts.partitions] - Number of partitions (extreme mode)
493
785
  */
494
- function showCostEstimate(auditType, analyzerCount, model) {
786
+ function showCostEstimate(auditType, analyzerCount, model, opts) {
495
787
  const resolved = resolveModel(model, 'haiku');
496
- const estimate = estimateCost(resolved, analyzerCount);
497
-
498
- console.log(`\nCost estimate for ULTRADEEP ${auditType} audit:`);
499
- console.log(` Model: ${estimate.model}`);
500
- console.log(` Analyzers: ${analyzerCount}`);
501
- console.log(` Cost multiplier vs haiku: ${estimate.multiplier}x`);
502
- console.log(` Per-analyzer estimate: ${estimate.perAnalyzerCost}`);
503
- console.log(` Total estimate: ${estimate.totalEstimate}`);
504
- console.log(` Each analyzer runs as a full Claude Code session`);
788
+ const partCount =
789
+ opts && typeof opts.partitions === 'number' && opts.partitions > 1 ? opts.partitions : 1;
790
+ const estimate = estimateCost(resolved, analyzerCount, partCount);
791
+ const log = opts && opts.json ? console.error : console.log;
792
+
793
+ if (partCount > 1) {
794
+ log(`\nCost estimate for EXTREME ${auditType} audit:`);
795
+ log(` Model: ${estimate.model}`);
796
+ log(` Partitions: ${partCount}`);
797
+ log(` Analyzers per partition: ${analyzerCount}`);
798
+ log(` Total agent sessions: ${estimate.totalSessions}`);
799
+ log(` Per-agent estimate: ${estimate.perAnalyzerCost}`);
800
+ log(` Total estimate: ${estimate.totalEstimate}`);
801
+ } else {
802
+ log(`\nCost estimate for ULTRADEEP ${auditType} audit:`);
803
+ log(` Model: ${estimate.model}`);
804
+ log(` Analyzers: ${analyzerCount}`);
805
+ log(` Cost multiplier vs haiku: ${estimate.multiplier}x`);
806
+ log(` Per-analyzer estimate: ${estimate.perAnalyzerCost}`);
807
+ log(` Total estimate: ${estimate.totalEstimate}`);
808
+ log(` Each analyzer runs as a full Claude Code session`);
809
+ }
505
810
  }
506
811
 
507
812
  // Main
@@ -517,12 +822,33 @@ if (require.main === module) {
517
822
  process.exit(1);
518
823
  }
519
824
 
520
- const result = getAnalyzersForAudit(options.audit, 'ultradeep', options.focus);
825
+ const isExtreme = options.depth === 'extreme';
826
+ const depthForCost = isExtreme ? 'extreme' : 'ultradeep';
827
+ const result = getAnalyzersForAudit(options.audit, depthForCost, options.focus);
521
828
  if (result) {
522
- showCostEstimate(options.audit, result.analyzers.length, options.model);
829
+ showCostEstimate(options.audit, result.analyzers.length, options.model, {
830
+ json: options.json,
831
+ partitions: isExtreme && options.partitions ? options.partitions.length : 1,
832
+ });
523
833
  }
524
834
 
525
835
  const spawnResult = await spawnAuditInTmux(options);
836
+
837
+ if (options.json) {
838
+ const jsonOut = {
839
+ ok: spawnResult.ok,
840
+ traceId: spawnResult.traceId,
841
+ sentinelDir: spawnResult.sentinelDir,
842
+ sessionName: spawnResult.sessionName || null,
843
+ sessions: spawnResult.sessions,
844
+ dryRun: spawnResult.dryRun || false,
845
+ fallback: spawnResult.fallback || null,
846
+ mode: spawnResult.mode || 'ultradeep',
847
+ partitions: spawnResult.partitions || null,
848
+ };
849
+ console.log(JSON.stringify(jsonOut));
850
+ }
851
+
526
852
  if (!spawnResult.ok && spawnResult.fallback) {
527
853
  process.exit(2); // Signal fallback to caller
528
854
  }
@@ -538,6 +864,8 @@ module.exports = {
538
864
  createSentinelDir,
539
865
  writeStatusFile,
540
866
  buildAnalyzerPrompt,
867
+ buildExtremePrompt,
868
+ partitionSlug,
541
869
  spawnOneSession,
542
870
  spawnAuditInTmux,
543
871
  pollForCompletion,
@@ -492,6 +492,25 @@ function stopTeam(rootDir) {
492
492
  // Non-critical - metrics aggregation is best-effort
493
493
  }
494
494
 
495
+ // Reconcile teammate final states to status.json (AC2/AC3)
496
+ try {
497
+ const taskSync = require('./lib/task-sync');
498
+ const nativeTasks = (team.teammates || [])
499
+ .filter(t => t.status === 'completed' || t.status === 'done')
500
+ .map(t => ({
501
+ id: t.agent,
502
+ status: 'completed',
503
+ metadata: { story_id: t.story_id },
504
+ }))
505
+ .filter(t => t.metadata.story_id);
506
+
507
+ if (nativeTasks.length > 0) {
508
+ taskSync.reconcile(rootDir, nativeTasks);
509
+ }
510
+ } catch (e) {
511
+ // Non-critical - reconciliation is best-effort
512
+ }
513
+
495
514
  return {
496
515
  ok: true,
497
516
  template: team.template,