sequant 1.11.0 → 1.13.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 (66) hide show
  1. package/README.md +93 -7
  2. package/dist/bin/cli.js +12 -9
  3. package/dist/src/commands/doctor.js +25 -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 +75 -12
  8. package/dist/src/commands/stats.js +67 -48
  9. package/dist/src/commands/status.js +30 -12
  10. package/dist/src/index.d.ts +6 -0
  11. package/dist/src/index.js +4 -0
  12. package/dist/src/lib/ac-linter.d.ts +116 -0
  13. package/dist/src/lib/ac-linter.js +304 -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/plugin-version-sync.d.ts +26 -0
  21. package/dist/src/lib/plugin-version-sync.js +91 -0
  22. package/dist/src/lib/project-name.d.ts +40 -0
  23. package/dist/src/lib/project-name.js +191 -0
  24. package/dist/src/lib/semgrep.d.ts +136 -0
  25. package/dist/src/lib/semgrep.js +406 -0
  26. package/dist/src/lib/solve-comment-parser.d.ts +84 -0
  27. package/dist/src/lib/solve-comment-parser.js +200 -0
  28. package/dist/src/lib/stack-config.d.ts +51 -0
  29. package/dist/src/lib/stack-config.js +77 -0
  30. package/dist/src/lib/stacks.d.ts +66 -0
  31. package/dist/src/lib/stacks.js +332 -0
  32. package/dist/src/lib/templates.d.ts +2 -0
  33. package/dist/src/lib/templates.js +12 -3
  34. package/dist/src/lib/upstream/assessment.d.ts +70 -0
  35. package/dist/src/lib/upstream/assessment.js +385 -0
  36. package/dist/src/lib/upstream/index.d.ts +11 -0
  37. package/dist/src/lib/upstream/index.js +14 -0
  38. package/dist/src/lib/upstream/issues.d.ts +38 -0
  39. package/dist/src/lib/upstream/issues.js +267 -0
  40. package/dist/src/lib/upstream/relevance.d.ts +50 -0
  41. package/dist/src/lib/upstream/relevance.js +209 -0
  42. package/dist/src/lib/upstream/report.d.ts +29 -0
  43. package/dist/src/lib/upstream/report.js +391 -0
  44. package/dist/src/lib/upstream/types.d.ts +207 -0
  45. package/dist/src/lib/upstream/types.js +5 -0
  46. package/dist/src/lib/workflow/log-writer.d.ts +1 -1
  47. package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
  48. package/dist/src/lib/workflow/qa-cache.d.ts +199 -0
  49. package/dist/src/lib/workflow/qa-cache.js +440 -0
  50. package/dist/src/lib/workflow/run-log-schema.d.ts +34 -6
  51. package/dist/src/lib/workflow/run-log-schema.js +12 -1
  52. package/dist/src/lib/workflow/state-schema.d.ts +4 -4
  53. package/dist/src/lib/workflow/types.d.ts +4 -0
  54. package/package.json +6 -1
  55. package/templates/hooks/pre-tool.sh +6 -0
  56. package/templates/memory/constitution.md +1 -5
  57. package/templates/skills/_shared/references/prompt-templates.md +350 -0
  58. package/templates/skills/_shared/references/subagent-types.md +131 -0
  59. package/templates/skills/exec/SKILL.md +82 -0
  60. package/templates/skills/fullsolve/SKILL.md +19 -2
  61. package/templates/skills/loop/SKILL.md +3 -1
  62. package/templates/skills/qa/SKILL.md +79 -9
  63. package/templates/skills/qa/references/quality-gates.md +85 -1
  64. package/templates/skills/qa/references/semgrep-rules.md +207 -0
  65. package/templates/skills/qa/scripts/quality-checks.sh +525 -15
  66. package/templates/skills/spec/SKILL.md +322 -9
package/README.md CHANGED
@@ -9,12 +9,31 @@ Solve GitHub issues with structured phases and quality gates — from issue to m
9
9
 
10
10
  ## Quick Start
11
11
 
