sequant 2.0.1 → 2.1.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 (58) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/bin/cli.js +2 -1
  4. package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
  5. package/dist/marketplace/external_plugins/sequant/.mcp.json +6 -0
  6. package/dist/marketplace/external_plugins/sequant/README.md +58 -8
  7. package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +19 -8
  8. package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +36 -49
  9. package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +158 -48
  10. package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +354 -352
  11. package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +1155 -33
  12. package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +35 -4
  13. package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +2157 -104
  14. package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +1 -1
  15. package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +386 -0
  16. package/dist/marketplace/external_plugins/sequant/skills/solve/SKILL.md +38 -664
  17. package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +505 -120
  18. package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +246 -1
  19. package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +138 -1
  20. package/dist/src/commands/dashboard.js +1 -1
  21. package/dist/src/commands/doctor.js +1 -1
  22. package/dist/src/commands/init.js +10 -10
  23. package/dist/src/commands/logs.js +1 -1
  24. package/dist/src/commands/run.js +49 -39
  25. package/dist/src/commands/state.js +3 -3
  26. package/dist/src/commands/status.js +5 -5
  27. package/dist/src/commands/sync.js +8 -8
  28. package/dist/src/commands/update.js +16 -16
  29. package/dist/src/lib/cli-ui.js +20 -19
  30. package/dist/src/lib/merge-check/index.js +2 -2
  31. package/dist/src/lib/settings.d.ts +8 -0
  32. package/dist/src/lib/settings.js +1 -0
  33. package/dist/src/lib/shutdown.js +1 -1
  34. package/dist/src/lib/templates.js +2 -0
  35. package/dist/src/lib/wizard.js +6 -4
  36. package/dist/src/lib/workflow/batch-executor.js +1 -1
  37. package/dist/src/lib/workflow/log-writer.js +6 -6
  38. package/dist/src/lib/workflow/metrics-writer.js +5 -3
  39. package/dist/src/lib/workflow/phase-executor.js +5 -1
  40. package/dist/src/lib/workflow/platforms/github.js +5 -1
  41. package/dist/src/lib/workflow/state-cleanup.js +1 -1
  42. package/dist/src/lib/workflow/state-manager.js +15 -13
  43. package/dist/src/lib/workflow/state-rebuild.js +2 -2
  44. package/dist/src/lib/workflow/types.d.ts +11 -0
  45. package/dist/src/lib/workflow/worktree-manager.js +40 -41
  46. package/dist/src/lib/worktree-isolation.d.ts +130 -0
  47. package/dist/src/lib/worktree-isolation.js +310 -0
  48. package/package.json +8 -8
  49. package/templates/agents/sequant-explorer.md +23 -0
  50. package/templates/agents/sequant-implementer.md +18 -0
  51. package/templates/agents/sequant-qa-checker.md +24 -0
  52. package/templates/agents/sequant-testgen.md +25 -0
  53. package/templates/scripts/cleanup-worktree.sh +18 -0
  54. package/templates/skills/_shared/references/subagent-types.md +158 -48
  55. package/templates/skills/exec/SKILL.md +72 -6
  56. package/templates/skills/qa/SKILL.md +8 -217
  57. package/templates/skills/spec/SKILL.md +446 -120
  58. package/templates/skills/testgen/SKILL.md +138 -1
@@ -109,7 +109,7 @@ export async function runCommand(issues, options) {
109
109
  try {
110
110
  const versionResult = await checkVersionCached();
111
111
  if (versionResult.isOutdated && versionResult.latestVersion) {
112
- console.log(chalk.yellow(` ⚠️ ${getVersionWarning(versionResult.currentVersion, versionResult.latestVersion, versionResult.isLocalInstall)}`));
112
+ console.log(chalk.yellow(` ! ${getVersionWarning(versionResult.currentVersion, versionResult.latestVersion, versionResult.isLocalInstall)}`));
113
113
  console.log("");
114
114
  }
115
115
  }
@@ -138,6 +138,8 @@ export async function runCommand(issues, options) {
138
138
  qualityLoop: normalizedOptions.qualityLoop ?? settings.run.qualityLoop,
139
139
  maxIterations: normalizedOptions.maxIterations ?? settings.run.maxIterations,
140
140
  noSmartTests: normalizedOptions.noSmartTests ?? !settings.run.smartTests,
141
+ // Agent settings (from agents section, not run section)
142
+ isolateParallel: normalizedOptions.isolateParallel ?? settings.agents.isolateParallel,
141
143
  // Env overrides
142
144
  ...envConfig,
143
145
  // CLI explicit options override all
@@ -182,7 +184,7 @@ export async function runCommand(issues, options) {
182
184
  }
183
185
  // Warn about long chains
184
186
  if (issueNumbers.length > 5) {
185
- console.log(chalk.yellow(` ⚠️ Warning: Chain has ${issueNumbers.length} issues (recommended max: 5)`));
187
+ console.log(chalk.yellow(` ! Warning: Chain has ${issueNumbers.length} issues (recommended max: 5)`));
186
188
  console.log(chalk.yellow(" Long chains increase merge complexity and review difficulty."));
187
189
  console.log(chalk.yellow(" Consider breaking into smaller chains or using batch mode."));
188
190
  console.log("");
@@ -242,6 +244,7 @@ export async function runCommand(issues, options) {
242
244
  retry: retryEnabled,
243
245
  agent: mergedOptions.agent ?? settings.run.agent,
244
246
  aiderSettings: settings.run.aider,
247
+ isolateParallel: mergedOptions.isolateParallel,
245
248
  };
246
249
  // Propagate verbose mode to UI config so spinners use text-only mode.
247
250
  // This prevents animated spinner control characters from colliding with
@@ -276,7 +279,7 @@ export async function runCommand(issues, options) {
276
279
  // Log initialization failure is non-fatal - warn and continue without logging
277
280
  // Common causes: permissions issues, disk full, invalid path
278
281
  const errorMessage = err instanceof Error ? err.message : String(err);
279
- console.log(chalk.yellow(` ⚠️ Log initialization failed, continuing without logging: ${errorMessage}`));
282
+ console.log(chalk.yellow(` ! Log initialization failed, continuing without logging: ${errorMessage}`));
280
283
  logWriter = null;
281
284
  }
282
285
  }
@@ -295,37 +298,38 @@ export async function runCommand(issues, options) {
295
298
  await writer.finalize();
296
299
  });
297
300
  }
