sequant 1.13.0 → 1.13.2

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.
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
3
+ "name": "sequant",
4
+ "description": "Sequant plugin marketplace - structured workflow system for Claude Code",
5
+ "owner": {
6
+ "name": "admarble",
7
+ "email": "github@admarble.com"
8
+ },
9
+ "plugins": [
10
+ {
11
+ "name": "sequant",
12
+ "description": "Structured workflow system for Claude Code - GitHub issue resolution with spec, exec, test, and QA phases. Includes 16 skills for planning, implementation, testing, code review, and quality assurance.",
13
+ "version": "1.13.0",
14
+ "author": {
15
+ "name": "admarble",
16
+ "email": "github@admarble.com"
17
+ },
18
+ "source": "./",
19
+ "category": "workflow"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "sequant",
3
+ "description": "Structured workflow system for Claude Code - GitHub issue resolution with spec, exec, test, and QA phases",
4
+ "version": "1.13.2",
5
+ "author": {
6
+ "name": "admarble",
7
+ "email": "github@admarble.com"
8
+ }
9
+ }
package/dist/bin/cli.js CHANGED
@@ -43,6 +43,7 @@ import { logsCommand } from "../src/commands/logs.js";
43
43
  import { statsCommand } from "../src/commands/stats.js";
44
44
  import { dashboardCommand } from "../src/commands/dashboard.js";
45
45
  import { stateInitCommand, stateRebuildCommand, stateCleanCommand, } from "../src/commands/state.js";
46
+ import { syncCommand } from "../src/commands/sync.js";
46
47
  const program = new Command();
47
48
  // Handle --no-color before parsing
48
49
  if (process.argv.includes("--no-color")) {
@@ -85,6 +86,12 @@ program
85
86
  .option("-d, --dry-run", "Show what would be updated without making changes")
86
87
  .option("-f, --force", "Overwrite local modifications")
87
88
  .action(updateCommand);
89
+ program
90
+ .command("sync")
91
+ .description("Sync skills and templates from the Sequant package (non-interactive)")
92
+ .option("-f, --force", "Sync even if versions match")
93
+ .option("-q, --quiet", "Suppress output")
94
+ .action(syncCommand);
88
95
  program
89
96
  .command("doctor")
90
97
  .description("Check your Sequant installation for issues")
@@ -8,6 +8,7 @@ import { fileExists, isExecutable } from "../lib/fs.js";
8
8
  import { getManifest } from "../lib/manifest.js";
9
9
  import { commandExists, isGhAuthenticated, isNativeWindows, isWSL, checkOptionalMcpServers, getMcpServersConfig, OPTIONAL_MCP_SERVERS, } from "../lib/system.js";
10
10
  import { checkVersionThorough, getVersionWarning, } from "../lib/version-check.js";
11
+ import { areSkillsOutdated } from "./sync.js";
11
12
  /**
12
13
  * Labels that indicate an issue should be skipped from closed-issue verification
13
14
  * (case-insensitive matching)
@@ -157,6 +158,22 @@ export async function doctorCommand(options = {}) {
157
158
  message: `Missing skills: ${missingSkills.join(", ")}`,
158
159
  });
159
160
  }
161
+ // Check 3.5: Skills version (are skills in sync with package?)
162
+ const skillsStatus = await areSkillsOutdated();
163
+ if (skillsStatus.outdated) {
164
+ checks.push({
165
+ name: "Skills Version",
166
+ status: "warn",
167
+ message: `Outdated (${skillsStatus.currentVersion || "unknown"} → ${skillsStatus.packageVersion}) - run: npx sequant sync`,
168
+ });
169
+ }
170
+ else {
171
+ checks.push({
172
+ name: "Skills Version",
173
+ status: "pass",
174
+ message: `Up to date (${skillsStatus.packageVersion})`,
175
+ });
176
+ }
160
177
  // Check 4: Hooks directory
161
178
  const hooksExist = await fileExists(".claude/hooks");
162
179
  if (hooksExist) {
@@ -21,6 +21,7 @@ import { checkVersionCached, getVersionWarning } from "../lib/version-check.js";
21
21
  import { MetricsWriter } from "../lib/workflow/metrics-writer.js";
22
22
  import { determineOutcome, } from "../lib/workflow/metrics-schema.js";
23
23
  import { ui, colors } from "../lib/cli-ui.js";
24
+ import { PhaseSpinner } from "../lib/phase-spinner.js";
24
25
  /**
25
26
  * Slugify a title for branch naming
26
27
  */
@@ -581,7 +582,7 @@ const ISOLATED_PHASES = ["exec", "test", "qa"];
581
582
  /**
582
583
  * Execute a single phase for an issue using Claude Agent SDK
583
584
  */
584
- async function executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager) {
585
+ async function executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner) {
585
586
  const startTime = Date.now();
586
587
  if (config.dryRun) {
587
588
  // Dry run - just simulate
@@ -686,7 +687,10 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath,
686
687
  capturedOutput += textContent;
687
688
  // Show streaming output in verbose mode
688
689
  if (config.verbose) {
690
+ // Pause spinner during verbose streaming to avoid terminal corruption
691
+ spinner?.pause();
689
692
  process.stdout.write(chalk.gray(textContent));
693
+ spinner?.resume();
690
694
  }
691
695
  }
692
696
  }
@@ -1516,7 +1520,14 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
1516
1520
  else {
1517
1521
  // Run spec first to get recommended workflow
1518
1522
  console.log(chalk.gray(` Running spec to determine workflow...`));
1519
- console.log(chalk.gray(` ⏳ spec...`));
1523
+ // Create spinner for spec phase (1 of estimated 3: spec, exec, qa)
1524
+ const specSpinner = new PhaseSpinner({
1525
+ phase: "spec",
1526
+ phaseIndex: 1,
1527
+ totalPhases: 3, // Estimate; will be refined after spec
1528
+ shutdownManager,
1529
+ });
1530
+ specSpinner.start();
1520
1531
  // Track spec phase start in state
1521
1532
  if (stateManager) {
1522
1533
  try {
@@ -1529,7 +1540,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
1529
1540
  const specStartTime = new Date();
1530
1541
  // Note: spec runs in main repo (not worktree) for planning
1531
1542
  const specResult = await executePhase(issueNumber, "spec", config, sessionId, worktreePath, // Will be ignored for spec (non-isolated phase)
1532
- shutdownManager);
1543
+ shutdownManager, specSpinner);
1533
1544
  const specEndTime = new Date();
1534
1545
  if (specResult.sessionId) {
1535
1546
  sessionId = specResult.sessionId;
@@ -1567,7 +1578,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
1567
1578
  }
1568
1579
  }
1569
1580
  if (!specResult.success) {
1570
- console.log(chalk.red(` ✗ spec: ${specResult.error}`));
1581
+ specSpinner.fail(specResult.error);
1571
1582
  const durationSeconds = (Date.now() - startTime) / 1000;
1572
1583
  return {
1573
1584
  issueNumber,
@@ -1577,10 +1588,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
1577
1588
  loopTriggered: false,
1578
1589
  };
1579
1590
  }
1580
- const duration = specResult.durationSeconds
1581
- ? ` (${formatDuration(specResult.durationSeconds)})`
1582
- : "";
1583
- console.log(chalk.green(` ✓ spec${duration}`));
1591
+ specSpinner.succeed();
1584
1592
  // Parse recommended workflow from spec output
1585
1593
  const parsedWorkflow = specResult.output
1586
1594
  ? parseRecommendedWorkflow(specResult.output)
@@ -1634,8 +1642,22 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
1634
1642
  loopTriggered = true;
1635
1643
  }
1636
1644
  let phasesFailed = false;
1637
- for (const phase of phases) {
1638
- console.log(chalk.gray(` ⏳ ${phase}...`));
1645
+ // Calculate total phases for progress indicator
1646
+ // If spec already ran in auto-detect mode, it's counted separately
1647
+ const totalPhases = specAlreadyRan ? phases.length + 1 : phases.length;
1648
+ const phaseIndexOffset = specAlreadyRan ? 1 : 0;
1649
+ for (let phaseIdx = 0; phaseIdx < phases.length; phaseIdx++) {
1650
+ const phase = phases[phaseIdx];
1651
+ const phaseNumber = phaseIdx + 1 + phaseIndexOffset;
1652
+ // Create spinner for this phase
1653
+ const phaseSpinner = new PhaseSpinner({
1654
+ phase,
1655
+ phaseIndex: phaseNumber,
1656
+ totalPhases,
1657
+ shutdownManager,
1658
+ iteration: useQualityLoop ? iteration : undefined,
1659
+ });
1660
+ phaseSpinner.start();
1639
1661
  // Track phase start in state
1640
1662
  if (stateManager) {
1641
1663
  try {
@@ -1646,7 +1668,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
1646
1668
  }
1647
1669
  }
1648
1670
  const phaseStartTime = new Date();
1649
- const result = await executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager);
1671
+ const result = await executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, phaseSpinner);
1650
1672
  const phaseEndTime = new Date();
1651
1673
  // Capture session ID for subsequent phases
1652
1674
  if (result.sessionId) {
@@ -1690,29 +1712,34 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
1690
1712
  }
1691
1713
  }