12
+ ### Option A: Install as Claude Code Plugin (Recommended)
13
+
14
+ ```bash
15
+ # Add the Sequant marketplace
16
+ /plugin marketplace add admarble/sequant
17
+
18
+ # Install the plugin
19
+ /plugin install sequant
20
+ ```
21
+
22
+ Then run setup:
23
+ ```
24
+ /sequant:setup # Initialize worktrees directory
25
+ ```
26
+
27
+ ### Option B: Install via npm
28
+
12
29
  ```bash
13
30
  # In your project directory
14
31
  npx sequant init
15
32
  npx sequant doctor # Verify setup
16
33
  ```
17
34
 
35
+ ### Start Using
36
+
18
37
  Then in Claude Code:
19
38
 
20
39
  ```
@@ -31,9 +50,20 @@ Or step-by-step:
31
50
 
32
51
  ### Prerequisites
33
52
 
53
+ **Required:**
34
54
  - [Claude Code](https://claude.ai/code) — AI coding assistant
35
55
  - [GitHub CLI](https://cli.github.com/) — run `gh auth login`
36
- - Node.js 18+ and Git
56
+ - Git (for worktree-based isolation)
57
+
58
+ **For npm installation:**
59
+ - Node.js 18+
60
+
61
+ **Optional MCP servers (enhanced features):**
62
+ - `chrome-devtools` — enables `/test` for browser-based UI testing
63
+ - `sequential-thinking` — enhanced reasoning for complex decisions
64
+ - `context7` — library documentation lookup
65
+
66
+ > **Note:** Sequant is optimized for Node.js/TypeScript projects. The worktree workflow works with any git repository.
37
67
 
38
68
  ---
39
69
 
@@ -52,6 +82,24 @@ Sequant adds slash commands to Claude Code that enforce a structured workflow:
52
82
 
53
83
  > `/test` is optional — used for UI features when Chrome DevTools MCP is available.
54
84
 
85
+ ### Worktree Isolation
86
+
87
+ Sequant uses Git worktrees to isolate each issue's work:
88
+
89
+ ```
90
+ your-project/ # Main repo (stays on main branch)
91
+ ../worktrees/
92
+ feature/
93
+ 123-add-login/ # Issue #123 worktree (feature branch)
94
+ 456-fix-bug/ # Issue #456 worktree (feature branch)
95
+ ```
96
+
97
+ **Why worktrees?**
98
+ - Work on multiple issues simultaneously
99
+ - Never pollute your main branch
100
+ - Each issue has its own dependencies and build
101
+ - Safe to discard failed experiments
102
+
55
103
  ### Quality Gates
56
104
 
57
105
  Every `/qa` runs automated checks:
@@ -59,6 +107,7 @@ Every `/qa` runs automated checks:
59
107
  - **AC Adherence** — Code verified against acceptance criteria
60
108
  - **Type Safety** — Detects `any`, `as any`, missing types
61
109
  - **Security Scans** — OWASP-style vulnerability detection
110
+ - **Semgrep Static Analysis** — Stack-aware rulesets, custom rules via `.sequant/semgrep-rules.yaml`
62
111
  - **Scope Analysis** — Flags changes outside issue scope
63
112
  - **Execution Evidence** — Scripts/CLI must pass smoke tests
64
113
  - **Test Quality** — Validates test coverage and mock hygiene
@@ -115,9 +164,9 @@ npx sequant run 123 --base feature/dashboard # Custom base branch
115
164
 
116
165
  | Command | Purpose |
117
166
  |---------|---------|
118
- | `/merger` | Multi-issue integration and merge |
119
167
  | `/testgen` | Generate test stubs from spec |
120
168
  | `/verify` | CLI/script execution verification |
169
+ | `/setup` | Initialize Sequant in a project |
121
170
 
122
171
  ### Utilities
123
172
 
@@ -126,6 +175,7 @@ npx sequant run 123 --base feature/dashboard # Custom base branch
126
175
  | `/assess` | Issue triage and status assessment |
127
176
  | `/docs` | Generate feature documentation |
128
177
  | `/clean` | Repository cleanup |
178
+ | `/improve` | Codebase analysis and improvement discovery |
129
179
  | `/security-review` | Deep security analysis |
130
180
  | `/reflect` | Workflow improvement analysis |
131
181
 
@@ -141,9 +191,10 @@ npx sequant status # Show version and config
141
191
  npx sequant run <issues...> # Execute workflow
142
192
  npx sequant state <cmd> # Manage workflow state (init/rebuild/clean)
143
193
  npx sequant stats # View local workflow analytics
194
+ npx sequant dashboard # Launch real-time workflow dashboard
144
195
  ```