298
- // Display configuration
299
- console.log(chalk.gray(` Stack: ${manifest.stack}`));
301
+ // Display configuration (columnar alignment)
302
+ const pad = (label) => label.padEnd(15);
303
+ console.log(chalk.gray(` ${pad("Stack")}${manifest.stack}`));
300
304
  if (autoDetectPhases) {
301
- console.log(chalk.gray(` Phases: auto-detect from labels`));
305
+ console.log(chalk.gray(` ${pad("Phases")}auto-detect from labels`));
302
306
  }
303
307
  else {
304
- console.log(chalk.gray(` Phases: ${config.phases.join(" ")}`));
308
+ console.log(chalk.gray(` ${pad("Phases")}${config.phases.join(" \u2192 ")}`));
305
309
  }
306
- console.log(chalk.gray(` Mode: ${config.sequential ? "sequential (stop-on-failure)" : `parallel (concurrency: ${config.concurrency})`}`));
310
+ console.log(chalk.gray(` ${pad("Mode")}${config.sequential ? "sequential (stop-on-failure)" : `parallel (concurrency: ${config.concurrency})`}`));
307
311
  if (config.qualityLoop) {
308
- console.log(chalk.gray(` Quality loop: enabled (max ${config.maxIterations} iterations)`));
312
+ console.log(chalk.gray(` ${pad("Quality loop")}enabled (max ${config.maxIterations} iterations)`));
309
313
  }
310
314
  if (mergedOptions.testgen) {
311
- console.log(chalk.gray(` Testgen: enabled`));
315
+ console.log(chalk.gray(` ${pad("Testgen")}enabled`));
312
316
  }
313
317
  if (config.noSmartTests) {
314
- console.log(chalk.gray(` Smart tests: disabled`));
318
+ console.log(chalk.gray(` ${pad("Smart tests")}disabled`));
315
319
  }
316
320
  if (config.dryRun) {
317
- console.log(chalk.yellow(` ⚠️ DRY RUN - no actual execution`));
321
+ console.log(chalk.yellow(` ${pad("!")}DRY RUN - no actual execution`));
318
322
  }
319
323
  if (logWriter) {
320
- console.log(chalk.gray(` Logging: JSON (run ${logWriter.getRunId()?.slice(0, 8)}...)`));
324
+ console.log(chalk.gray(` ${pad("Logging")}JSON (run ${logWriter.getRunId()?.slice(0, 8)}...)`));
321
325
  }
322
326
  if (stateManager) {
323
- console.log(chalk.gray(` State tracking: enabled`));
327
+ console.log(chalk.gray(` ${pad("State")}enabled`));
324
328
  }
325
329
  if (mergedOptions.force) {
326
- console.log(chalk.yellow(` Force mode: enabled (bypass state guard)`));
330
+ console.log(chalk.yellow(` ${pad("Force")}enabled (bypass state guard)`));
327
331
  }
