sequant 1.12.0 → 1.13.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 (53) hide show
  1. package/README.md +10 -8
  2. package/dist/bin/cli.js +19 -9
  3. package/dist/src/commands/doctor.js +42 -20
  4. package/dist/src/commands/init.js +152 -65
  5. package/dist/src/commands/logs.js +7 -6
  6. package/dist/src/commands/run.d.ts +13 -1
  7. package/dist/src/commands/run.js +122 -32
  8. package/dist/src/commands/stats.js +67 -48
  9. package/dist/src/commands/status.js +30 -12
  10. package/dist/src/commands/sync.d.ts +28 -0
  11. package/dist/src/commands/sync.js +102 -0
  12. package/dist/src/index.d.ts +6 -0
  13. package/dist/src/index.js +4 -0
  14. package/dist/src/lib/cli-ui.d.ts +196 -0
  15. package/dist/src/lib/cli-ui.js +544 -0
  16. package/dist/src/lib/content-analyzer.d.ts +89 -0
  17. package/dist/src/lib/content-analyzer.js +437 -0
  18. package/dist/src/lib/phase-signal.d.ts +94 -0
  19. package/dist/src/lib/phase-signal.js +171 -0
  20. package/dist/src/lib/phase-spinner.d.ts +146 -0
  21. package/dist/src/lib/phase-spinner.js +255 -0
  22. package/dist/src/lib/solve-comment-parser.d.ts +84 -0
  23. package/dist/src/lib/solve-comment-parser.js +200 -0
  24. package/dist/src/lib/stack-config.d.ts +51 -0
  25. package/dist/src/lib/stack-config.js +77 -0
  26. package/dist/src/lib/stacks.d.ts +52 -0
  27. package/dist/src/lib/stacks.js +173 -0
  28. package/dist/src/lib/templates.d.ts +2 -0
  29. package/dist/src/lib/templates.js +9 -2
  30. package/dist/src/lib/upstream/assessment.d.ts +70 -0
  31. package/dist/src/lib/upstream/assessment.js +385 -0
  32. package/dist/src/lib/upstream/index.d.ts +11 -0
  33. package/dist/src/lib/upstream/index.js +14 -0
  34. package/dist/src/lib/upstream/issues.d.ts +38 -0
  35. package/dist/src/lib/upstream/issues.js +267 -0
  36. package/dist/src/lib/upstream/relevance.d.ts +50 -0
  37. package/dist/src/lib/upstream/relevance.js +209 -0
  38. package/dist/src/lib/upstream/report.d.ts +29 -0
  39. package/dist/src/lib/upstream/report.js +391 -0
  40. package/dist/src/lib/upstream/types.d.ts +207 -0
  41. package/dist/src/lib/upstream/types.js +5 -0
  42. package/dist/src/lib/workflow/log-writer.d.ts +1 -1
  43. package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
  44. package/dist/src/lib/workflow/qa-cache.d.ts +199 -0
  45. package/dist/src/lib/workflow/qa-cache.js +440 -0
  46. package/dist/src/lib/workflow/run-log-schema.d.ts +34 -6
  47. package/dist/src/lib/workflow/run-log-schema.js +12 -1
  48. package/dist/src/lib/workflow/state-schema.d.ts +4 -4
  49. package/dist/src/lib/workflow/types.d.ts +4 -0
  50. package/package.json +6 -1
  51. package/templates/skills/qa/scripts/quality-checks.sh +509 -53
  52. package/templates/skills/solve/SKILL.md +375 -83
  53. package/templates/skills/spec/SKILL.md +107 -5
package/README.md CHANGED
@@ -194,7 +194,7 @@ npx sequant stats # View local workflow analytics
194
194
  npx sequant dashboard # Launch real-time workflow dashboard
195
195
  ```
196
196
 
197
- See [Run Command Options](docs/run-command.md), [State Command](docs/state-command.md), and [Analytics](docs/analytics.md) for details.
197
+ See [Run Command Options](docs/reference/run-command.md), [State Command](docs/reference/state-command.md), and [Analytics](docs/reference/analytics.md) for details.
198
198
 
199
199
  ---
200
200
 
@@ -211,7 +211,7 @@ See [Run Command Options](docs/run-command.md), [State Command](docs/state-comma
211
211
  }
212
212
  ```