145
196
 
146
- 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.
147
198
 
148
199
  ---
149
200
 
@@ -160,7 +211,7 @@ See [Run Command Options](docs/run-command.md), [State Command](docs/state-comma
160
211
  }
161
212
  ```
162
213
 
163
- See [Customization Guide](docs/customization.md) for all options.
214
+ See [Customization Guide](docs/guides/customization.md) for all options.
164
215
 
165
216
  ---
166
217
 
@@ -177,17 +228,52 @@ See [Customization Guide](docs/customization.md) for all options.
177
228
 
178
229
  ## Documentation
179
230
 
231
+ - [Quickstart](docs/guides/quickstart.md) — 5-minute guide
232
+ - [Complete Workflow](docs/guides/workflow.md) — Full workflow including post-QA patterns
180
233
  - [Getting Started](docs/getting-started/installation.md)
234
+ - [What We've Built](docs/internal/what-weve-built.md) — Comprehensive project overview
181
235
  - [Workflow Concepts](docs/concepts/workflow-phases.md)
182
- - [Run Command](docs/run-command.md)
183
- - [Feature Branch Workflows](docs/feature-branch-workflow.md)
184
- - [Customization](docs/customization.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)
185
240
  - [Troubleshooting](docs/troubleshooting.md)
186
241
 
187
242
  Stack guides: [Next.js](docs/stacks/nextjs.md) Ā· [Rust](docs/stacks/rust.md) Ā· [Python](docs/stacks/python.md) Ā· [Go](docs/stacks/go.md)
188
243
 
189
244
  ---
190
245
 
246
+ ## Feedback & Contributing
247
+
248
+ ### Reporting Issues
249
+
250
+ - **Plugin issues:** [Plugin Feedback template](https://github.com/admarble/sequant/issues/new?template=plugin-feedback.yml)
251
+ - **Bug reports:** [Bug template](https://github.com/admarble/sequant/issues/new?template=bug.yml)
252
+ - **Feature requests:** [Feature template](https://github.com/admarble/sequant/issues/new?template=feature.yml)
253
+ - **Questions:** [GitHub Discussions](https://github.com/admarble/sequant/discussions)
254
+
255
+ ### Using `/improve` for Feedback
256
+
257
+ Run `/improve` in Claude Code to analyze your codebase and create structured issues:
258
+
259
+ ```
260
+ /improve # Analyze entire codebase
261
+ /improve security # Focus on security concerns
262
+ /improve tests # Find test coverage gaps
263
+ ```
264
+
265
+ The skill will present findings and offer to create GitHub issues automatically.
266
+
267
+ ### Contributing
268
+
269
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
270
+
271
+ ### Telemetry
272
+
273
+ Sequant does not collect any usage telemetry. See [docs/reference/telemetry.md](docs/reference/telemetry.md) for details.
274
+
275
+ ---
276
+
191
277
  ## License
192
278
 
193
279
  MIT
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() {
@@ -46,6 +48,15 @@ const program = new Command();
46
48
  if (process.argv.includes("--no-color")) {
47
49
  process.env.FORCE_COLOR = "0";
48
50
  }
51
+ // Configure UI early based on environment and flags
52
+ configureUI({
53
+ noColor: process.argv.includes("--no-color") || !!process.env.NO_COLOR,
54
+ jsonMode: process.argv.includes("--json"),
55
+ verbose: process.argv.includes("--verbose") || process.argv.includes("-v"),
56
+ isTTY: isStdoutTTY(),
57
+ isCI: isCI(),
58
+ minimal: process.env.SEQUANT_MINIMAL === "1",
59
+ });
49
60
  // Warn if running from local node_modules (not npx cache or global)
50
61
  // This helps users who accidentally have a stale local install
51
62
  if (!process.argv.includes("--quiet") && isLocalNodeModulesInstall()) {
@@ -176,14 +187,6 @@ stateCmd
176
187
  program.parse();
177
188
  // Show help if no command provided
178
189
  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
- `));
190
+ console.log(banner());
188
191
  program.help();