328
- console.log(chalk.gray(` Issues: ${issueNumbers.map((n) => `#${n}`).join(", ")}`));
332
+ console.log(chalk.gray(` ${pad("Issues")}${issueNumbers.map((n) => `#${n}`).join(", ")}`));
329
333
  // ============================================================================
330
334
  // Pre-flight State Guard (#305)
331
335
  // ============================================================================
@@ -341,7 +345,7 @@ export async function runCommand(issues, options) {
341
345
  }
342
346
  catch (error) {
343
347
  // AC-8: Graceful degradation - don't block execution on reconciliation failure
344
- logNonFatalWarning(` ⚠️ State reconciliation failed, continuing...`, error, config.verbose);
348
+ logNonFatalWarning(` ! State reconciliation failed, continuing...`, error, config.verbose);
345
349
  }
346
350
  }
347
351
  // AC-1 & AC-2: Pre-flight state guard - skip completed issues unless --force
@@ -355,7 +359,7 @@ export async function runCommand(issues, options) {
355
359
  (issueState.status === "ready_for_merge" ||
356
360
  issueState.status === "merged")) {
357
361
  skippedIssues.push(issueNumber);
358
- console.log(chalk.yellow(` ⚠️ #${issueNumber}: already ${issueState.status} — skipping (use --force to re-run)`));
362
+ console.log(chalk.yellow(` ! #${issueNumber}: already ${issueState.status} — skipping (use --force to re-run)`));
359
363
  }
360
364
  else {
361
365
  activeIssues.push(issueNumber);
@@ -363,7 +367,7 @@ export async function runCommand(issues, options) {
363
367
  }
364
368
  catch (error) {
365
369
  // AC-8: Graceful degradation - if state check fails, include the issue
366
- logNonFatalWarning(` ⚠️ State lookup failed for #${issueNumber}, including anyway...`, error, config.verbose);
370
+ logNonFatalWarning(` ! State lookup failed for #${issueNumber}, including anyway...`, error, config.verbose);
367
371
  activeIssues.push(issueNumber);
368
372
  }
369
373
  }
@@ -451,7 +455,7 @@ export async function runCommand(issues, options) {
451
455
  // Check if batch failed and we should stop
452
456
  const batchFailed = batchResults.some((r) => !r.success);
453
457
  if (batchFailed && config.sequential) {
454
- console.log(chalk.yellow(`\n ⚠️ Batch ${batchIdx + 1} failed, stopping batch execution`));
458
+ console.log(chalk.yellow(`\n ! Batch ${batchIdx + 1} failed, stopping batch execution`));
455
459
  break;
456
460
  }
457
461
  }
@@ -495,7 +499,7 @@ export async function runCommand(issues, options) {
495
499
  }
496
500
  }
497
501
  const chainInfo = mergedOptions.chain ? " (chain stopped)" : "";
498
- console.log(chalk.yellow(`\n ⚠️ Issue #${issueNumber} failed, stopping sequential execution${chainInfo}`));
502
+ console.log(chalk.yellow(`\n ! Issue #${issueNumber} failed, stopping sequential execution${chainInfo}`));
499
503
  break;
500
504
  }
501
505
  }
@@ -512,12 +516,12 @@ export async function runCommand(issues, options) {
512
516
  const parts = issueNumbers.map((num) => {
513
517
  const info = issueStatus.get(num);
514
518
  if (info.state === "done")
515
- return colors.success(`#${num} ✓`);
519
+ return colors.success(`#${num} \u2714`);
516
520
  if (info.state === "failed")
517
- return colors.error(`#${num} ✗`);
518
- return colors.warning(`#${num} ⏳`);
521
+ return colors.error(`#${num} \u2716`);
522
+ return colors.muted(`#${num} \u00B7`);
519
523
  });
520
- return ` Progress: ${parts.join(" ")}`;
524
+ return ` ${parts.join(" ")}`;
521
525
  };