1692
1714
  if (result.success) {
1693
- const duration = result.durationSeconds
1694
- ? ` (${formatDuration(result.durationSeconds)})`
1695
- : "";
1696
- console.log(chalk.green(` ✓ ${phase}${duration}`));
1715
+ phaseSpinner.succeed();
1697
1716
  }
1698
1717
  else {
1699
- console.log(chalk.red(` ✗ ${phase}: ${result.error}`));
1718
+ phaseSpinner.fail(result.error);
1700
1719
  phasesFailed = true;
1701
1720
  // If quality loop enabled, run loop phase to fix issues
1702
1721
  if (useQualityLoop && iteration < maxIterations) {
1703
- console.log(chalk.yellow(` Running /loop to fix issues...`));
1704
- const loopResult = await executePhase(issueNumber, "loop", config, sessionId, worktreePath, shutdownManager);
1722
+ // Create spinner for loop phase
1723
+ const loopSpinner = new PhaseSpinner({
1724
+ phase: "loop",
1725
+ phaseIndex: phaseNumber,
1726
+ totalPhases,
1727
+ shutdownManager,
1728
+ iteration,
1729
+ });
1730
+ loopSpinner.start();
1731
+ const loopResult = await executePhase(issueNumber, "loop", config, sessionId, worktreePath, shutdownManager, loopSpinner);
1705
1732
  phaseResults.push(loopResult);
1706
1733
  if (loopResult.sessionId) {
1707
1734
  sessionId = loopResult.sessionId;
1708
1735
  }
1709
1736
  if (loopResult.success) {
1710
- console.log(chalk.green(` ✓ loop - retrying phases`));
1737
+ loopSpinner.succeed();
1711
1738
  // Continue to next iteration
1712
1739
  break;
1713
1740
  }
1714
1741
  else {
1715
- console.log(chalk.red(` ✗ loop: ${loopResult.error}`));
1742
+ loopSpinner.fail(loopResult.error);
1716
1743
  }
1717
1744
  }
1718
1745
  // Stop on first failure (if not in quality loop or loop failed)
@@ -0,0 +1,28 @@
1
+ /**
2
+ * sequant sync - Fast, non-interactive template sync
3
+ *
4
+ * Syncs skills and other templates from the package to the local project.
5
+ * Designed for plugin users who need to update after upgrading sequant.
6
+ */
7
+ interface SyncOptions {
8
+ force?: boolean;
9
+ quiet?: boolean;
10
+ }
11
+ /**
12
+ * Get the version of skills currently installed
13
+ */
14
+ export declare function getSkillsVersion(): Promise<string | null>;
15
+ /**
16
+ * Check if skills are outdated compared to package version
17
+ */
18
+ export declare function areSkillsOutdated(): Promise<{
19
+ outdated: boolean;
20
+ currentVersion: string | null;
21
+ packageVersion: string;
22
+ }>;
23
+ export declare function syncCommand(options?: SyncOptions): Promise<void>;
24
+ /**
25
+ * Check and warn if skills are outdated (for use by other commands)
26
+ */
27
+ export declare function checkAndWarnSkillsOutdated(): Promise<boolean>;
28
+ export {};
@@ -0,0 +1,102 @@
1
+ /**
2
+ * sequant sync - Fast, non-interactive template sync
3
+ *
4
+ * Syncs skills and other templates from the package to the local project.
5
+ * Designed for plugin users who need to update after upgrading sequant.
6
+ */
7
+ import chalk from "chalk";
8
+ import { getManifest, updateManifest, getPackageVersion, } from "../lib/manifest.js";
9
+ import { copyTemplates } from "../lib/templates.js";
10
+ import { getConfig } from "../lib/config.js";
11
+ import { writeFile, readFile, fileExists } from "../lib/fs.js";
12
+ const SKILLS_VERSION_PATH = ".claude/skills/.sequant-version";
13
+ /**
14
+ * Get the version of skills currently installed
15
+ */
16
+ export async function getSkillsVersion() {
17
+ if (!(await fileExists(SKILLS_VERSION_PATH))) {
18
+ return null;
19
+ }
20
+ try {
21
+ const content = await readFile(SKILLS_VERSION_PATH);
22
+ return content.trim();
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ /**
29
+ * Check if skills are outdated compared to package version
30
+ */
31
+ export async function areSkillsOutdated() {
32
+ const currentVersion = await getSkillsVersion();
33
+ const packageVersion = getPackageVersion();
34
+ return {
35
+ outdated: currentVersion !== packageVersion,
36
+ currentVersion,
37
+ packageVersion,
38
+ };
39
+ }
40
+ /**
41
+ * Update the skills version marker
42
+ */
43
+ async function updateSkillsVersion() {
44
+ await writeFile(SKILLS_VERSION_PATH, getPackageVersion());
45
+ }
46
+ export async function syncCommand(options = {}) {
47
+ const { force = false, quiet = false } = options;
48
+ if (!quiet) {
49
+ console.log(chalk.blue("\n🔄 Syncing templates...\n"));
50
+ }
51
+ // Check if initialized
52
+ const manifest = await getManifest();
53
+ if (!manifest) {
54
+ console.log(chalk.red("❌ Sequant is not initialized. Run `sequant init` first."));
55
+ process.exitCode = 1;
56
+ return;
57
+ }
58
+ const packageVersion = getPackageVersion();
59
+ const skillsVersion = await getSkillsVersion();
60
+ if (!quiet) {
61
+ console.log(chalk.gray(`Skills version: ${skillsVersion || "(unknown)"}`));
62
+ console.log(chalk.gray(`Package version: ${packageVersion}`));
63
+ console.log(chalk.gray(`Stack: ${manifest.stack}\n`));
64
+ }
65
+ // Check if sync is needed
66
+ if (!force && skillsVersion === packageVersion) {
67
+ if (!quiet) {
68
+ console.log(chalk.green("✅ Skills are already up to date!"));
69
+ }
70
+ return;
71
+ }
72
+ // Get config tokens for template processing
73
+ const config = await getConfig();
74
+ const tokens = config?.tokens || {};
75
+ // Copy templates with force to overwrite existing files
76
+ const copyOptions = {
77
+ force: true, // Always overwrite when syncing
78
+ };
79
+ if (!quiet) {
80
+ console.log(chalk.blue("📥 Copying templates..."));
81
+ }
82
+ await copyTemplates(manifest.stack, tokens, copyOptions);
83
+ // Update version markers
84
+ await updateSkillsVersion();
85
+ await updateManifest();
86
+ if (!quiet) {
87
+ console.log(chalk.green(`\n✅ Synced to v${packageVersion}`));
88
+ console.log(chalk.gray("\nSkills, hooks, and memory files have been updated."));
89
+ }
90
+ }
91
+ /**
92
+ * Check and warn if skills are outdated (for use by other commands)
93
+ */
94
+ export async function checkAndWarnSkillsOutdated() {
95
+ const { outdated, currentVersion, packageVersion } = await areSkillsOutdated();
96
+ if (outdated) {
97
+ console.log(chalk.yellow(`\n⚠️ Skills are outdated (${currentVersion || "unknown"} → ${packageVersion})`));
98
+ console.log(chalk.yellow(" Run: npx sequant sync\n"));
99
+ return true;
100
+ }
101
+ return false;
102
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Phase Spinner - Animated spinner with elapsed time for phase execution
3
+ *
4
+ * Wraps the cli-ui spinner with:
5
+ * - Elapsed time tracking (updates every 5 seconds)
6
+ * - Phase progress indicator (e.g., "spec (1/3)")
7
+ * - Integration with ShutdownManager for graceful cleanup
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const spinner = new PhaseSpinner({
12
+ * phase: 'exec',
13
+ * phaseIndex: 2,
14
+ * totalPhases: 3,
15
+ * shutdownManager,
16
+ * });
17
+ *
18
+ * spinner.start();
19
+ * // ... phase execution
20
+ * spinner.succeed(); // Shows "✓ exec (2/3) (45s)"
21
+ * ```
22
+ */
23
+ import type { ShutdownManager } from "./shutdown.js";
24
+ /**
25
+ * Options for creating a PhaseSpinner
26
+ */
27
+ export interface PhaseSpinnerOptions {
28
+ /** Phase name (e.g., "spec", "exec", "qa") */
29
+ phase: string;
30
+ /** Current phase index (1-based) */
31
+ phaseIndex: number;
32
+ /** Total number of phases */
33
+ totalPhases: number;
34
+ /** Optional ShutdownManager for graceful cleanup */
35
+ shutdownManager?: ShutdownManager;
36
+ /** Optional prefix for indentation (default: " ") */
37
+ prefix?: string;
38
+ /** Optional quality loop iteration (shows "iteration N" if > 1) */
39
+ iteration?: number;
40
+ }
41
+ /**
42
+ * Format elapsed time in human-readable format
43
+ *
44
+ * @param seconds - Elapsed time in seconds
45
+ * @returns Formatted string (e.g., "45s", "2m 15s", "1h 5m")
46
+ */
47
+ export declare function formatElapsedTime(seconds: number): string;
48
+ /**
49
+ * PhaseSpinner - Animated spinner with elapsed time and phase progress
50
+ *
51
+ * Features:
52
+ * - Animated spinner (or text fallback) via cli-ui
53
+ * - Elapsed time tracking with automatic updates
54
+ * - Phase progress indicator (e.g., "exec (2/3)")
55
+ * - ShutdownManager integration for graceful Ctrl+C cleanup
56
+ * - Pause/resume for verbose streaming output
57
+ */
58
+ export declare class PhaseSpinner {
59
+ private spinner;
60
+ private startTime;
61
+ private intervalId;
62
+ private disposed;
63
+ private paused;
64
+ private readonly phase;
65
+ private readonly phaseIndex;
66
+ private readonly totalPhases;
67
+ private readonly shutdownManager?;
68
+ private readonly prefix;
69
+ private readonly iteration?;
70
+ private readonly cleanupName;
71
+ constructor(options: PhaseSpinnerOptions);
72
+ /**
73
+ * Format the spinner text with phase, progress, and elapsed time
74
+ */
75
+ private formatText;
76
+ /**
77
+ * Update the spinner text with current elapsed time
78
+ */
79
+ private updateElapsedTime;
80
+ /**
81
+ * Start the spinner
82
+ *
83
+ * Begins the animation and elapsed time tracking.
84
+ */
85
+ start(): PhaseSpinner;
86
+ /**
87
+ * Mark the phase as succeeded
88
+ *
89
+ * Stops the spinner and shows a checkmark with total duration.
90
+ */
91
+ succeed(customText?: string): PhaseSpinner;
92
+ /**
93
+ * Mark the phase as failed
94
+ *
95
+ * Stops the spinner and shows an error indicator with message.
96
+ */
97
+ fail(error?: string): PhaseSpinner;
98
+ /**
99
+ * Stop the spinner without success/fail indication
100
+ *
101
+ * Use this for cleanup or when the phase is interrupted.
102
+ */
103
+ stop(): PhaseSpinner;
104
+ /**
105
+ * Pause the spinner (for verbose streaming output)
106
+ *
107
+ * Temporarily stops the animation without disposing.
108
+ * Call resume() to continue.
109
+ */
110
+ pause(): PhaseSpinner;
111
+ /**
112
+ * Resume the spinner after pause
113
+ *
114
+ * Restarts the animation with updated elapsed time.
115
+ */
116
+ resume(): PhaseSpinner;
117
+ /**
118
+ * Check if the spinner is currently active
119
+ */
120
+ get isSpinning(): boolean;
121
+ /**
122
+ * Get the total elapsed time in seconds
123
+ */
124
+ get elapsedSeconds(): number;
125
+ /**
126
+ * Dispose of the spinner and clean up resources
127
+ *
128
+ * Called automatically by succeed(), fail(), or stop().
129
+ * Safe to call multiple times.
130
+ */
131
+ dispose(): void;
132
+ /**
133
+ * Clear the elapsed time update interval
134
+ */
135
+ private clearInterval;
136
+ /**
137
+ * Unregister from ShutdownManager
138
+ */
139
+ private unregisterCleanup;
140
+ }
141
+ /**
142
+ * Create a PhaseSpinner with the given options
143
+ *
144
+ * Convenience function matching the pattern of cli-ui.spinner().
145
+ */
146
+ export declare function phaseSpinner(options: PhaseSpinnerOptions): PhaseSpinner;
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Phase Spinner - Animated spinner with elapsed time for phase execution
3
+ *
4
+ * Wraps the cli-ui spinner with:
5
+ * - Elapsed time tracking (updates every 5 seconds)
6
+ * - Phase progress indicator (e.g., "spec (1/3)")
7
+ * - Integration with ShutdownManager for graceful cleanup
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const spinner = new PhaseSpinner({
12
+ * phase: 'exec',
13
+ * phaseIndex: 2,
14
+ * totalPhases: 3,
15
+ * shutdownManager,
16
+ * });
17
+ *
18
+ * spinner.start();
19
+ * // ... phase execution
20
+ * spinner.succeed(); // Shows "✓ exec (2/3) (45s)"
21
+ * ```
22
+ */
23
+ import { spinner as createSpinner } from "./cli-ui.js";
24
+ /**
25
+ * Elapsed time update interval in milliseconds
26
+ */
27
+ const ELAPSED_UPDATE_INTERVAL_MS = 5000;
28
+ /**
29
+ * Format elapsed time in human-readable format
30
+ *
31
+ * @param seconds - Elapsed time in seconds
32
+ * @returns Formatted string (e.g., "45s", "2m 15s", "1h 5m")
33
+ */
34
+ export function formatElapsedTime(seconds) {
35
+ if (seconds < 60) {
36
+ return `${Math.floor(seconds)}s`;
37
+ }
38
+ if (seconds < 3600) {
39
+ const minutes = Math.floor(seconds / 60);
40
+ const remainingSeconds = Math.floor(seconds % 60);
41
+ return remainingSeconds > 0
42
+ ? `${minutes}m ${remainingSeconds}s`
43
+ : `${minutes}m`;
44
+ }
45
+ const hours = Math.floor(seconds / 3600);
46
+ const remainingMinutes = Math.floor((seconds % 3600) / 60);
47
+ return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
48
+ }
49
+ /**
50
+ * PhaseSpinner - Animated spinner with elapsed time and phase progress
51
+ *
52
+ * Features:
53
+ * - Animated spinner (or text fallback) via cli-ui
54
+ * - Elapsed time tracking with automatic updates
55
+ * - Phase progress indicator (e.g., "exec (2/3)")
56
+ * - ShutdownManager integration for graceful Ctrl+C cleanup
57
+ * - Pause/resume for verbose streaming output
58
+ */
59
+ export class PhaseSpinner {
60
+ spinner;
61
+ startTime = 0;
62
+ intervalId = null;
63
+ disposed = false;
64
+ paused = false;
65
+ phase;
66
+ phaseIndex;
67
+ totalPhases;
68
+ shutdownManager;
69
+ prefix;
70
+ iteration;
71
+ cleanupName;
72
+ constructor(options) {
73
+ this.phase = options.phase;
74
+ this.phaseIndex = options.phaseIndex;
75
+ this.totalPhases = options.totalPhases;
76
+ this.shutdownManager = options.shutdownManager;
77
+ this.prefix = options.prefix ?? " ";
78
+ this.iteration = options.iteration;
79
+ this.cleanupName = `phase-spinner-${options.phase}`;
80
+ // Create the underlying spinner with initial text
81
+ this.spinner = createSpinner(this.formatText(0));
82
+ }
83
+ /**
84
+ * Format the spinner text with phase, progress, and elapsed time
85
+ */
86
+ formatText(elapsedSeconds) {
87
+ const progress = `(${this.phaseIndex}/${this.totalPhases})`;
88
+ const elapsed = elapsedSeconds > 0 ? ` ${formatElapsedTime(elapsedSeconds)}` : "";
89
+ const iterationSuffix = this.iteration && this.iteration > 1
90
+ ? ` [iteration ${this.iteration}]`
91
+ : "";
92
+ return `${this.prefix}${this.phase} ${progress}...${elapsed}${iterationSuffix}`;
93
+ }
94
+ /**
95
+ * Update the spinner text with current elapsed time
96
+ */
97
+ updateElapsedTime() {
98
+ if (this.disposed || this.paused)
99
+ return;
100
+ const elapsedSeconds = (Date.now() - this.startTime) / 1000;
101
+ this.spinner.text = this.formatText(elapsedSeconds);
102
+ }
103
+ /**
104
+ * Start the spinner
105
+ *
106
+ * Begins the animation and elapsed time tracking.
107
+ */
108
+ start() {
109
+ if (this.disposed)
110
+ return this;
111
+ this.startTime = Date.now();
112
+ this.spinner.start(this.formatText(0));
113
+ // Start elapsed time update interval
114
+ this.intervalId = setInterval(() => {
115
+ this.updateElapsedTime();
116
+ }, ELAPSED_UPDATE_INTERVAL_MS);
117
+ // Register with ShutdownManager for graceful cleanup
118
+ if (this.shutdownManager) {
119
+ this.shutdownManager.registerCleanup(this.cleanupName, async () => {
120
+ this.stop();
121
+ });
122
+ }
123
+ return this;
124
+ }
125
+ /**
126
+ * Mark the phase as succeeded
127
+ *
128
+ * Stops the spinner and shows a checkmark with total duration.
129
+ */
130
+ succeed(customText) {
131
+ if (this.disposed)
132
+ return this;
133
+ this.clearInterval();
134
+ this.unregisterCleanup();
135
+ const elapsedSeconds = (Date.now() - this.startTime) / 1000;
136
+ const duration = formatElapsedTime(elapsedSeconds);
137
+ const progress = `(${this.phaseIndex}/${this.totalPhases})`;
138
+ const text = customText ?? `${this.prefix}${this.phase} ${progress} (${duration})`;
139
+ this.spinner.succeed(text);
140
+ this.disposed = true;
141
+ return this;
142
+ }
143
+ /**
144
+ * Mark the phase as failed
145
+ *
146
+ * Stops the spinner and shows an error indicator with message.
147
+ */
148
+ fail(error) {
149
+ if (this.disposed)
150
+ return this;
151
+ this.clearInterval();
152
+ this.unregisterCleanup();
153
+ const elapsedSeconds = (Date.now() - this.startTime) / 1000;
154
+ const duration = formatElapsedTime(elapsedSeconds);
155
+ const progress = `(${this.phaseIndex}/${this.totalPhases})`;
156
+ const errorSuffix = error ? `: ${error}` : "";
157
+ const text = `${this.prefix}${this.phase} ${progress} (${duration})${errorSuffix}`;
158
+ this.spinner.fail(text);
159
+ this.disposed = true;
160
+ return this;
161
+ }
162
+ /**
163
+ * Stop the spinner without success/fail indication
164
+ *
165
+ * Use this for cleanup or when the phase is interrupted.
166
+ */
167
+ stop() {
168
+ if (this.disposed)
169
+ return this;
170
+ this.clearInterval();
171
+ this.unregisterCleanup();
172
+ this.spinner.stop();
173
+ this.disposed = true;
174
+ return this;
175
+ }
176
+ /**
177
+ * Pause the spinner (for verbose streaming output)
178
+ *
179
+ * Temporarily stops the animation without disposing.
180
+ * Call resume() to continue.
181
+ */
182
+ pause() {
183
+ if (this.disposed || this.paused)
184
+ return this;
185
+ this.paused = true;
186
+ this.spinner.stop();
187
+ return this;
188
+ }
189
+ /**
190
+ * Resume the spinner after pause
191
+ *
192
+ * Restarts the animation with updated elapsed time.
193
+ */
194
+ resume() {
195
+ if (this.disposed || !this.paused)
196
+ return this;
197
+ this.paused = false;
198
+ const elapsedSeconds = (Date.now() - this.startTime) / 1000;
199
+ this.spinner.start(this.formatText(elapsedSeconds));
200
+ return this;
201
+ }
202
+ /**
203
+ * Check if the spinner is currently active
204
+ */
205
+ get isSpinning() {
206
+ return this.spinner.isSpinning && !this.disposed && !this.paused;
207
+ }
208
+ /**
209
+ * Get the total elapsed time in seconds
210
+ */
211
+ get elapsedSeconds() {
212
+ if (this.startTime === 0)
213
+ return 0;
214
+ return (Date.now() - this.startTime) / 1000;
215
+ }
216
+ /**
217
+ * Dispose of the spinner and clean up resources
218
+ *
219
+ * Called automatically by succeed(), fail(), or stop().
220
+ * Safe to call multiple times.
221
+ */
222
+ dispose() {
223
+ if (this.disposed)
224
+ return;
225
+ this.clearInterval();
226
+ this.unregisterCleanup();
227
+ this.spinner.stop();
228
+ this.disposed = true;
229
+ }
230
+ /**
231
+ * Clear the elapsed time update interval
232
+ */
233
+ clearInterval() {
234
+ if (this.intervalId !== null) {
235
+ clearInterval(this.intervalId);
236
+ this.intervalId = null;
237
+ }
238
+ }
239
+ /**
240
+ * Unregister from ShutdownManager
241
+ */
242
+ unregisterCleanup() {
243
+ if (this.shutdownManager) {
244
+ this.shutdownManager.unregisterCleanup(this.cleanupName);
245
+ }
246
+ }
247
+ }
248
+ /**
249
+ * Create a PhaseSpinner with the given options
250
+ *
251
+ * Convenience function matching the pattern of cli-ui.spinner().
252
+ */
253
+ export function phaseSpinner(options) {
254
+ return new PhaseSpinner(options);
255
+ }
@@ -5,6 +5,8 @@ import { readdir, chmod } from "fs/promises";
5
5
  import { join, dirname, relative, isAbsolute } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { readFile, writeFile, ensureDir, fileExists, isSymlink, createSymlink, removeFileOrSymlink, } from "./fs.js";
8
+ import { getPackageVersion } from "./manifest.js";
9
+ const SKILLS_VERSION_PATH = ".claude/skills/.sequant-version";
8
10
  import { getStackConfig, getStackNotes, getMultiStackNotes } from "./stacks.js";
9
11
  import { isNativeWindows } from "./system.js";
10
12
  import { getProjectName } from "./project-name.js";
@@ -227,5 +229,7 @@ export async function copyTemplates(stack, tokens, options = {}) {
227
229
  const content = await readFile(settingsPath);
228
230
  await writeFile(".claude/settings.json", processTemplate(content, variables));
229
231
  }
232
+ // Write skills version marker for sync detection
233
+ await writeFile(SKILLS_VERSION_PATH, getPackageVersion());
230
234
  return { scriptsSymlinked, symlinkResults };
231
235
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sequant",
3
- "version": "1.13.0",
3
+ "version": "1.13.2",
4
4
  "description": "Quantize your development workflow - Sequential AI phases with quality gates",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,8 @@
17
17
  "files": [
18
18
  "dist",
19
19
  "templates",
20
- "stacks"
20
+ "stacks",
21
+ ".claude-plugin"
21
22
  ],
22
23
  "scripts": {
23
24
  "build": "tsc",
@@ -50,71 +50,299 @@ gh issue view <issue-number> --json labels --jq '.labels[].name'
50
50
  - Recommend `--quality-loop` flag for auto-retry on failures
51
51
  - Quality loop auto-enables for these labels in `sequant run`
52
52
 
53
- ## Output Format
53
+ **New Features** (labels: `enhancement`, `feature`):
54
+ - Include `testgen` phase when ACs need automated tests
55
+ - Workflow: `spec → testgen → exec → qa`
54
56
 
55
- Provide a clear, actionable response with:
57
+ ### Quality Loop Detection
56
58
 
57
- 1. **Issue Summary Table** showing:
58
- - Issue number
59
- - Title
60
- - Labels
61
- - Recommended workflow
59
+ Quality loop (`--quality-loop` or `-q`) provides automatic fix iterations when phases fail. **Recommend quality loop broadly** for any non-trivial work.
62
60
 
63
- 2. **Recommended Commands** in order
61
+ **Always recommend `--quality-loop` when:**
62
+ - Labels include: `complex`, `refactor`, `breaking`, `major` (auto-enabled)
63
+ - Labels include: `enhancement`, `feature` (new functionality)
64
+ - Issue involves multiple files or components
65
+ - Issue title contains: "add", "implement", "create", "refactor", "update"
66
+ - Issue is NOT a simple bug fix with `bug` or `fix` label only
64
67
 
65
- 3. **CLI Command** - ALWAYS include `npx sequant run <issue>` for terminal/CI usage
68
+ **Skip quality loop recommendation only when:**
69
+ - Simple bug fix (only `bug` or `fix` label, no other labels)
70
+ - Documentation-only changes (`docs` label only)
71
+ - Issue explicitly marked as trivial
66
72
 
67
- 4. **Explanation** of why this workflow was chosen
73
+ **Quality loop benefits:**
74
+ - Auto-retries failed phases up to 3 times
75
+ - Catches intermittent test failures
76
+ - Handles build issues from dependency changes
77
+ - Reduces manual intervention for recoverable errors
68
78
 
69
- ### Example Output
79
+ ### Feature Branch Detection
70
80
 
71
- ```markdown
72
- ## Solve Workflow for Issues: 152, 153
81
+ When analyzing issues, check if `--base` flag should be recommended.
73
82
 
74
- ### Issue Analysis
83
+ **Check for feature branch indicators:**
75
84
 
76
- | Issue | Title | Labels | Workflow |
77
- |-------|-------|--------|----------|
78
- | #152 | Add user dashboard | ui, enhancement | Full (with /test) |
79
- | #153 | Refactor auth module | backend, refactor | Standard + quality loop |
85
+ ```bash
86
+ # Check for feature branch references in issue body
87
+ gh issue view <issue-number> --json body --jq '.body' | grep -iE "(feature/|branch from|based on|part of.*feature)"
80
88
 
81
- ### Recommended Workflow
89
+ # Check issue labels for feature context
90
+ gh issue view <issue-number> --json labels --jq '.labels[].name' | grep -iE "(dashboard|feature-|epic-)"
82
91
 
83
- **For #152 (UI feature):**
84
- ```bash
85
- /spec 152 # Plan the implementation
86
- /exec 152 # Implement the feature
87
- /test 152 # Browser-based UI testing
88
- /qa 152 # Quality review
92
+ # Check if project has defaultBase configured
93
+ cat .sequant/settings.json 2>/dev/null | jq -r '.run.defaultBase // empty'
89
94
  ```
90
95
 
91
- **For #153 (Backend refactor):**
96
+ **Recommend `--base <branch>` when:**
97
+ - Issue body references a feature branch (e.g., "Part of dashboard feature")
98
+ - Issue is labeled with a feature epic label (e.g., `dashboard`, `epic-auth`)
99
+ - Multiple related issues reference the same parent feature
100
+ - Project has `run.defaultBase` configured in settings
101
+
102
+ **Do NOT recommend `--base` when:**
103
+ - Issue should branch from main (default, most common)
104
+ - No feature branch context detected
105
+ - Issue is a standalone bug fix or independent feature
106
+
107
+ ### Chain Mode Detection
108
+
109
+ When analyzing multiple issues, determine if `--chain` flag should be recommended.
110
+
111
+ **Check for chain indicators:**
112
+
92
113
  ```bash
93
- /spec 153 # Plan the refactor
94
- /exec 153 # Implement changes
95
- /qa 153 # Quality review
114
+ # Check for dependency keywords in issue body
115
+ gh issue view <issue-number> --json body --jq '.body' | grep -iE "(depends on|blocked by|requires|after #|builds on)"
116
+
117
+ # Check for sequence labels
118
+ gh issue view <issue-number> --json labels --jq '.labels[].name' | grep -iE "(part-[0-9]|step-[0-9]|phase-[0-9])"
119
+
120
+ # Check for related issue references
121
+ gh issue view <issue-number> --json body --jq '.body' | grep -oE "#[0-9]+"
96
122
  ```
97
123
 
98
- > **Note:** Issue #153 has `refactor` label. Quality loop will **auto-enable** when using `sequant run`, providing automatic fix iterations if phases fail.
124
+ **Recommend `--chain` when:**
125
+ - Multiple issues have explicit dependencies (e.g., "depends on #123")
126
+ - Issues are labeled as parts of a sequence (e.g., `part-1`, `part-2`)
127
+ - Issue titles indicate sequence (e.g., "Part 1:", "Step 2:")
128
+ - Issues reference each other in their bodies
129
+ - Issues modify the same files in a specific order
99
130
 
100
- ### CLI Command
131
+ **Do NOT recommend `--chain` when:**
132
+ - Single issue (chain requires 2+ issues)
133
+ - Issues are independent (no shared files or dependencies)
134
+ - Issues touch completely different areas of codebase
135
+ - Parallel batch mode is more appropriate (unrelated issues)
101
136
 
102
- Run from terminal:
103
- ```bash
104
- npx sequant run 152 153
137
+ ### QA Gate Detection
138
+
139
+ When recommending `--chain`, also consider if `--qa-gate` should be added.
140
+
141
+ **Recommend `--qa-gate` when:**
142
+ - Chain has 3+ issues (longer chains have higher stale code risk)
143
+ - Issues have tight dependencies (later issues heavily rely on earlier ones)
144
+ - Issues modify the same files across the chain
145
+ - Production-critical or high-risk changes
146
+
147
+ **Do NOT recommend `--qa-gate` when:**
148
+ - Chain has only 2 issues (lower risk)
149
+ - Issues are mostly independent despite chain structure
150
+ - Speed is prioritized over safety
151
+ - Simple, low-risk changes
152
+
153
+ **Chain structure visualization:**
105
154
  ```
155
+ origin/main → #10 → #11 → #12
156
+ │ │ │
157
+ └──────┴──────┴── Each branch builds on previous
158
+ ```
159
+
160
+ ## Output Format
161
+
162
+ **Design Principles:**
163
+ - Lead with the recommendation (command first, top-down)
164
+ - Show all flag decisions explicitly with reasoning
165
+ - Be concise — signal over prose
166
+ - Visual hierarchy using ASCII boxes and lines
167
+ - Max ~25 lines (excluding conflict warnings)
168
+
169
+ **Required Sections (in order):**
170
+
171
+ 1. **Header Box** — Command recommendation prominently displayed
172
+ 2. **Issues List** — Compact: `#N Title ··· labels → workflow`
173
+ 3. **Flags Table** — ALL flags with ✓/✗ and one-line reasoning
174
+ 4. **Why Section** — 3-5 bullet points explaining key decisions
175
+ 5. **Also Consider** — Conditional curated alternatives (0-3 items)
176
+ 6. **Conflict Warning** — Only if in-flight work overlaps (conditional)
177
+
178
+ ---
179
+
180
+ ## Conflict Detection
181
+
182
+ Before generating output, check for in-flight work that may conflict:
106
183
 
107
- For issue #153 (or any complex work), quality loop is recommended:
108
184
  ```bash
109
- npx sequant run 153 --quality-loop # Explicit (auto-enabled for refactor label)
185
+ # List open worktrees
186
+ git worktree list --porcelain 2>/dev/null | grep "^worktree" | cut -d' ' -f2
187
+
188
+ # For each worktree, get changed files
189
+ git -C <worktree-path> diff --name-only main...HEAD 2>/dev/null
110
190
  ```
111
191
 
112
- > **Tip:** Install globally with `npm install -g sequant` to omit the `npx` prefix.
192
+ **If overlap detected** with files this issue likely touches, include warning:
193
+ ```
194
+ ⚠ Conflict risk: #45 (open) modifies lib/auth/* — coordinate or wait
195
+ ```
196
+
197
+ ---
198
+
199
+ ## "Also Consider" Logic
200
+
201
+ Only show alternatives representing genuine trade-offs. Max 2-3 items.
113
202
 
114
- ### Notes
115
- - Issue #152 requires UI testing due to `ui` label
116
- - Issue #153 will auto-enable quality loop due to `refactor` label
117
- - Quality loop: auto-retries failed phases up to 3 times
203
+ | Condition | Show Alternative |
204
+ |-----------|------------------|
205
+ | Complex issues OR user unfamiliar with sequant | `--dry-run` (preview before executing) |
206
+ | UI-adjacent AND test phase not included | `--phases +test` (add browser testing) |
207
+ | Mild dependency risk between issues | `--sequential` (run one at a time) |
208
+ | Dependencies ambiguous | Show both parallel and `--chain` options |
209
+
210
+ **Rules:**
211
+ - Omit section entirely if nothing worth showing
212
+ - Never list every flag — only curated, contextual options
213
+ - Each alternative needs one-line explanation
214
+
215
+ ---
216
+
217
+ ## Output Template
218
+
219
+ You MUST use this exact structure:
220
+
221
+ ```
222
+ ╭──────────────────────────────────────────────────────────────╮
223
+ │ sequant solve │
224
+ │ │
225
+ │ npx sequant run <ISSUES> <FLAGS> │
226
+ ╰──────────────────────────────────────────────────────────────╯
227
+
228
+ #<N> <Title truncated to ~35 chars> ·········· <labels> → <workflow>
229
+ #<N> <Title truncated to ~35 chars> ·········· <labels> → <workflow>
230
+
231
+ ┌─ Flags ──────────────────────────────────────────────────────┐
232
+ │ -q quality-loop ✓/✗ <one-line reasoning> │
233
+ │ --chain ✓/✗ <one-line reasoning> │
234
+ │ --qa-gate ✓/✗ <one-line reasoning> │
235
+ │ --base ✓/✗ <one-line reasoning> │
236
+ │ --testgen ✓/✗ <one-line reasoning> │
237
+ └──────────────────────────────────────────────────────────────┘
238
+
239
+ Why this workflow:
240
+ • <reason 1>
241
+ • <reason 2>
242
+ • <reason 3>
243
+
244
+ <!-- CONDITIONAL: Only if alternatives worth showing -->
245
+ Also consider:
246
+ <flag> <one-line explanation>
247
+ <flag> <one-line explanation>
248
+
249
+ <!-- CONDITIONAL: Only if conflict detected -->
250
+ ⚠ Conflict risk: #<N> (open) modifies <path> — coordinate or wait
251
+ ```
252
+
253
+ ---
254
+
255
+ ### Example Output (Independent Issues)
256
+
257
+ ```
258
+ ╭──────────────────────────────────────────────────────────────╮
259
+ │ sequant solve │
260
+ │ │
261
+ │ npx sequant run 152 153 -q │
262
+ ╰──────────────────────────────────────────────────────────────╯
263
+
264
+ #152 Add user dashboard ······················ ui → spec → testgen → exec → test → qa
265
+ #153 Refactor auth module ···················· backend → spec → exec → qa
266
+
267
+ ┌─ Flags ──────────────────────────────────────────────────────┐
268
+ │ -q quality-loop ✓ refactor label auto-enables retry │
269
+ │ --chain ✗ independent (different codepaths) │
270
+ │ --qa-gate ✗ no chain mode │
271
+ │ --base ✗ branching from main │
272
+ │ --testgen ✓ #152 has testable ACs (Unit Tests) │
273
+ └──────────────────────────────────────────────────────────────┘
274
+
275
+ Why this workflow:
276
+ • #152 has ui label → includes /test for browser verification
277
+ • #152 has testable ACs → includes /testgen for test stubs
278
+ • #153 has refactor label → quality loop auto-enabled
279
+ • No shared files → safe to parallelize
280
+
281
+ Also consider:
282
+ --dry-run Preview execution before running
283
+ ```
284
+
285
+ ---
286
+
287
+ ### Example Output (Dependent Issues — Chain)
288
+
289
+ ```
290
+ ╭──────────────────────────────────────────────────────────────╮
291
+ │ sequant solve │
292
+ │ │
293
+ │ npx sequant run 10 11 12 --sequential --chain --qa-gate -q │
294
+ ╰──────────────────────────────────────────────────────────────╯
295
+
296
+ #10 Add auth middleware ······················ backend → spec → testgen → exec → qa
297
+ #11 Add login page ··························· ui → spec → testgen → exec → test → qa
298
+ #12 Add logout functionality ················· ui → spec → exec → test → qa
299
+
300
+ ┌─ Flags ──────────────────────────────────────────────────────┐
301
+ │ -q quality-loop ✓ multi-step implementation │
302
+ │ --chain ✓ #11 depends on #10, #12 depends on #11│
303
+ │ --qa-gate ✓ 3 issues with tight dependencies │
304
+ │ --base ✗ branching from main │
305
+ │ --testgen ✓ #10, #11 have Unit Test ACs │
306
+ └──────────────────────────────────────────────────────────────┘
307
+
308
+ Chain structure:
309
+ main → #10 → #11 → #12
310
+
311
+ Why this workflow:
312
+ • Explicit dependencies detected in issue bodies
313
+ • Chain ensures each branch builds on previous
314
+ • QA gate prevents stale code in downstream issues
315
+ • UI issues (#11, #12) include /test phase
316
+ • #10, #11 have testable ACs → include /testgen
317
+ ```
318
+
319
+ ---
320
+
321
+ ### Example Output (With Conflict Warning)
322
+
323
+ ```
324
+ ╭──────────────────────────────────────────────────────────────╮
325
+ │ sequant solve │
326
+ │ │
327
+ │ npx sequant run 85 -q │
328
+ ╰──────────────────────────────────────────────────────────────╯
329
+
330
+ #85 Update auth cookie handling ·············· bug → exec → qa
331
+
332
+ ┌─ Flags ──────────────────────────────────────────────────────┐
333
+ │ -q quality-loop ✓ auth changes benefit from retry │
334
+ │ --chain ✗ single issue │
335
+ │ --qa-gate ✗ no chain mode │
336
+ │ --base ✗ branching from main │
337
+ │ --testgen ✗ bug fix (targeted tests in exec) │
338
+ └──────────────────────────────────────────────────────────────┘
339
+
340
+ Why this workflow:
341
+ • Bug fix with clear AC → skip /spec
342
+ • Bug fix → skip /testgen (targeted tests added during exec)
343
+ • Single issue → no chain needed
344
+
345
+ ⚠ Conflict risk: #82 (open) modifies lib/auth/cookies.ts — coordinate or wait
118
346
  ```
119
347
 
120
348
  ## Workflow Selection Logic
@@ -128,17 +356,30 @@ npx sequant run 153 --quality-loop # Explicit (auto-enabled for refactor label
128
356
  - UI/frontend changes → Add `test` phase
129
357
  - Complex refactors → Enable quality loop
130
358
  - Security-sensitive → Add `security-review` phase
359
+ - New features with testable ACs → Add `testgen` phase
131
360
 
132
361
  ### Standard Workflow (Most Issues)
133
362
  ```
134
363
  /spec → /exec → /qa
135
364
  ```
136
365
 
366
+ ### Feature with Testable ACs
367
+ ```
368
+ /spec → /testgen → /exec → /qa
369
+ ```
370
+ Include `testgen` when ACs have Unit Test or Integration Test verification methods.
371
+
137
372
  ### UI Feature Workflow
138
373
  ```
139
374
  /spec → /exec → /test → /qa
140
375
  ```
141
376
 
377
+ ### UI Feature with Tests
378
+ ```
379
+ /spec → /testgen → /exec → /test → /qa
380
+ ```
381
+ Combine `testgen` and `test` for UI features with testable ACs.
382
+
142
383
  ### Bug Fix Workflow (Simple)
143
384
  ```
144
385
  /exec → /qa
@@ -156,11 +397,25 @@ Runs complete workflow with automatic fix iterations.
156
397
  | Issue Type | Labels | Workflow |
157
398
  |------------|--------|----------|
158
399
  | UI Feature | ui, frontend, admin | spec → exec → test → qa |
400
+ | UI Feature with Tests | ui + enhancement | spec → testgen → exec → test → qa |
159
401
  | Backend Feature | backend, api | spec → exec → qa |
402
+ | New Feature (testable) | enhancement, feature | spec → testgen → exec → qa |
160
403
  | Bug Fix | bug, fix | exec → qa (or full if complex) |
161
404
  | Complex Feature | complex, refactor | `--quality-loop` or fullsolve |
162
405
  | Documentation | docs | exec → qa |
163
406
 
407
+ ### Testgen Phase Detection
408
+
409
+ **Include `testgen` in workflow when:**
410
+ - Issue has `enhancement` or `feature` label AND
411
+ - Issue is NOT a simple bug fix or docs-only change AND
412
+ - Project has test infrastructure (Jest, Vitest, etc.)
413
+
414
+ **Skip `testgen` when:**
415
+ - Issue is `bug` or `fix` only (targeted tests added during exec)
416
+ - Issue is `docs` only (no code tests needed)
417
+ - All ACs use Manual Test or Browser Test verification
418
+
164
419
  **Quality Loop vs Fullsolve:**
165
420
  - `--quality-loop`: Enables auto-retry within `sequant run` (good for CI/automation)
166
421
  - `/fullsolve`: Interactive single-issue resolution with inline loops (good for manual work)
@@ -179,6 +434,18 @@ npx sequant run 152 153 154
179
434
  # Sequential execution (respects dependencies)
180
435
  npx sequant run 152 153 --sequential
181
436
 
437
+ # Chain mode (each issue branches from previous completed issue)
438
+ npx sequant run 10 11 12 --sequential --chain
439
+
440
+ # Chain mode with QA gate (pause if QA fails, prevent stale code)
441
+ npx sequant run 10 11 12 --sequential --chain --qa-gate
442
+
443
+ # Custom base branch (branch from feature branch instead of main)
444
+ npx sequant run 117 --base feature/dashboard
445
+
446
+ # Chain mode with custom base branch
447
+ npx sequant run 117 118 119 --sequential --chain --base feature/dashboard
448
+
182
449
  # Custom phases
183
450
  npx sequant run 152 --phases spec,exec,qa
184
451
 
@@ -192,6 +459,50 @@ npx sequant run 152 --quality-loop --max-iterations 5
192
459
  npx sequant run 152 --dry-run
193
460
  ```
194
461
 
462
+ ### Custom Base Branch
463
+
464
+ The `--base` flag specifies which branch to create worktrees from:
465
+
466
+ ```
467
+ Without --base: With --base feature/dashboard:
468
+ origin/main feature/dashboard
469
+ ├── #117 ├── #117
470
+ ├── #118 ├── #118
471
+ └── #119 └── #119
472
+ ```
473
+
474
+ **Use `--base` when:**
475
+ - Working on issues for a feature integration branch
476
+ - Issue references a parent branch (e.g., "Part of dashboard feature")
477
+ - Project uses `.sequant/settings.json` with `run.defaultBase` configured
478
+ - Issues should build on an existing feature branch
479
+
480
+ **Do NOT use `--base` when:**
481
+ - Issues should branch from main (default behavior)
482
+ - Working on independent bug fixes or features
483
+
484
+ ### Chain Mode Explained
485
+
486
+ The `--chain` flag (requires `--sequential`) creates dependent branches:
487
+
488
+ ```
489
+ Without --chain: With --chain:
490
+ origin/main origin/main
491
+ ├── #10 └── #10 (merged)
492
+ ├── #11 └── #11 (merged)
493
+ └── #12 └── #12
494
+ ```
495
+
496
+ **Use `--chain` when:**
497
+ - Issues have explicit dependencies
498
+ - Later issues build on earlier implementations
499
+ - Order matters for correctness
500
+
501
+ **Do NOT use `--chain` when:**
502
+ - Issues are independent
503
+ - Parallel execution is appropriate
504
+ - Issues can be merged in any order
505
+
195
506
  > **Tip:** Install globally with `npm install -g sequant` to omit the `npx` prefix.
196
507
 
197
508
  ## Edge Cases
@@ -224,56 +535,37 @@ If issues depend on each other:
224
535
 
225
536
  ---
226
537
 
227
- ## Output Verification
228
-
229
- **Before responding, verify your output includes ALL of these:**
230
-
231
- - [ ] **Issue Summary Table** - Table with Issue, Title, Labels, Workflow columns
232
- - [ ] **Recommended Workflow** - Slash commands in order for each issue
233
- - [ ] **CLI Command** - `npx sequant run <issue-numbers>` command (REQUIRED)
234
- - [ ] **Explanation** - Brief notes explaining workflow choices
235
-
236
- **DO NOT respond until all items are verified.**
237
-
238
- ## Output Template
538
+ ## State Tracking
239
539
 
240
- You MUST use this exact structure:
540
+ **IMPORTANT:** `/solve` initializes issue state when analyzing issues.
241
541
 
242
- ```markdown
243
- ## Solve Workflow for Issues: <ISSUE_NUMBERS>
542
+ ### State Updates
244
543
 
245
- ### Issue Analysis
544
+ When analyzing issues, initialize state tracking so the dashboard can show planned work:
246
545
 
247
- | Issue | Title | Labels | Workflow |
248
- |-------|-------|--------|----------|
249
- <!-- FILL: one row per issue. For complex/refactor/breaking/major labels, add "+ quality loop" to Workflow -->
250
-
251
- ### Recommended Workflow
546
+ **Initialize each issue being analyzed:**
547
+ ```bash
548
+ # Get issue title
549
+ TITLE=$(gh issue view <issue-number> --json title -q '.title')
252
550
 
253
- **For #<N> (<type>):**
254
- \`\`\`bash
255
- <!-- FILL: slash commands in order -->
256
- \`\`\`
551
+ # Initialize state (if not already tracked)
552
+ npx tsx scripts/state/update.ts init <issue-number> "$TITLE"
553
+ ```
257
554
 
258
- <!-- IF any issue has complex/refactor/breaking/major label, include this callout: -->
259
- > **Note:** Issue #<N> has `<label>` label. Quality loop will **auto-enable** when using `sequant run`, providing automatic fix iterations if phases fail.
555
+ **Note:** `/solve` only initializes issues - actual phase tracking happens during workflow execution (`/fullsolve`, `sequant run`, or individual skills).
260
556
 
261
- ### CLI Command
557
+ ---
262
558
 
263
- Run from terminal:
264
- \`\`\`bash
265
- npx sequant run <ISSUE_NUMBERS>
266
- \`\`\`
559
+ ## Output Verification
267
560
 
268
- <!-- IF any issue has complex/refactor/breaking/major label, include: -->
269
- For complex issues, quality loop is recommended:
270
- \`\`\`bash
271
- npx sequant run <ISSUE_NUMBER> --quality-loop # Explicit (auto-enabled for <label> label)
272
- \`\`\`
561
+ **Before responding, verify your output includes ALL of these:**
273
562
 
274
- > **Tip:** Install globally with `npm install -g sequant` to omit the `npx` prefix.
563
+ - [ ] **Header Box** ASCII box with `sequant solve` and full command
564
+ - [ ] **Issues List** — Each issue with dot leaders: `#N Title ··· labels → workflow`
565
+ - [ ] **Flags Table** — ALL five flags (-q, --chain, --qa-gate, --base, --testgen) with ✓/✗ and reasoning
566
+ - [ ] **Why Section** — 3-5 bullet points explaining decisions
567
+ - [ ] **Also Consider** — (conditional) Curated alternatives if applicable
568
+ - [ ] **Conflict Warning** — (conditional) If in-flight work overlaps
569
+ - [ ] **Line Count** — Total ≤25 lines (excluding conflict warnings)
275
570
 
276
- ### Notes
277
- <!-- FILL: explanation of workflow choices -->
278
- <!-- Include note about quality loop auto-enabling if applicable -->
279
- ```
571
+ **DO NOT respond until all items are verified.**