213
213
 
214
- See [Customization Guide](docs/customization.md) for all options.
214
+ See [Customization Guide](docs/guides/customization.md) for all options.
215
215
 
216
216
  ---
217
217
 
@@ -228,13 +228,15 @@ See [Customization Guide](docs/customization.md) for all options.
228
228
 
229
229
  ## Documentation
230
230
 
231
+ - [Quickstart](docs/guides/quickstart.md) — 5-minute guide
232
+ - [Complete Workflow](docs/guides/workflow.md) — Full workflow including post-QA patterns
231
233
  - [Getting Started](docs/getting-started/installation.md)
232
- - [What We've Built](docs/what-weve-built.md) — Comprehensive project overview
234
+ - [What We've Built](docs/internal/what-weve-built.md) — Comprehensive project overview
233
235
  - [Workflow Concepts](docs/concepts/workflow-phases.md)
234
- - [Run Command](docs/run-command.md)
235
- - [Feature Branch Workflows](docs/feature-branch-workflow.md)
236
- - [Customization](docs/customization.md)
237
- - [Plugin Updates & Versioning](docs/plugin-updates.md)
236
+ - [Run Command](docs/reference/run-command.md)
237
+ - [Git Workflows](docs/guides/git-workflows.md)
238
+ - [Customization](docs/guides/customization.md)
239
+ - [Plugin Updates & Versioning](docs/internal/plugin-updates.md)
238
240
  - [Troubleshooting](docs/troubleshooting.md)
239
241
 
240
242
  Stack guides: [Next.js](docs/stacks/nextjs.md) · [Rust](docs/stacks/rust.md) · [Python](docs/stacks/python.md) · [Go](docs/stacks/go.md)
@@ -268,7 +270,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
268
270
 
269
271
  ### Telemetry
270
272
 
271
- Sequant does not collect any usage telemetry. See [docs/telemetry.md](docs/telemetry.md) for details.
273
+ Sequant does not collect any usage telemetry. See [docs/reference/telemetry.md](docs/reference/telemetry.md) for details.
272
274
 
273
275
  ---
274
276
 
package/dist/bin/cli.js CHANGED
@@ -11,6 +11,8 @@ import { dirname, resolve } from "path";
11
11
  import { readFileSync } from "fs";
12
12
  import { initCommand } from "../src/commands/init.js";
13
13
  import { isLocalNodeModulesInstall } from "../src/lib/version-check.js";
14
+ import { configureUI, banner } from "../src/lib/cli-ui.js";
15
+ import { isCI, isStdoutTTY } from "../src/lib/tty.js";
14
16
  // Read version from package.json dynamically
15
17
  // Works from both source (bin/) and compiled (dist/bin/) locations
16
18
  function getVersion() {
@@ -41,11 +43,21 @@ import { logsCommand } from "../src/commands/logs.js";
41
43
  import { statsCommand } from "../src/commands/stats.js";
42
44
  import { dashboardCommand } from "../src/commands/dashboard.js";
43
45
  import { stateInitCommand, stateRebuildCommand, stateCleanCommand, } from "../src/commands/state.js";
46
+ import { syncCommand } from "../src/commands/sync.js";
44
47
  const program = new Command();
45
48
  // Handle --no-color before parsing
46
49
  if (process.argv.includes("--no-color")) {
47
50
  process.env.FORCE_COLOR = "0";
48
51
  }
52
+ // Configure UI early based on environment and flags
53
+ configureUI({
54
+ noColor: process.argv.includes("--no-color") || !!process.env.NO_COLOR,
55
+ jsonMode: process.argv.includes("--json"),
56
+ verbose: process.argv.includes("--verbose") || process.argv.includes("-v"),
57
+ isTTY: isStdoutTTY(),
58
+ isCI: isCI(),
59
+ minimal: process.env.SEQUANT_MINIMAL === "1",
60
+ });
49
61
  // Warn if running from local node_modules (not npx cache or global)
50
62
  // This helps users who accidentally have a stale local install
51
63
  if (!process.argv.includes("--quiet") && isLocalNodeModulesInstall()) {
@@ -74,6 +86,12 @@ program
74
86
  .option("-d, --dry-run", "Show what would be updated without making changes")
75
87
  .option("-f, --force", "Overwrite local modifications")
76
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);
77
95
  program
78
96
  .command("doctor")
79
97
  .description("Check your Sequant installation for issues")
@@ -176,14 +194,6 @@ stateCmd
176
194
  program.parse();
177
195
  // Show help if no command provided
178
196
  if (!process.argv.slice(2).length) {
179
- console.log(chalk.green(`
180
- ╔═══════════════════════════════════════════════════════════╗
181
- ║ ║
182
- ║ ${chalk.bold("Sequant")} - Quantize your development workflow ║
183
- ║ ║
184
- ║ Sequential AI phases with quality gates ║
185
- ║ ║
186
- ╚═══════════════════════════════════════════════════════════╝
187
- `));
197
+ console.log(banner());
188
198
  program.help();
189
199
  }
@@ -3,10 +3,12 @@
3
3
  */
4
4
  import chalk from "chalk";
5
5
  import { execSync } from "child_process";
6
+ import { ui, colors } from "../lib/cli-ui.js";
6
7
  import { fileExists, isExecutable } from "../lib/fs.js";
7
8
  import { getManifest } from "../lib/manifest.js";
8
9
  import { commandExists, isGhAuthenticated, isNativeWindows, isWSL, checkOptionalMcpServers, getMcpServersConfig, OPTIONAL_MCP_SERVERS, } from "../lib/system.js";
9
10
  import { checkVersionThorough, getVersionWarning, } from "../lib/version-check.js";
11
+ import { areSkillsOutdated } from "./sync.js";
10
12
  /**
11
13
  * Labels that indicate an issue should be skipped from closed-issue verification
12
14
  * (case-insensitive matching)
@@ -67,7 +69,8 @@ export function checkClosedIssues() {
67
69
  return missingCommitIssues;
68
70
  }
69
71
  export async function doctorCommand(options = {}) {
70
- console.log(chalk.blue("\n🔍 Running health checks...\n"));
72
+ console.log(ui.headerBox("SEQUANT HEALTH CHECK"));
73
+ console.log();
71
74
  const checks = [];
72
75
  // Track gh availability and auth for conditional checks later
73
76
  let ghAvailable = false;
@@ -155,6 +158,22 @@ export async function doctorCommand(options = {}) {
155
158
  message: `Missing skills: ${missingSkills.join(", ")}`,
156
159
  });
157
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
+ }
158
177
  // Check 4: Hooks directory
159
178
  const hooksExist = await fileExists(".claude/hooks");
160
179
  if (hooksExist) {
@@ -369,22 +388,22 @@ export async function doctorCommand(options = {}) {
369
388
  }
370
389
  }
371
390
  }
372
- // Display results
391
+ // Display results with status icons
373
392
  let passCount = 0;
374
393
  let warnCount = 0;
375
394
  let failCount = 0;
376
395
  for (const check of checks) {
377
- const icon = check.status === "pass"
378
- ? chalk.green("")
396
+ const statusType = check.status === "pass"
397
+ ? "success"
379
398
  : check.status === "warn"
380
- ? chalk.yellow("")
381
- : chalk.red("");
399
+ ? "warning"
400
+ : "error";
382
401
  const color = check.status === "pass"
383
- ? chalk.green
402
+ ? colors.success
384
403
  : check.status === "warn"
385
- ? chalk.yellow
386
- : chalk.red;
387
- console.log(`${icon} ${chalk.bold(check.name)}: ${color(check.message)}`);
404
+ ? colors.warning
405
+ : colors.error;
406
+ console.log(` ${ui.statusIcon(statusType)} ${chalk.bold(check.name)}: ${color(check.message)}`);
388
407
  if (check.status === "pass")
389
408
  passCount++;
390
409
  else if (check.status === "warn")
@@ -392,21 +411,24 @@ export async function doctorCommand(options = {}) {
392
411
  else
393
412
  failCount++;
394
413
  }
395
- // Summary
396
- console.log(chalk.bold("\nSummary:"));
397
- console.log(chalk.green(` ✓ Passed: ${passCount}`));
398
- if (warnCount > 0)
399
- console.log(chalk.yellow(` ⚠ Warnings: ${warnCount}`));
400
- if (failCount > 0)
401
- console.log(chalk.red(` ✗ Failed: ${failCount}`));
414
+ // Summary with boxed output
415
+ const totalChecks = passCount + warnCount + failCount;
416
+ let summaryTitle;
417
+ let summaryMessage;
402
418
  if (failCount > 0) {
403
- console.log(chalk.red("\n❌ Some checks failed. Run `sequant init` to fix."));
419
+ summaryTitle = `${failCount} check${failCount > 1 ? "s" : ""} failed`;
420
+ summaryMessage = `Passed: ${passCount}/${totalChecks}\nWarnings: ${warnCount}\nFailed: ${failCount}\n\nRun \`sequant init\` to fix issues.`;
421
+ console.log("\n" + ui.errorBox(summaryTitle, summaryMessage));
404
422
  process.exit(1);
405
423
  }
406
424
  else if (warnCount > 0) {
407
- console.log(chalk.yellow("\n⚠️ Some warnings found but Sequant should work."));
425
+ summaryTitle = `All checks passed (${warnCount} warning${warnCount > 1 ? "s" : ""})`;
426
+ summaryMessage = `Passed: ${passCount}/${totalChecks}\nWarnings: ${warnCount}\n\nSequant should work correctly.`;
427
+ console.log("\n" + ui.warningBox(summaryTitle, summaryMessage));
408
428
  }
409
429
  else {
410
- console.log(chalk.green("\n✅ All checks passed!"));
430
+ summaryTitle = `All ${totalChecks} checks passed!`;
431
+ summaryMessage = `Your Sequant installation is healthy.`;
432
+ console.log("\n" + ui.successBox(summaryTitle, summaryMessage));
411
433
  }
412
434
  }
@@ -3,7 +3,8 @@
3
3
  */
4
4
  import chalk from "chalk";
5
5
  import inquirer from "inquirer";
6
- import { detectStack, getStackConfig, detectPackageManager, getPackageManagerCommands, } from "../lib/stacks.js";
6
+ import { ui, colors } from "../lib/cli-ui.js";
7
+ import { detectStack, detectAllStacks, getStackConfig, detectPackageManager, getPackageManagerCommands, STACKS, } from "../lib/stacks.js";
7
8
  import { copyTemplates } from "../lib/templates.js";
8
9
  import { createManifest } from "../lib/manifest.js";
9
10
  import { saveConfig } from "../lib/config.js";
@@ -12,6 +13,7 @@ import { fileExists, ensureDir, readFile, writeFile } from "../lib/fs.js";
12
13
  import { commandExists, isGhAuthenticated, getInstallHint, } from "../lib/system.js";
13
14
  import { shouldUseInteractiveMode, getNonInteractiveReason, } from "../lib/tty.js";
14
15
  import { checkAllDependencies, displayDependencyStatus, runSetupWizard, shouldRunSetupWizard, } from "../lib/wizard.js";
16
+ import { saveStackConfig } from "../lib/stack-config.js";
15
17
  /**
16
18
  * Check prerequisites and display warnings
17
19
  */
@@ -66,7 +68,9 @@ function logDefault(label, value) {
66
68
  console.log(chalk.blue(`📦 ${label}: ${value} (default)`));
67
69
  }
68
70
  export async function initCommand(options) {
69
- console.log(chalk.green("\n🚀 Initializing Sequant...\n"));
71
+ // Show banner
72
+ console.log(ui.banner());
73
+ console.log(colors.success("\nInitializing Sequant...\n"));
70
74
  // Determine if we should use interactive mode
71
75
  const useInteractive = shouldUseInteractiveMode(options.interactive);
72
76
  const skipPrompts = options.yes || !useInteractive;
@@ -139,54 +143,116 @@ export async function initCommand(options) {
139
143
  }
140
144
  // Detect or prompt for stack
141
145
  let stack = options.stack;
146
+ let additionalStacks = [];
142
147
  if (!stack) {
143
- const detected = await detectStack();
144
- if (detected && skipPrompts) {
145
- stack = detected;
146
- logDefault("Detected stack", stack);
147
- }
148
- else if (detected && !skipPrompts) {
149
- const { confirmedStack } = await inquirer.prompt([
148
+ // Check for multi-stack project in interactive mode
149
+ const allDetectedStacks = !skipPrompts ? await detectAllStacks() : [];
150
+ if (allDetectedStacks.length > 1 && !skipPrompts) {
151
+ // Multi-stack project detected - show checkbox selection
152
+ console.log(chalk.blue(`\n🔍 Detected ${allDetectedStacks.length} stacks in this project:`));
153
+ for (const ds of allDetectedStacks) {
154
+ const location = ds.path ? ` (${ds.path}/)` : " (root)";
155
+ console.log(chalk.gray(` • ${STACKS[ds.stack]?.displayName || ds.stack}${location}`));
156
+ }
157
+ console.log();
158
+ // Multi-stack selection prompt
159
+ const { selectedStacks } = await inquirer.prompt([
150
160
  {
151
- type: "list",
152
- name: "confirmedStack",
153
- message: `Detected ${detected} project. Is this correct?`,
154
- choices: [
155
- { name: `Yes, use ${detected}`, value: detected },
156
- { name: "No, let me choose", value: null },
157
- ],
161
+ type: "checkbox",
162
+ name: "selectedStacks",
163
+ message: "Select stacks to include in your constitution:",
164
+ choices: allDetectedStacks.map((ds) => ({
165
+ name: `${STACKS[ds.stack]?.displayName || ds.stack}${ds.path ? ` (${ds.path}/)` : " (root)"}`,
166
+ value: ds.stack,
167
+ checked: true, // Pre-select all detected stacks
168
+ })),
169
+ validate: (answer) => {
170
+ if (answer.length < 1) {
171
+ return "You must select at least one stack.";
172
+ }
173
+ return true;
174
+ },
158
175
  },
159
176
  ]);
160
- stack = confirmedStack;
161
- }
162
- if (!stack && skipPrompts) {
163
- // No detection and skipping prompts: use generic as default
164
- stack = "generic";
165
- logDefault("Using stack", stack);
177
+ // First selected stack is the primary
178
+ if (selectedStacks.length > 0) {
179
+ stack = selectedStacks[0];
180
+ additionalStacks = selectedStacks.slice(1);
181
+ }
182
+ // Confirm or change primary stack if multiple selected
183
+ if (selectedStacks.length > 1) {
184
+ const { primaryStack } = await inquirer.prompt([
185
+ {
186
+ type: "list",
187
+ name: "primaryStack",
188
+ message: "Which stack should be the primary? (determines dev URL and commands)",
189
+ choices: selectedStacks.map((s) => ({
190
+ name: STACKS[s]?.displayName || s,
191
+ value: s,
192
+ })),
193
+ },
194
+ ]);
195
+ stack = primaryStack;
196
+ additionalStacks = selectedStacks.filter((s) => s !== primaryStack);
197
+ }
166
198
  }
167
- else if (!stack) {
168
- const { selectedStack } = await inquirer.prompt([
169
- {
170
- type: "list",
171
- name: "selectedStack",
172
- message: "Select your project stack:",
173
- choices: [
174
- { name: "Next.js / React", value: "nextjs" },
175
- { name: "Astro", value: "astro" },
176
- { name: "SvelteKit", value: "sveltekit" },
177
- { name: "Remix", value: "remix" },
178
- { name: "Nuxt", value: "nuxt" },
179
- { name: "Rust", value: "rust" },
180
- { name: "Python", value: "python" },
181
- { name: "Go", value: "go" },
182
- { name: "Generic (no stack-specific config)", value: "generic" },
183
- ],
184
- },
185
- ]);
186
- stack = selectedStack;
199
+ else {
200
+ // Single stack detection (original behavior)
201
+ const detected = await detectStack();
202
+ if (detected && skipPrompts) {
203
+ stack = detected;
204
+ logDefault("Detected stack", stack);
205
+ }
206
+ else if (detected && !skipPrompts) {
207
+ const { confirmedStack } = await inquirer.prompt([
208
+ {
209
+ type: "list",
210
+ name: "confirmedStack",
211
+ message: `Detected ${detected} project. Is this correct?`,
212
+ choices: [
213
+ { name: `Yes, use ${detected}`, value: detected },
214
+ { name: "No, let me choose", value: null },
215
+ ],
216
+ },
217
+ ]);
218
+ stack = confirmedStack;
219
+ }
220
+ if (!stack && skipPrompts) {
221
+ // No detection and skipping prompts: use generic as default
222
+ stack = "generic";
223
+ logDefault("Using stack", stack);
224
+ }
225
+ else if (!stack) {
226
+ const { selectedStack } = await inquirer.prompt([
227
+ {
228
+ type: "list",
229
+ name: "selectedStack",
230
+ message: "Select your project stack:",
231
+ choices: [
232
+ { name: "Next.js / React", value: "nextjs" },
233
+ { name: "Astro", value: "astro" },
234
+ { name: "SvelteKit", value: "sveltekit" },
235
+ { name: "Remix", value: "remix" },
236
+ { name: "Nuxt", value: "nuxt" },
237
+ { name: "Rust", value: "rust" },
238
+ { name: "Python", value: "python" },
239
+ { name: "Go", value: "go" },
240
+ { name: "Generic (no stack-specific config)", value: "generic" },
241
+ ],
242
+ },
243
+ ]);
244
+ stack = selectedStack;
245
+ }
187
246
  }
188
247
  }
189
- console.log(chalk.blue(`\n📋 Stack: ${stack}`));
248
+ // Display selected stacks
249
+ if (additionalStacks.length > 0) {
250
+ console.log(chalk.blue(`\n📋 Primary Stack: ${stack}`));
251
+ console.log(chalk.blue(` Additional: ${additionalStacks.join(", ")}`));
252
+ }
253
+ else {
254
+ console.log(chalk.blue(`\n📋 Stack: ${stack}`));
255
+ }
190
256
  // Detect package manager
191
257
  const packageManager = await detectPackageManager();
192
258
  if (packageManager) {
@@ -236,21 +302,24 @@ export async function initCommand(options) {
236
302
  return;
237
303
  }
238
304
  }
239
- // Create directories
240
- console.log(chalk.blue("\n📁 Creating directories..."));
305
+ // Create directories with spinner
306
+ const dirSpinner = ui.spinner("Creating directories...");
307
+ dirSpinner.start();
241
308
  await ensureDir(".claude/skills");
242
309
  await ensureDir(".claude/hooks");
243
310
  await ensureDir(".claude/memory");
244
311
  await ensureDir(".claude/.sequant");
245
312
  await ensureDir(".sequant/logs");
246
313
  await ensureDir("scripts/dev");
314
+ dirSpinner.succeed("Created directories");
247
315
  // Update .gitignore
248
316
  const gitignoreUpdated = await updateGitignore();
249
317
  if (gitignoreUpdated) {
250
- console.log(chalk.blue("📝 Updated .gitignore with Sequant entries"));
318
+ ui.printStatus("success", "Updated .gitignore with Sequant entries");
251
319
  }
252
320
  // Save config with tokens
253
- console.log(chalk.blue("💾 Saving configuration..."));
321
+ const configSpinner = ui.spinner("Saving configuration...");
322
+ configSpinner.start();
254
323
  const pmConfig = packageManager
255
324
  ? getPackageManagerCommands(packageManager)
256
325
  : getPackageManagerCommands("npm");
@@ -263,18 +332,33 @@ export async function initCommand(options) {
263
332
  stack: stack,
264
333
  initialized: new Date().toISOString(),
265
334
  });
335
+ configSpinner.succeed("Saved configuration");
336
+ // Save multi-stack configuration if additional stacks selected
337
+ if (additionalStacks.length > 0) {
338
+ const stackConfig = {
339
+ primary: { name: stack },
340
+ additional: additionalStacks.map((name) => ({ name })),
341
+ };
342
+ await saveStackConfig(stackConfig);
343
+ console.log(chalk.blue("📋 Saved multi-stack configuration"));
344
+ }
266
345
  // Create default settings
267
- console.log(chalk.blue("⚙️ Creating default settings..."));
346
+ const settingsSpinner = ui.spinner("Creating default settings...");
347
+ settingsSpinner.start();
268
348
  await createDefaultSettings();
349
+ settingsSpinner.succeed("Created default settings");
269
350
  // Copy templates (with symlinks for scripts unless --no-symlinks)
270
- console.log(chalk.blue("📄 Copying templates..."));
351
+ const templatesSpinner = ui.spinner("Copying templates...");
352
+ templatesSpinner.start();
271
353
  const { scriptsSymlinked, symlinkResults } = await copyTemplates(stack, tokens, {
272
354
  noSymlinks: options.noSymlinks,
273
355
  force: options.force,
356
+ additionalStacks,
274
357
  });
358
+ templatesSpinner.succeed("Copied templates");
275
359
  // Report symlink status
276
360
  if (scriptsSymlinked) {
277
- console.log(chalk.blue("🔗 Created symlinks for scripts/dev/"));
361
+ ui.printStatus("success", "Created symlinks for scripts/dev/");
278
362
  }
279
363
  else if (!options.noSymlinks && symlinkResults) {
280
364
  // Some symlinks may have fallen back to copies
@@ -294,8 +378,10 @@ export async function initCommand(options) {
294
378
  }
295
379
  }
296
380
  // Create manifest
297
- console.log(chalk.blue("📋 Creating manifest..."));
381
+ const manifestSpinner = ui.spinner("Creating manifest...");
382
+ manifestSpinner.start();
298
383
  await createManifest(stack, packageManager ?? undefined);
384
+ manifestSpinner.succeed("Created manifest");
299
385
  // Build optional suggestions section
300
386
  const optionalSuggestions = suggestions.filter((s) => s.startsWith("Optional"));
301
387
  const optionalSection = optionalSuggestions.length > 0
@@ -306,19 +392,20 @@ export async function initCommand(options) {
306
392
  const prereqReminder = hasRemainingIssues
307
393
  ? `\n${chalk.yellow("⚠️ Remember to install missing dependencies before using issue workflows.")}\n${chalk.gray(" Run 'sequant doctor' to verify your setup.\n")}`
308
394
  : "";
309
- // Success message
310
- console.log(chalk.green(`
311
- Sequant initialized successfully!
312
- ${prereqReminder}
313
- ${chalk.bold("Next steps:")}
314
- 1. Review .claude/memory/constitution.md and customize for your project
315
- 2. Start using workflow commands in Claude Code:
395
+ // Success message with boxed output
396
+ const nextStepsContent = `${chalk.bold("Next steps:")}
397
+ 1. Review .claude/memory/constitution.md
398
+ 2. Start using workflow commands:
316
399
 
317
- ${chalk.cyan("/spec 123")} - Plan implementation for issue #123
318
- ${chalk.cyan("/exec 123")} - Implement the feature
319
- ${chalk.cyan("/qa 123")} - Quality review
320
- ${optionalSection}
321
- ${chalk.bold("Documentation:")}
322
- https://github.com/admarble/sequant#readme
323
- `));
400
+ ${chalk.cyan("/spec 123")} - Plan implementation
401
+ ${chalk.cyan("/exec 123")} - Implement the feature
402
+ ${chalk.cyan("/qa 123")} - Quality review`;
403
+ console.log("\n" + ui.successBox("Sequant initialized successfully!", nextStepsContent));
404
+ if (prereqReminder) {
405
+ console.log(prereqReminder);
406
+ }
407
+ if (optionalSection) {
408
+ console.log(optionalSection);
409
+ }
410
+ console.log(chalk.gray("\nDocumentation: https://github.com/admarble/sequant#readme\n"));
324
411
  }
@@ -7,6 +7,7 @@ import chalk from "chalk";
7
7
  import * as fs from "fs";
8
8
  import * as path from "path";
9
9
  import * as os from "os";
10
+ import { ui, colors } from "../lib/cli-ui.js";
10
11
  import { RunLogSchema, LOG_PATHS, } from "../lib/workflow/run-log-schema.js";
11
12
  import { manualRotate, getLogStats, formatBytes, } from "../lib/workflow/log-rotation.js";
12
13
  import { getSettings } from "../lib/settings.js";
@@ -177,17 +178,17 @@ export async function logsCommand(options) {
177
178
  await handleRotation(logDir, options.dryRun ?? false);
178
179
  return;
179
180
  }
180
- console.log(chalk.blue("\n📝 Sequant Run Logs\n"));
181
- console.log(chalk.gray(` Log directory: ${logDir}`));
181
+ console.log(ui.headerBox("SEQUANT RUN LOGS"));
182
+ console.log(colors.muted(`\n Log directory: ${logDir}`));
182
183
  // List log files
183
184
  const logFiles = listLogFiles(logDir);
184
185
  if (logFiles.length === 0) {
185
- console.log(chalk.yellow("\n No logs found."));
186
- console.log(chalk.gray(" Run `npx sequant run <issues>` to generate logs."));
186
+ console.log(colors.warning("\n No logs found."));
187
+ console.log(colors.muted(" Run `npx sequant run <issues>` to generate logs."));
187
188
  console.log("");
188
189
  return;
189
190
  }
190
- console.log(chalk.gray(` Found ${logFiles.length} log file(s)\n`));
191
+ console.log(colors.muted(` Found ${logFiles.length} log file(s)\n`));
191
192
  // Parse all logs
192
193
  const logs = logFiles
193
194
  .map((filename) => {
@@ -215,7 +216,7 @@ export async function logsCommand(options) {
215
216
  displayLogSummary(log, filename);
216
217
  }
217
218
  // Summary
218
- console.log(chalk.blue("\n" + "━".repeat(50)));
219
+ console.log("\n" + ui.divider());
219
220
  const totalPassed = filteredLogs.reduce((sum, { log }) => sum + log.summary.passed, 0);
220
221
  const totalFailed = filteredLogs.reduce((sum, { log }) => sum + log.summary.failed, 0);
221
222
  const totalDuration = filteredLogs.reduce((sum, { log }) => sum + log.summary.totalDurationSeconds, 0);
@@ -4,7 +4,19 @@
4
4
  * Runs the Sequant workflow (/spec → /exec → /qa) for one or more issues
5
5
  * using the Claude Agent SDK for proper skill invocation.
6
6
  */
7
- import { Phase } from "../lib/workflow/types.js";
7
+ import { Phase, QaVerdict } from "../lib/workflow/types.js";
8
+ /**
9
+ * Parse QA verdict from phase output
10
+ *
11
+ * Looks for verdict patterns in the QA output:
12
+ * - "### Verdict: READY_FOR_MERGE"
13
+ * - "**Verdict:** AC_NOT_MET"
14
+ * - "Verdict: AC_MET_BUT_NOT_A_PLUS"
15
+ *
16
+ * @param output - The captured output from QA phase
17
+ * @returns The parsed verdict or null if not found
18
+ */
19
+ export declare function parseQaVerdict(output: string): QaVerdict | null;
8
20
  /**
9
21
  * List all active worktrees with their branches
10
22
  */