522
526
  const updateProgress = (completedIssue) => {
523
527
  if (mergedOptions.quiet)
@@ -533,7 +537,7 @@ export async function runCommand(issues, options) {
533
537
  ? ` (${formatElapsedTime(info.durationSeconds)})`
534
538
  : "";
535
539
  if (info.state === "done") {
536
- const line = ` ${colors.success("")} Issue #${completedIssue} completed${duration}`;
540
+ const line = ` ${colors.success("\u2714")} #${completedIssue} completed${duration}`;
537
541
  if (process.stdout.isTTY) {
538
542
  // Move to a new line before printing the summary, then re-render progress
539
543
  process.stdout.write(`\n${line}\n${renderProgressLine()}`);
@@ -544,7 +548,7 @@ export async function runCommand(issues, options) {
544
548
  }
545
549
  else {
546
550
  const errorSuffix = info.error ? `: ${info.error}` : "";
547
- const line = ` ${colors.error("")} Issue #${completedIssue} failed${duration}${errorSuffix}`;
551
+ const line = ` ${colors.error("\u2716")} #${completedIssue} failed${duration}${errorSuffix}`;
548
552
  if (process.stdout.isTTY) {
549
553
  process.stdout.write(`\n${line}\n${renderProgressLine()}`);
550
554
  }
@@ -556,31 +560,37 @@ export async function runCommand(issues, options) {
556
560
  };
557
561
  // Per-phase progress callback for parallel mode (AC-1, AC-3)
558
562
  const parallelStartTime = Date.now();
563
+ let lastPhaseEventTime = Date.now();
559
564
  const onPhaseProgress = (issue, phase, event, extra) => {
560
565
  if (mergedOptions.quiet)
561
566
  return;
567
+ lastPhaseEventTime = Date.now();
562
568
  let line;
563
569
  if (event === "start") {
564
- line = ` ${colors.warning("")} #${issue}: ${phase} started`;
570
+ line = ` ${colors.running("\u25B8")} #${issue} ${phase}`;
565
571
  }
566
572
  else if (event === "complete") {
567
573
  const dur = extra?.durationSeconds != null
568
- ? ` (${formatElapsedTime(extra.durationSeconds)})`
574
+ ? ` ${formatElapsedTime(extra.durationSeconds)}`
569
575
  : "";
570
- line = ` ${colors.success("")} #${issue}: ${phase} ✓${dur}`;
576
+ line = ` ${colors.success("\u2714")} #${issue} ${phase}${dur}`;
571
577
  }
572
578
  else {
573
- line = ` ${colors.error("")} #${issue}: ${phase} ✗`;
579
+ line = ` ${colors.error("\u2716")} #${issue} ${phase}`;
574
580
  }
575
581
  console.log(line);
576
582
  };
577
- // 60-second heartbeat timer so the terminal never appears frozen (AC-2)
578
- const HEARTBEAT_INTERVAL_MS = 60_000;
583
+ // 5-minute heartbeat timer, suppressed when phase events occur within window
584
+ const HEARTBEAT_INTERVAL_MS = 300_000;
585
+ const HEARTBEAT_SUPPRESS_MS = 60_000;
579
586
  const heartbeatTimer = setInterval(() => {
580
587
  if (mergedOptions.quiet)
581
588
  return;
589
+ // Suppress if a phase event occurred recently
590
+ if (Date.now() - lastPhaseEventTime < HEARTBEAT_SUPPRESS_MS)
591
+ return;
582
592
  const elapsedSec = Math.round((Date.now() - parallelStartTime) / 1000);
583
- console.log(` ${colors.warning("⏳")} Still running... (${formatElapsedTime(elapsedSec)} elapsed)`);
593
+ console.log(` ${colors.muted(`Still running... (${formatElapsedTime(elapsedSec)} elapsed)`)}`);
584
594
  }, HEARTBEAT_INTERVAL_MS);
585
595
  updateProgress();
586
596
  const settledResults = await Promise.allSettled(issueNumbers.map((issueNumber) => limit(async () => {
@@ -719,19 +729,19 @@ export async function runCommand(issues, options) {
719
729
  },
720
730
  });
721
731
  if (config.verbose) {
722
- console.log(chalk.gray(` 📊 Metrics recorded to .sequant/metrics.json`));
732
+ console.log(chalk.gray(` Metrics recorded to .sequant/metrics.json`));
723
733
  }
724
734
  }
725
735
  catch (metricsError) {
726
736
  // Metrics recording errors shouldn't stop execution
727
- logNonFatalWarning(` ⚠️ Metrics recording failed, continuing...`, metricsError, config.verbose);
737
+ logNonFatalWarning(` ! Metrics recording failed, continuing...`, metricsError, config.verbose);
728
738
  }
729
739
  }
730
740
  // Summary
731
741
  console.log("\n" + ui.divider());
732
742
  console.log(colors.info(" Summary"));
733
743
  console.log(ui.divider());
734
- console.log(colors.muted(`\n Results: ${colors.success(`${passed} passed`)}, ${colors.error(`${failed} failed`)}`));
744
+ console.log(`\n ${colors.success(`${passed} passed`)} ${colors.muted("\u00B7")} ${colors.error(`${failed} failed`)}`);
735
745
  for (const result of results) {
736
746
  const status = result.success
737
747
  ? ui.statusIcon("success")
@@ -750,7 +760,7 @@ export async function runCommand(issues, options) {
750
760
  }
751
761
  console.log("");
752
762
  if (logPath) {
753
- console.log(colors.muted(` 📝 Log: ${logPath}`));
763
+ console.log(colors.muted(` Log: ${logPath}`));
754
764
  console.log("");
755
765
  }
756
766
  // Reflection analysis (--reflect flag)
@@ -772,7 +782,7 @@ export async function runCommand(issues, options) {
772
782
  }
773
783
  // Suggest merge checks for multi-issue batches
774
784
  if (results.length > 1 && passed > 0 && !config.dryRun) {
775
- console.log(colors.muted(" 💡 Verify batch integration before merging:"));
785
+ console.log(colors.muted(" Tip: Verify batch integration before merging:"));
776
786
  console.log(colors.muted(" sequant merge --check"));
777
787
  console.log("");
778
788
  }
@@ -19,7 +19,7 @@ import { createIssueState } from "../lib/workflow/state-schema.js";
19
19
  */
20
20
  export async function stateInitCommand(options = {}) {
21
21
  if (!options.json) {
22
- console.log(chalk.bold("\n🔍 Discovering untracked worktrees...\n"));
22
+ console.log(chalk.bold("\nDiscovering untracked worktrees...\n"));
23
23
  }
24
24
  const discoverOptions = {
25
25
  verbose: options.verbose && !options.json,
@@ -86,7 +86,7 @@ export async function stateInitCommand(options = {}) {
86
86
  */
87
87
  export async function stateRebuildCommand(options = {}) {
88
88
  if (!options.json) {
89
- console.log(chalk.bold("\n🔄 Rebuilding state from scratch...\n"));
89
+ console.log(chalk.bold("\nRebuilding state from scratch...\n"));
90
90
  if (!options.force) {
91
91
  console.log(chalk.yellow("⚠ This will replace the existing state file. Use --force to proceed.\n"));
92
92
  return;
@@ -257,7 +257,7 @@ export async function stateCommand(subcommand, options = {}) {
257
257
  case "clean":
258
258
  return stateCleanCommand(options);
259
259
  default:
260
- console.log(chalk.bold("\n📊 sequant state - Manage workflow state\n"));
260
+ console.log(chalk.bold("\nsequant state - Manage workflow state\n"));
261
261
  console.log("Available subcommands:");
262
262
  console.log(chalk.gray(" init Populate state for untracked worktrees"));
263
263
  console.log(chalk.gray(" rebuild Recreate state from logs + worktrees"));
@@ -21,7 +21,7 @@ async function runReconciliation(stateManager, options) {
21
21
  if (!options.json) {
22
22
  // Show reconciliation warnings
23
23
  if (result.warnings.length > 0) {
24
- console.log(chalk.yellow("\n ⚠️ Drift detected:"));
24
+ console.log(chalk.yellow("\n ! Drift detected:"));
25
25
  for (const w of result.warnings) {
26
26
  console.log(chalk.yellow(` #${w.issueNumber}: ${w.description}`));
27
27
  }
@@ -33,7 +33,7 @@ async function runReconciliation(stateManager, options) {
33
33
  }
34
34
  }
35
35
  if (!result.githubReachable && !options.offline) {
36
- console.log(chalk.yellow("\n ⚠️ GitHub unreachable — showing cached data. Use --offline to suppress this warning."));
36
+ console.log(chalk.yellow("\n ! GitHub unreachable — showing cached data. Use --offline to suppress this warning."));
37
37
  }
38
38
  }
39
39
  return result;
@@ -331,7 +331,7 @@ async function displayIssueState(options) {
331
331
  console.log(JSON.stringify(jsonData, null, 2));
332
332
  }
333
333
  else if (issueState) {
334
- console.log(chalk.bold(`\n📊 Issue #${options.issue} State\n`));
334
+ console.log(chalk.bold(`\nIssue #${options.issue} State\n`));
335
335
  console.log(formatIssueState(issueState));
336
336
  const hint = getNextActionHint(issueState);
337
337
  if (hint) {
@@ -364,7 +364,7 @@ async function displayIssueState(options) {
364
364
  }, null, 2));
365
365
  }
366
366
  else {
367
- console.log(chalk.bold("\n📊 Workflow State\n"));
367
+ console.log(chalk.bold("\nWorkflow State\n"));
368
368
  displayIssueSummary(issues);
369
369
  // Last synced footer
370
370
  const syncedAgo = formatRelativeTime(reconcileResult.lastSynced);
@@ -387,7 +387,7 @@ async function displayIssueState(options) {
387
387
  */
388
388
  async function handleRebuild(options) {
389
389
  if (!options.json) {
390
- console.log(chalk.bold("\n🔄 Rebuilding state from logs...\n"));
390
+ console.log(chalk.bold("\nRebuilding state from logs...\n"));
391
391
  }
392
392
  const result = await rebuildStateFromLogs({ verbose: !options.json });
393
393
  if (options.json) {
@@ -49,8 +49,8 @@ async function updateSkillsVersion() {
49
49
  export async function syncCommand(options = {}) {
50
50
  const { force = false, quiet = false } = options;
51
51
  if (!quiet) {
52
- console.log(chalk.blue("\n🔄 Syncing templates...\n"));
53
- console.log(chalk.yellow("📢 Note: For seamless auto-updates, install sequant as a Claude Code plugin:\n" +
52
+ console.log(chalk.blue("\nSyncing templates...\n"));
53
+ console.log(chalk.yellow("Note: For seamless auto-updates, install sequant as a Claude Code plugin:\n" +
54
54
  " /plugin install sequant@claude-plugin-directory\n" +
55
55
  " Plugin users get auto-updates without running sync manually.\n"));
56
56
  }
@@ -71,7 +71,7 @@ export async function syncCommand(options = {}) {
71
71
  // Check if sync is needed
72
72
  if (!force && skillsVersion === packageVersion) {
73
73
  if (!quiet) {
74
- console.log(chalk.green(" Skills are already up to date!"));
74
+ console.log(chalk.green(" Skills are already up to date!"));
75
75
  }
76
76
  return;
77
77
  }
@@ -83,7 +83,7 @@ export async function syncCommand(options = {}) {
83
83
  force: true, // Always overwrite when syncing
84
84
  };
85
85
  if (!quiet) {
86
- console.log(chalk.blue("📥 Copying templates..."));
86
+ console.log(chalk.blue("Copying templates..."));
87
87
  }
88
88
  await copyTemplates(manifest.stack, tokens, copyOptions);
89
89
  // Update version markers
@@ -103,17 +103,17 @@ export async function syncCommand(options = {}) {
103
103
  });
104
104
  await writeAgentsMd(agentsMdContent);
105
105
  if (!quiet) {
106
- console.log(chalk.blue("📄 Regenerated AGENTS.md"));
106
+ console.log(chalk.blue("Regenerated AGENTS.md"));
107
107
  }
108
108
  }
109
109
  catch {
110
110
  if (!quiet) {
111
- console.log(chalk.yellow("⚠️ Could not regenerate AGENTS.md (non-blocking)"));
111
+ console.log(chalk.yellow("! Could not regenerate AGENTS.md (non-blocking)"));
112
112
  }
113
113
  }
114
114
  }
115
115
  if (!quiet) {
116
- console.log(chalk.green(`\n Synced to v${packageVersion}`));
116
+ console.log(chalk.green(`\n Synced to v${packageVersion}`));
117
117
  console.log(chalk.gray("\nSkills, hooks, and memory files have been updated."));
118
118
  }
119
119
  }
@@ -123,7 +123,7 @@ export async function syncCommand(options = {}) {
123
123
  export async function checkAndWarnSkillsOutdated() {
124
124
  const { outdated, currentVersion, packageVersion } = await areSkillsOutdated();
125
125
  if (outdated) {
126
- console.log(chalk.yellow(`\n⚠️ Skills are outdated (${currentVersion || "unknown"} → ${packageVersion})`));
126
+ console.log(chalk.yellow(`\n! Skills are outdated (${currentVersion || "unknown"} → ${packageVersion})`));
127
127
  console.log(chalk.yellow(" Run: npx sequant sync\n"));
128
128
  return true;
129
129
  }
@@ -11,8 +11,8 @@ import { getConfig, saveConfig } from "../lib/config.js";
11
11
  import { getStackConfig, PM_CONFIG, getPackageManagerCommands, } from "../lib/stacks.js";
12
12
  import { readFile, writeFile, fileExists } from "../lib/fs.js";
13
13
  export async function updateCommand(options) {
14
- console.log(chalk.blue("\n🔄 Checking for updates...\n"));
15
- console.log(chalk.yellow("📢 Note: For seamless auto-updates, install sequant as a Claude Code plugin:\n" +
14
+ console.log(chalk.blue("\nChecking for updates...\n"));
15
+ console.log(chalk.yellow("Note: For seamless auto-updates, install sequant as a Claude Code plugin:\n" +
16
16
  " /plugin install sequant@claude-plugin-directory\n" +
17
17
  " Plugin users get auto-updates without running update manually.\n"));
18
18
  // Check if initialized
@@ -31,7 +31,7 @@ export async function updateCommand(options) {
31
31
  const manifestNum = mMajor * 10000 + mMinor * 100 + mPatch;
32
32
  const packageNum = pMajor * 10000 + pMinor * 100 + pPatch;
33
33
  if (packageNum < manifestNum) {
34
- console.log(chalk.yellow(`⚠️ Warning: You're running an older CLI version (${packageVersion}) than installed (${manifest.version}).`));
34
+ console.log(chalk.yellow(`! Warning: You're running an older CLI version (${packageVersion}) than installed (${manifest.version}).`));
35
35
  console.log(chalk.yellow(` Run with: npx sequant@latest update\n`));
36
36
  }
37
37
  // Get config with tokens (or migrate legacy installs)
@@ -47,12 +47,12 @@ export async function updateCommand(options) {
47
47
  tokens.PM_RUN = pmConfig.run;
48
48
  config.tokens = tokens;
49
49
  await saveConfig(config);
50
- console.log(chalk.blue(`📝 Added PM_RUN token: ${tokens.PM_RUN}\n`));
50
+ console.log(chalk.blue(`Added PM_RUN token: ${tokens.PM_RUN}\n`));
51
51
  }
52
52
  }
53
53
  else {
54
54
  // First-time config setup
55
- console.log(chalk.blue("📝 Setting up configuration (one-time setup)\n"));
55
+ console.log(chalk.blue("Setting up configuration (one-time setup)\n"));
56
56
  const stackConfig = getStackConfig(manifest.stack);
57
57
  const defaultDevUrl = stackConfig.devUrl;
58
58
  // Get package manager run command
@@ -60,7 +60,7 @@ export async function updateCommand(options) {
60
60
  const pmConfig = getPackageManagerCommands(pm);
61
61
  if (options.force) {
62
62
  tokens = { DEV_URL: defaultDevUrl, PM_RUN: pmConfig.run };
63
- console.log(chalk.blue(`🌐 Using default dev URL: ${defaultDevUrl}`));
63
+ console.log(chalk.blue(`Using default dev URL: ${defaultDevUrl}`));
64
64
  }
65
65
  else {
66
66
  const { inputDevUrl } = await inquirer.prompt([
@@ -80,7 +80,7 @@ export async function updateCommand(options) {
80
80
  initialized: manifest.installedAt,
81
81
  };
82
82
  await saveConfig(config);
83
- console.log(chalk.green(" Configuration saved\n"));
83
+ console.log(chalk.green(" Configuration saved\n"));
84
84
  }
85
85
  // Get list of template files
86
86
  const templateFiles = await listTemplateFiles();
@@ -130,12 +130,12 @@ export async function updateCommand(options) {
130
130
  const unchangedFiles = changes.filter((c) => c.status === "unchanged");
131
131
  const localOverrides = changes.filter((c) => c.status === "local-override");
132
132
  console.log(chalk.bold("Summary:"));
133
- console.log(chalk.green(` New files: ${newFiles.length}`));
134
- console.log(chalk.yellow(` 📝 Modified: ${modifiedFiles.length}`));
133
+ console.log(chalk.green(` New files: ${newFiles.length}`));
134
+ console.log(chalk.yellow(` Modified: ${modifiedFiles.length}`));
135
135
  console.log(chalk.gray(` ✓ Unchanged: ${unchangedFiles.length}`));
136
- console.log(chalk.blue(` 🔒 Local overrides: ${localOverrides.length}`));
136
+ console.log(chalk.blue(` Local overrides: ${localOverrides.length}`));
137
137
  if (newFiles.length === 0 && modifiedFiles.length === 0) {
138
- console.log(chalk.green("\n Everything is up to date!"));
138
+ console.log(chalk.green("\n Everything is up to date!"));
139
139
  return;
140
140
  }
141
141
  // Show changes
@@ -174,7 +174,7 @@ export async function updateCommand(options) {
174
174
  }
175
175
  }
176
176
  // Apply updates
177
- console.log(chalk.blue("\n📥 Applying updates..."));
177
+ console.log(chalk.blue("\nApplying updates..."));
178
178
  let updated = 0;
179
179
  // Build complete variables for template processing
180
180
  const stackConfig = getStackConfig(manifest.stack);
@@ -194,24 +194,24 @@ export async function updateCommand(options) {
194
194
  }
195
195
  // Update manifest
196
196
  await updateManifest();
197
- console.log(chalk.green(`\n Updated ${updated} files`));
197
+ console.log(chalk.green(`\n Updated ${updated} files`));
198
198
  // Check if package.json was updated and run install
199
199
  const packageJsonUpdated = [...newFiles, ...modifiedFiles].some((f) => f.path === "package.json" || f.path.endsWith("/package.json"));
200
200
  if (packageJsonUpdated) {
201
201
  // Use detected package manager or default to npm
202
202
  const pm = manifest.packageManager || "npm";
203
203
  const pmConfig = PM_CONFIG[pm];
204
- console.log(chalk.blue(`\n📦 package.json updated, running ${pmConfig.install}...`));
204
+ console.log(chalk.blue(`\npackage.json updated, running ${pmConfig.install}...`));
205
205
  const [cmd, ...args] = pmConfig.install.split(" ");
206
206
  const result = spawnSync(cmd, args, {
207
207
  stdio: "inherit",
208
208
  shell: true,
209
209
  });
210
210
  if (result.status === 0) {
211
- console.log(chalk.green(" Dependencies installed"));
211
+ console.log(chalk.green(" Dependencies installed"));
212
212
  }
213
213
  else {
214
- console.log(chalk.yellow(`⚠️ ${pmConfig.install} failed - run manually`));
214
+ console.log(chalk.yellow(`! ${pmConfig.install} failed - run manually`));
215
215
  }
216
216
  }
217
217
  }
@@ -155,7 +155,7 @@ class TextSpinner {
155
155
  this.text = text;
156
156
  this.isSpinning = true;
157
157
  if (!config.jsonMode) {
158
- console.log(getColor(chalk.cyan)(`\u23F3 ${this.text}`));
158
+ console.log(getColor(chalk.cyan)(`\u25B8 ${this.text}`));
159
159
  }
160
160
  return this;
161
161
  }
@@ -164,7 +164,7 @@ class TextSpinner {
164
164
  this.text = text;
165
165
  this.isSpinning = false;
166
166
  if (!config.jsonMode) {
167
- console.log(getColor(chalk.green)(`\u2713 ${this.text}`));
167
+ console.log(getColor(chalk.green)(`\u2714 ${this.text}`));
168
168
  }
169
169
  return this;
170
170
  }
@@ -173,7 +173,7 @@ class TextSpinner {
173
173
  this.text = text;
174
174
  this.isSpinning = false;
175
175
  if (!config.jsonMode) {
176
- console.log(getColor(chalk.red)(`\u2717 ${this.text}`));
176
+ console.log(getColor(chalk.red)(`\u2716 ${this.text}`));
177
177
  }
178
178
  return this;
179
179
  }
@@ -182,7 +182,7 @@ class TextSpinner {
182
182
  this.text = text;
183
183
  this.isSpinning = false;
184
184
  if (!config.jsonMode) {
185
- console.log(getColor(chalk.yellow)(`\u26A0 ${this.text}`));
185
+ console.log(getColor(chalk.yellow)(`! ${this.text}`));
186
186
  }
187
187
  return this;
188
188
  }
@@ -301,29 +301,30 @@ export function box(content, style = "default") {
301
301
  * Create a success box with title and message
302
302
  */
303
303
  export function successBox(title, message) {
304
- const content = `${getColor(chalk.green.bold)(`\u2705 ${title}`)}\n\n${message}`;
304
+ const content = `${getColor(chalk.green.bold)(title)}\n\n${message}`;
305
305
  return box(content, "success");
306
306
  }
307
307
  /**
308
308
  * Create an error box with title and message
309
309
  */
310
310
  export function errorBox(title, message) {
311
- const content = `${getColor(chalk.red.bold)(`\u274C ${title}`)}\n\n${message}`;
311
+ const content = `${getColor(chalk.red.bold)(title)}\n\n${message}`;
312
312
  return box(content, "error");
313
313
  }
314
314
  /**
315
315
  * Create a warning box with title and message
316
316
  */
317
317
  export function warningBox(title, message) {
318
- const content = `${getColor(chalk.yellow.bold)(`\u26A0\uFE0F ${title}`)}\n\n${message}`;
318
+ const content = `${getColor(chalk.yellow.bold)(title)}\n\n${message}`;
319
319
  return box(content, "warning");
320
320
  }
321
321
  /**
322
322
  * Create a header box
323
323
  */
324
324
  export function headerBox(title) {
325
- const content = getColor(chalk.bold)(title);
326
- return box(content, "header");
325
+ if (config.jsonMode)
326
+ return "";
327
+ return getColor(chalk.bold)(title);
327
328
  }
328
329
  /**
329
330
  * Create a formatted table
@@ -424,15 +425,15 @@ export function statusIcon(type) {
424
425
  }
425
426
  switch (type) {
426
427
  case "success":
427
- return chalk.green("\u2713");
428
+ return chalk.green("\u2714");
428
429
  case "error":
429
- return chalk.red("\u2717");
430
+ return chalk.red("\u2716");
430
431
  case "warning":
431
- return chalk.yellow("\u26A0");
432
+ return chalk.yellow("!");
432
433
  case "pending":
433
- return chalk.gray("\u25CB");
434
+ return chalk.gray("\u00B7");
434
435
  case "running":
435
- return chalk.cyan("\u25D0");
436
+ return chalk.cyan("\u25B8");
436
437
  }
437
438
  }
438
439
  /**
@@ -452,7 +453,7 @@ export function printStatus(type, message) {
452
453
  export function divider(width = 50) {
453
454
  if (config.jsonMode)
454
455
  return "";
455
- const char = isLegacyWindows() ? "-" : "\u2501";
456
+ const char = isLegacyWindows() ? "-" : "\u2500";
456
457
  const line = char.repeat(width);
457
458
  return config.noColor ? line : chalk.gray(line);
458
459
  }
@@ -474,15 +475,15 @@ export function phaseProgress(phases) {
474
475
  const icons = phases.map((phase) => {
475
476
  switch (phase.status) {
476
477
  case "success":
477
- return config.noColor ? "[OK]" : chalk.green("\u25CF");
478
+ return config.noColor ? "[OK]" : chalk.green("\u2714");
478
479
  case "failure":
479
- return config.noColor ? "[X]" : chalk.red("\u2717");
480
+ return config.noColor ? "[X]" : chalk.red("\u2716");
480
481
  case "running":
481
- return config.noColor ? "[..]" : chalk.cyan("\u25D0");
482
+ return config.noColor ? "[..]" : chalk.cyan("\u25B8");
482
483
  case "skipped":
483
484
  return config.noColor ? "[-]" : chalk.gray("-");
484
485
  default:
485
- return config.noColor ? "[ ]" : chalk.gray("\u25CB");
486
+ return config.noColor ? "[ ]" : chalk.gray("\u00B7");
486
487
  }
487
488
  });
488
489
  const labels = phases.map((phase) => phase.name.charAt(0).toUpperCase());
@@ -106,7 +106,7 @@ export function resolveBranches(issueNumbers, repoRoot, runLog) {
106
106
  }
107
107
  // Get modified files from the branch
108
108
  const worktreePath = worktreePaths.get(branch);
109
- let filesModified = [];
109
+ let filesModified;
110
110
  if (worktreePath) {
111
111
  // Use worktree for diff
112
112
  const diffStats = getGitDiffStats(worktreePath);
@@ -172,7 +172,7 @@ export function getChecksToRun(options) {
172
172
  */
173
173
  export async function runMergeChecks(issueNumbers, options, repoRoot) {
174
174
  const logDir = resolveLogDir();
175
- let runLog = null;
175
+ let runLog;
176
176
  // Auto-detect issues from most recent run log if none specified
177
177
  if (issueNumbers.length === 0) {
178
178
  runLog = findMostRecentLog(logDir);
@@ -44,6 +44,14 @@ export interface AgentSettings {
44
44
  * Default: "haiku"
45
45
  */
46
46
  model: "haiku" | "sonnet" | "opus";
47
+ /**
48
+ * Isolate parallel agent groups in separate worktrees.
49
+ * When true, each agent in a parallel group gets its own sub-worktree,
50
+ * eliminating file conflicts structurally. Changes are merged back
51
+ * into the issue worktree after all agents complete.
52
+ * Default: false (opt-in for v1)
53
+ */
54
+ isolateParallel: boolean;
47
55
  }
48
56
  /**
49
57
  * Aider-specific settings for the aider agent driver.