189
192
  }
@@ -3,6 +3,7 @@
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";
@@ -67,7 +68,8 @@ export function checkClosedIssues() {
67
68
  return missingCommitIssues;
68
69
  }
69
70
  export async function doctorCommand(options = {}) {
70
- console.log(chalk.blue("\nšŸ” Running health checks...\n"));
71
+ console.log(ui.headerBox("SEQUANT HEALTH CHECK"));
72
+ console.log();
71
73
  const checks = [];
72
74
  // Track gh availability and auth for conditional checks later
73
75
  let ghAvailable = false;
@@ -369,22 +371,22 @@ export async function doctorCommand(options = {}) {
369
371
  }
370
372
  }
371
373
  }
372
- // Display results
374
+ // Display results with status icons
373
375
  let passCount = 0;
374
376
  let warnCount = 0;
375
377
  let failCount = 0;
376
378
  for (const check of checks) {
377
- const icon = check.status === "pass"
378
- ? chalk.green("āœ“")
379
+ const statusType = check.status === "pass"
380
+ ? "success"
379
381
  : check.status === "warn"
380
- ? chalk.yellow("⚠")
381
- : chalk.red("āœ—");
382
+ ? "warning"
383
+ : "error";
382
384
  const color = check.status === "pass"
383
- ? chalk.green
385
+ ? colors.success
384
386
  : check.status === "warn"
385
- ? chalk.yellow
386
- : chalk.red;
387
- console.log(`${icon} ${chalk.bold(check.name)}: ${color(check.message)}`);
387
+ ? colors.warning
388
+ : colors.error;
389
+ console.log(` ${ui.statusIcon(statusType)} ${chalk.bold(check.name)}: ${color(check.message)}`);
388
390
  if (check.status === "pass")
389
391
  passCount++;
390
392
  else if (check.status === "warn")
@@ -392,21 +394,24 @@ export async function doctorCommand(options = {}) {
392
394
  else
393
395
  failCount++;
394
396
  }
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}`));
397
+ // Summary with boxed output
398
+ const totalChecks = passCount + warnCount + failCount;
399
+ let summaryTitle;
400
+ let summaryMessage;
402
401
  if (failCount > 0) {
403
- console.log(chalk.red("\nāŒ Some checks failed. Run `sequant init` to fix."));
402
+ summaryTitle = `${failCount} check${failCount > 1 ? "s" : ""} failed`;
403
+ summaryMessage = `Passed: ${passCount}/${totalChecks}\nWarnings: ${warnCount}\nFailed: ${failCount}\n\nRun \`sequant init\` to fix issues.`;
404
+ console.log("\n" + ui.errorBox(summaryTitle, summaryMessage));
404
405
  process.exit(1);
405
406
  }
406
407
  else if (warnCount > 0) {
407
- console.log(chalk.yellow("\nāš ļø Some warnings found but Sequant should work."));
408
+ summaryTitle = `All checks passed (${warnCount} warning${warnCount > 1 ? "s" : ""})`;
409
+ summaryMessage = `Passed: ${passCount}/${totalChecks}\nWarnings: ${warnCount}\n\nSequant should work correctly.`;
410
+ console.log("\n" + ui.warningBox(summaryTitle, summaryMessage));
408
411
  }
409
412
  else {
410
- console.log(chalk.green("\nāœ… All checks passed!"));
413
+ summaryTitle = `All ${totalChecks} checks passed!`;
414
+ summaryMessage = `Your Sequant installation is healthy.`;
415
+ console.log("\n" + ui.successBox(summaryTitle, summaryMessage));
411
416
  }
412
417
  }
@@ -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);