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
@@ -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
  */
@@ -20,6 +20,7 @@ import { getMcpServersConfig } from "../lib/system.js";
20
20
  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
+ import { ui, colors } from "../lib/cli-ui.js";
23
24
  /**
24
25
  * Slugify a title for branch naming
25
26
  */
@@ -30,6 +31,33 @@ function slugify(title) {
30
31
  .replace(/^-+|-+$/g, "")
31
32
  .substring(0, 50);
32
33
  }
34
+ /**
35
+ * Parse QA verdict from phase output
36
+ *
37
+ * Looks for verdict patterns in the QA output:
38
+ * - "### Verdict: READY_FOR_MERGE"
39
+ * - "**Verdict:** AC_NOT_MET"
40
+ * - "Verdict: AC_MET_BUT_NOT_A_PLUS"
41
+ *
42
+ * @param output - The captured output from QA phase
43
+ * @returns The parsed verdict or null if not found
44
+ */
45
+ export function parseQaVerdict(output) {
46
+ if (!output)
47
+ return null;
48
+ // Match various verdict formats:
49
+ // - "### Verdict: X" (markdown header)
50
+ // - "**Verdict:** X" (bold label with colon inside)
51
+ // - "**Verdict:** **X**" (bold label and bold value)
52
+ // - "Verdict: X" (plain)
53
+ // Case insensitive, handles optional markdown formatting
54
+ const verdictMatch = output.match(/(?:###?\s*)?(?:\*\*)?Verdict:?\*?\*?\s*\*?\*?\s*(READY_FOR_MERGE|AC_MET_BUT_NOT_A_PLUS|AC_NOT_MET|NEEDS_VERIFICATION)\*?\*?/i);
55
+ if (!verdictMatch)
56
+ return null;
57
+ // Normalize to uppercase with underscores
58
+ const verdict = verdictMatch[1].toUpperCase().replace(/-/g, "_");
59
+ return verdict;
60
+ }
33
61
  /**
34
62
  * Get the git repository root directory
35
63
  */
@@ -676,6 +704,35 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath,
676
704
  // Check result status
677
705
  if (resultMessage) {
678
706
  if (resultMessage.subtype === "success") {
707
+ // For QA phase, check the verdict to determine actual success
708
+ // SDK "success" just means the query completed - we need to parse the verdict
709
+ if (phase === "qa" && capturedOutput) {
710
+ const verdict = parseQaVerdict(capturedOutput);
711
+ // Only READY_FOR_MERGE and NEEDS_VERIFICATION are considered passing
712
+ // NEEDS_VERIFICATION is external verification, not a code quality issue
713
+ if (verdict &&
714
+ verdict !== "READY_FOR_MERGE" &&
715
+ verdict !== "NEEDS_VERIFICATION") {
716
+ return {
717
+ phase,
718
+ success: false,
719
+ durationSeconds,
720
+ error: `QA verdict: ${verdict}`,
721
+ sessionId: resultSessionId,
722
+ output: capturedOutput,
723
+ verdict, // Include parsed verdict
724
+ };
725
+ }
726
+ // Pass case - include verdict for logging
727
+ return {
728
+ phase,
729
+ success: true,
730
+ durationSeconds,
731
+ sessionId: resultSessionId,
732
+ output: capturedOutput,
733
+ verdict: verdict ?? undefined, // Include if found
734
+ };
735
+ }
679
736
  return {
680
737
  phase,
681
738
  success: true,
@@ -924,7 +981,7 @@ function parseBatches(batchArgs) {
924
981
  * Main run command
925
982
  */
926
983
  export async function runCommand(issues, options) {
927
- console.log(chalk.blue("\n🌐 Sequant Workflow Execution\n"));
984
+ console.log(ui.headerBox("SEQUANT WORKFLOW"));
928
985
  // Version freshness check (cached, non-blocking, respects --quiet)
929
986
  if (!options.quiet) {
930
987
  try {
@@ -1337,28 +1394,30 @@ export async function runCommand(issues, options) {
1337
1394
  }
1338
1395
  }
1339
1396
  // Summary
1340
- console.log(chalk.blue("\n" + "━".repeat(50)));
1341
- console.log(chalk.blue(" Summary"));
1342
- console.log(chalk.blue("━".repeat(50)));
1343
- console.log(chalk.gray(`\n Results: ${chalk.green(`${passed} passed`)}, ${chalk.red(`${failed} failed`)}`));
1397
+ console.log("\n" + ui.divider());
1398
+ console.log(colors.info(" Summary"));
1399
+ console.log(ui.divider());
1400
+ console.log(colors.muted(`\n Results: ${colors.success(`${passed} passed`)}, ${colors.error(`${failed} failed`)}`));
1344
1401
  for (const result of results) {
1345
- const status = result.success ? chalk.green("✓") : chalk.red("✗");
1402
+ const status = result.success
1403
+ ? ui.statusIcon("success")
1404
+ : ui.statusIcon("error");
1346
1405
  const duration = result.durationSeconds
1347
- ? chalk.gray(` (${formatDuration(result.durationSeconds)})`)
1406
+ ? colors.muted(` (${formatDuration(result.durationSeconds)})`)
1348
1407
  : "";
1349
1408
  const phases = result.phaseResults
1350
- .map((p) => (p.success ? chalk.green(p.phase) : chalk.red(p.phase)))
1409
+ .map((p) => p.success ? colors.success(p.phase) : colors.error(p.phase))
1351
1410
  .join(" → ");
1352
- const loopInfo = result.loopTriggered ? chalk.yellow(" [loop]") : "";
1411
+ const loopInfo = result.loopTriggered ? colors.warning(" [loop]") : "";
1353
1412
  console.log(` ${status} #${result.issueNumber}: ${phases}${loopInfo}${duration}`);
1354
1413
  }
1355
1414
  console.log("");
1356
1415
  if (logPath) {
1357
- console.log(chalk.gray(` 📝 Log: ${logPath}`));
1416
+ console.log(colors.muted(` 📝 Log: ${logPath}`));
1358
1417
  console.log("");
1359
1418
  }
1360
1419
  if (config.dryRun) {
1361
- console.log(chalk.yellow(" ℹ️ This was a dry run. Use without --dry-run to execute."));
1420
+ console.log(colors.warning(" ℹ️ This was a dry run. Use without --dry-run to execute."));
1362
1421
  console.log("");
1363
1422
  }
1364
1423
  // Set exit code if any failed
@@ -1609,7 +1668,11 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
1609
1668
  ? "success"
1610
1669
  : result.error?.includes("Timeout")
1611
1670
  ? "timeout"
1612
- : "failure", { error: result.error });
1671
+ : "failure", {
1672
+ error: result.error,
1673
+ // Include verdict for QA phase (AC-6)
1674
+ verdict: result.verdict,
1675
+ });
1613
1676
  logWriter.logPhase(phaseLog);
1614
1677
  }
1615
1678
  // Track phase completion in state
@@ -4,10 +4,10 @@
4
4
  * Provides success/failure rates, workflow insights, and aggregate statistics.
5
5
  * All data is local - no telemetry is ever sent remotely.
6
6
  */
7
- import chalk from "chalk";
8
7
  import * as fs from "fs";
9
8
  import * as path from "path";
10
9
  import * as os from "os";
10
+ import { ui, colors } from "../lib/cli-ui.js";
11
11
  import { RunLogSchema, LOG_PATHS, } from "../lib/workflow/run-log-schema.js";
12
12
  import { MetricsSchema, METRICS_FILE_PATH, } from "../lib/workflow/metrics-schema.js";
13
13
  /**
@@ -158,46 +158,57 @@ function generateCsv(logs) {
158
158
  return [header, ...csvRows].join("\n");
159
159
  }
160
160
  /**
161
- * Display human-readable statistics
161
+ * Display human-readable statistics with dashboard-style boxes
162
162
  */
163
163
  function displayStats(stats, logDir) {
164
- console.log(chalk.blue("\n📊 Sequant Run Statistics\n"));
165
- console.log(chalk.gray(` Log directory: ${logDir}`));
166
- // Overall summary
167
- console.log(chalk.blue("\n Overview"));
168
- console.log(chalk.gray(` ─────────────────────────────────`));
169
- console.log(chalk.gray(` Total runs analyzed: ${stats.totalRuns}`));
170
- console.log(chalk.gray(` Total issues processed: ${stats.totalIssues}`));
171
- console.log(chalk.gray(` Average run duration: ${formatDuration(stats.avgDurationSeconds)}`));
172
- // Success/failure rates
173
- console.log(chalk.blue("\n Success/Failure Rates"));
174
- console.log(chalk.gray(` ─────────────────────────────────`));
175
- console.log(chalk.green(` Passed: ${stats.passed} (${stats.successRate.toFixed(1)}%)`));
176
- console.log(chalk.red(` Failed: ${stats.failed} (${stats.failureRate.toFixed(1)}%)`));
164
+ console.log(ui.headerBox("SEQUANT ANALYTICS"));
165
+ console.log(colors.muted(`\n Log directory: ${logDir}`));
166
+ console.log(colors.muted(" Local data only - no telemetry\n"));
167
+ // Overview table
168
+ const overviewData = {
169
+ "Total Runs": stats.totalRuns,
170
+ "Issues Processed": stats.totalIssues,
171
+ "Avg Duration": formatDuration(stats.avgDurationSeconds),
172
+ };
173
+ console.log(ui.keyValueTable(overviewData));
174
+ // Success rates with progress bar
175
+ console.log(ui.sectionHeader("Success Rates"));
176
+ const passedBar = ui.progressBar(stats.passed, stats.totalIssues, 12);
177
+ const failedBar = ui.progressBar(stats.failed, stats.totalIssues, 12);
178
+ console.log(` ${colors.success("\u2713 Passed")} ${stats.passed} (${stats.successRate.toFixed(1)}%) ${passedBar}`);
179
+ console.log(` ${colors.error("\u2717 Failed")} ${stats.failed} (${stats.failureRate.toFixed(1)}%) ${failedBar}`);
177
180
  if (stats.partial > 0) {
178
- console.log(chalk.yellow(` Partial: ${stats.partial}`));
181
+ const partialRate = (stats.partial / stats.totalIssues) * 100;
182
+ const partialBar = ui.progressBar(stats.partial, stats.totalIssues, 12);
183
+ console.log(` ${colors.warning("\u26A0 Partial")} ${stats.partial} (${partialRate.toFixed(1)}%) ${partialBar}`);
179
184
  }
180
- // Phase durations
185
+ // Phase durations table
181
186
  if (stats.phaseDurations.size > 0) {
182
- console.log(chalk.blue("\n Average Phase Durations"));
183
- console.log(chalk.gray(` ─────────────────────────────────`));
187
+ console.log(ui.sectionHeader("Phase Durations"));
184
188
  // Sort phases by count (most common first)
185
189
  const sortedPhases = [...stats.phaseDurations.entries()].sort((a, b) => b[1].count - a[1].count);
186
- for (const [phase, data] of sortedPhases) {
187
- const avgFormatted = formatDuration(data.avg);
188
- console.log(chalk.gray(` ${phase.padEnd(10)} ${avgFormatted.padStart(8)} avg (${data.count} runs)`));
189
- }
190
+ const phaseRows = sortedPhases.map(([phase, data]) => [
191
+ phase,
192
+ formatDuration(data.avg),
193
+ data.count,
194
+ ]);
195
+ console.log(ui.table(phaseRows, {
196
+ columns: [
197
+ { header: "Phase", width: 12 },
198
+ { header: "Avg Time", width: 12 },
199
+ { header: "Runs", width: 8 },
200
+ ],
201
+ }));
190
202
  }
191
203
  // Common failures
192
204
  if (stats.commonFailures.size > 0) {
193
- console.log(chalk.blue("\n Common Failure Points"));
194
- console.log(chalk.gray(` ─────────────────────────────────`));
205
+ console.log(ui.sectionHeader("Common Failures"));
195
206
  // Sort by frequency
196
207
  const sortedFailures = [...stats.commonFailures.entries()]
197
208
  .sort((a, b) => b[1] - a[1])
198
209
  .slice(0, 5); // Top 5
199
210
  for (const [error, count] of sortedFailures) {
200
- console.log(chalk.red(` ${count}x ${error}`));
211
+ console.log(` ${colors.error(`${count}x`)} ${error}`);
201
212
  }
202
213
  }
203
214
  console.log("");
@@ -332,39 +343,46 @@ function generateInsights(runs, successRate, avgFilesChanged, avgLinesAdded, cha
332
343
  return insights;
333
344
  }
334
345
  /**
335
- * Display local metrics analytics
346
+ * Display local metrics analytics with dashboard-style boxes
336
347
  */
337
348
  function displayMetricsAnalytics(analytics) {
338
- console.log(chalk.blue("\n📊 Sequant Analytics (local data only)\n"));
339
- // Overall summary
340
- console.log(chalk.gray(` Runs: ${analytics.totalRuns} total`));
341
- console.log(chalk.green(` Success: ${analytics.successCount} (${analytics.successRate.toFixed(0)}%)`));
349
+ console.log(ui.headerBox("SEQUANT ANALYTICS"));
350
+ console.log(colors.muted("\n Local data only - no telemetry\n"));
351
+ // Overview with progress bars
352
+ const total = analytics.successCount + analytics.partialCount + analytics.failedCount;
353
+ const successBar = ui.progressBar(analytics.successCount, total, 12);
354
+ const failedBar = ui.progressBar(analytics.failedCount, total, 12);
355
+ console.log(` Runs: ${analytics.totalRuns} total\n`);
356
+ console.log(` ${colors.success("\u2713 Success")} ${analytics.successCount} (${analytics.successRate.toFixed(0)}%) ${successBar}`);
342
357
  if (analytics.partialCount > 0) {
343
- console.log(chalk.yellow(` Partial: ${analytics.partialCount}`));
358
+ const partialRate = (analytics.partialCount / total) * 100;
359
+ const partialBar = ui.progressBar(analytics.partialCount, total, 12);
360
+ console.log(` ${colors.warning("\u26A0 Partial")} ${analytics.partialCount} (${partialRate.toFixed(0)}%) ${partialBar}`);
344
361
  }
345
362
  if (analytics.failedCount > 0) {
346
- console.log(chalk.red(` Failed: ${analytics.failedCount}`));
363
+ const failedRate = (analytics.failedCount / total) * 100;
364
+ console.log(` ${colors.error("\u2717 Failed")} ${analytics.failedCount} (${failedRate.toFixed(0)}%) ${failedBar}`);
347
365
  }
348
- // Averages
349
- console.log(chalk.blue("\n Averages"));
350
- console.log(chalk.gray(` ─────────────────────────────────`));
366
+ // Averages table
367
+ console.log(ui.sectionHeader("Averages"));
368
+ const avgData = {};
351
369
  if (analytics.avgTokensPerRun > 0) {
352
- console.log(chalk.gray(` Tokens per run: ${analytics.avgTokensPerRun.toLocaleString()}`));
370
+ avgData["Tokens/run"] = analytics.avgTokensPerRun.toLocaleString();
353
371
  }
354
- console.log(chalk.gray(` Files changed: ${analytics.avgFilesChanged.toFixed(1)}`));
372
+ avgData["Files changed"] = analytics.avgFilesChanged.toFixed(1);
355
373
  if (analytics.avgLinesAdded > 0) {
356
- console.log(chalk.gray(` Lines added: ${analytics.avgLinesAdded.toFixed(0)}`));
374
+ avgData["Lines added"] = analytics.avgLinesAdded.toFixed(0);
357
375
  }
358
- console.log(chalk.gray(` Duration: ${formatDuration(analytics.avgDuration)}`));
376
+ avgData["Duration"] = formatDuration(analytics.avgDuration);
377
+ console.log(ui.keyValueTable(avgData));
359
378
  // Insights
360
379
  if (analytics.insights.length > 0) {
361
- console.log(chalk.blue("\n Insights"));
362
- console.log(chalk.gray(` ─────────────────────────────────`));
380
+ console.log(ui.sectionHeader("Insights"));
363
381
  for (const insight of analytics.insights) {
364
- console.log(chalk.gray(` • ${insight}`));
382
+ console.log(` ${colors.accent("\u2022")} ${insight}`);
365
383
  }
366
384
  }
367
- console.log(chalk.gray("\n Data stored locally in .sequant/metrics.json"));
385
+ console.log(colors.muted("\n Data stored locally in .sequant/metrics.json"));
368
386
  console.log("");
369
387
  }
370
388
  /**
@@ -457,9 +475,10 @@ export async function statsCommand(options) {
457
475
  const logDir = resolveLogPath(options.path);
458
476
  const logFiles = listLogFiles(logDir);
459
477
  if (logFiles.length === 0) {
460
- console.log(chalk.blue("\n📊 Sequant Analytics (local data only)\n"));
461
- console.log(chalk.yellow(" No data found."));
462
- console.log(chalk.gray(" Run `npx sequant run <issues>` to collect metrics."));
478
+ console.log(ui.headerBox("SEQUANT ANALYTICS"));
479
+ console.log(colors.muted("\n Local data only - no telemetry\n"));
480
+ console.log(colors.warning(" No data found."));
481
+ console.log(colors.muted(" Run `npx sequant run <issues>` to collect metrics."));
463
482
  console.log("");
464
483
  return;
465
484
  }
@@ -470,7 +489,7 @@ export async function statsCommand(options) {
470
489
  })
471
490
  .filter((log) => log !== null);
472
491
  if (logs.length === 0) {
473
- console.log(chalk.yellow("\n No valid log files found.\n"));
492
+ console.log(colors.warning("\n No valid log files found.\n"));
474
493
  return;
475
494
  }
476
495
  const stats = calculateStats(logs);
@@ -2,6 +2,7 @@
2
2
  * sequant status - Show version, configuration, and workflow state
3
3
  */
4
4
  import chalk from "chalk";
5
+ import { ui, colors } from "../lib/cli-ui.js";
5
6
  import { getManifest, getPackageVersion } from "../lib/manifest.js";
6
7
  import { fileExists } from "../lib/fs.js";
7
8
  import { readdir } from "fs/promises";
@@ -122,7 +123,6 @@ function displayIssueSummary(issues) {
122
123
  console.log(chalk.gray(" Run `sequant run <issue>` to start tracking."));
123
124
  return;
124
125
  }
125
- console.log(chalk.bold("\n Tracked Issues:\n"));
126
126
  // Group by status
127
127
  const byStatus = {
128
128
  in_progress: [],
@@ -136,7 +136,7 @@ function displayIssueSummary(issues) {
136
136
  for (const issue of issues) {
137
137
  byStatus[issue.status].push(issue);
138
138
  }
139
- // Display in priority order
139
+ // Display in priority order using a table
140
140
  const statusOrder = [
141
141
  "in_progress",
142
142
  "waiting_for_qa_gate",
@@ -146,34 +146,51 @@ function displayIssueSummary(issues) {
146
146
  "merged",
147
147
  "abandoned",
148
148
  ];
149
+ // Build rows for the table
150
+ const rows = [];
149
151
  for (const status of statusOrder) {
150
152
  const statusIssues = byStatus[status];
151
- if (statusIssues.length === 0)
152
- continue;
153
153
  for (const issue of statusIssues) {
154
- console.log(formatIssueState(issue));
155
- console.log("");
154
+ const title = issue.title.length > 30
155
+ ? issue.title.substring(0, 27) + "..."
156
+ : issue.title;
157
+ rows.push([
158
+ `#${issue.number}`,
159
+ title,
160
+ colorStatus(issue.status),
161
+ issue.currentPhase || "-",
162
+ ]);
156
163
  }
157
164
  }
165
+ // Display table
166
+ console.log("\n" +
167
+ ui.table(rows, {
168
+ columns: [
169
+ { header: "Issue", width: 8 },
170
+ { header: "Title", width: 32 },
171
+ { header: "Status", width: 20 },
172
+ { header: "Phase", width: 10 },
173
+ ],
174
+ }));
158
175
  // Summary counts
159
176
  const summary = [
160
177
  `Total: ${issues.length}`,
161
178
  byStatus.in_progress.length > 0
162
- ? chalk.blue(`In Progress: ${byStatus.in_progress.length}`)
179
+ ? colors.info(`In Progress: ${byStatus.in_progress.length}`)
163
180
  : null,
164
181
  byStatus.waiting_for_qa_gate.length > 0
165
- ? chalk.yellow(`QA Gate: ${byStatus.waiting_for_qa_gate.length}`)
182
+ ? colors.warning(`QA Gate: ${byStatus.waiting_for_qa_gate.length}`)
166
183
  : null,
167
184
  byStatus.ready_for_merge.length > 0
168
- ? chalk.green(`Ready: ${byStatus.ready_for_merge.length}`)
185
+ ? colors.success(`Ready: ${byStatus.ready_for_merge.length}`)
169
186
  : null,
170
187
  byStatus.blocked.length > 0
171
- ? chalk.yellow(`Blocked: ${byStatus.blocked.length}`)
188
+ ? colors.warning(`Blocked: ${byStatus.blocked.length}`)
172
189
  : null,
173
190
  ]
174
191
  .filter(Boolean)
175
192
  .join(" ");
176
- console.log(chalk.gray(` ${summary}`));
193
+ console.log(`\n ${summary}`);
177
194
  }
178
195
  export async function statusCommand(options = {}) {
179
196
  // Handle --rebuild flag
@@ -191,7 +208,8 @@ export async function statusCommand(options = {}) {
191
208
  await displayIssueState(options);
192
209
  return;
193
210
  }
194
- console.log(chalk.bold("\n📊 Sequant Status\n"));
211
+ console.log(ui.headerBox("SEQUANT STATUS"));
212
+ console.log();
195
213
  // Package version
196
214
  console.log(chalk.gray(`Package version: ${getPackageVersion()}`));
197
215
  // Check initialization
@@ -23,3 +23,9 @@ export { createStateHook, isOrchestrated, getOrchestrationContext, } from "./lib
23
23
  export type { StateHook, StateHookOptions } from "./lib/workflow/state-hook.js";
24
24
  export { rebuildStateFromLogs, cleanupStaleEntries, } from "./lib/workflow/state-utils.js";
25
25
  export type { RebuildOptions, RebuildResult, CleanupOptions, CleanupResult, } from "./lib/workflow/state-utils.js";
26
+ export { analyzeTitleForPhases, analyzeBodyForPhases, analyzeContentForPhases, isTrivialWork, formatContentAnalysis, } from "./lib/content-analyzer.js";
27
+ export type { ContentSignal, ContentAnalysisResult, } from "./lib/content-analyzer.js";
28
+ export { mergePhaseSignals, signalFromLabel, signalsFromLabels, formatMergedPhases, SIGNAL_PRIORITY, } from "./lib/phase-signal.js";
29
+ export type { SignalSource, SignalConfidence, PhaseSignal, MergedPhaseResult, } from "./lib/phase-signal.js";
30
+ export { isSolveComment, findSolveComment, parseSolveWorkflow, solveWorkflowToSignals, solveCoversIssue, } from "./lib/solve-comment-parser.js";
31
+ export type { SolveWorkflowResult, IssueComment, } from "./lib/solve-comment-parser.js";
package/dist/src/index.js CHANGED
@@ -16,3 +16,7 @@ export { StateManager, getStateManager } from "./lib/workflow/state-manager.js";
16
16
  export { createEmptyState, createIssueState, createPhaseState, STATE_FILE_PATH, WORKFLOW_PHASES, } from "./lib/workflow/state-schema.js";
17
17
  export { createStateHook, isOrchestrated, getOrchestrationContext, } from "./lib/workflow/state-hook.js";
18
18
  export { rebuildStateFromLogs, cleanupStaleEntries, } from "./lib/workflow/state-utils.js";
19
+ // Content analysis exports
20
+ export { analyzeTitleForPhases, analyzeBodyForPhases, analyzeContentForPhases, isTrivialWork, formatContentAnalysis, } from "./lib/content-analyzer.js";
21
+ export { mergePhaseSignals, signalFromLabel, signalsFromLabels, formatMergedPhases, SIGNAL_PRIORITY, } from "./lib/phase-signal.js";
22
+ export { isSolveComment, findSolveComment, parseSolveWorkflow, solveWorkflowToSignals, solveCoversIssue, } from "./lib/solve-comment-parser.js";
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Acceptance Criteria Linter
3
+ *
4
+ * Static analysis of acceptance criteria to flag vague, untestable,
5
+ * or incomplete requirements before implementation begins.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { lintAcceptanceCriteria } from './ac-linter';
10
+ * import { parseAcceptanceCriteria } from './ac-parser';
11
+ *
12
+ * const criteria = parseAcceptanceCriteria(issueBody);
13
+ * const lintResults = lintAcceptanceCriteria(criteria);
14
+ *
15
+ * console.log(formatACLintResults(lintResults));
16
+ * ```
17
+ */
18
+ import type { AcceptanceCriterion } from "./workflow/state-schema.js";
19
+ /**
20
+ * Types of issues that can be flagged in AC
21
+ */
22
+ export type ACLintIssueType = "vague" | "unmeasurable" | "incomplete" | "open_ended";
23
+ /**
24
+ * A lint issue found in an acceptance criterion
25
+ */
26
+ export interface ACLintIssue {
27
+ /** Type of issue detected */
28
+ type: ACLintIssueType;
29
+ /** The matched pattern that triggered this issue */
30
+ matchedPattern: string;
31
+ /** Human-readable description of the problem */
32
+ problem: string;
33
+ /** Suggested improvement */
34
+ suggestion: string;
35
+ }
36
+ /**
37
+ * Lint result for a single acceptance criterion
38
+ */
39
+ export interface ACLintResult {
40
+ /** The AC being linted */
41
+ ac: AcceptanceCriterion;
42
+ /** Issues found (empty if AC is clear) */
43
+ issues: ACLintIssue[];
44
+ /** Whether this AC passed linting (no issues) */
45
+ passed: boolean;
46
+ }
47
+ /**
48
+ * Overall lint results for all acceptance criteria
49
+ */
50
+ export interface ACLintResults {
51
+ /** Results for each AC */
52
+ results: ACLintResult[];
53
+ /** Summary counts */
54
+ summary: {
55
+ total: number;
56
+ passed: number;
57
+ flagged: number;
58
+ };
59
+ /** Whether any issues were found */
60
+ hasIssues: boolean;
61
+ }
62
+ /**
63
+ * Pattern definition for detecting issues
64
+ */
65
+ interface LintPattern {
66
+ /** Regular expression to match (case-insensitive) */
67
+ regex: RegExp;
68
+ /** Type of issue this pattern indicates */
69
+ type: ACLintIssueType;
70
+ /** Problem description */
71
+ problem: string;
72
+ /** Suggested fix */
73
+ suggestion: string;
74
+ }
75
+ /**
76
+ * Lint a single acceptance criterion against all patterns
77
+ *
78
+ * @param ac - The acceptance criterion to lint
79
+ * @param patterns - Optional custom patterns (defaults to DEFAULT_LINT_PATTERNS)
80
+ * @returns Lint result with any issues found
81
+ */
82
+ export declare function lintAcceptanceCriterion(ac: AcceptanceCriterion, patterns?: LintPattern[]): ACLintResult;
83
+ /**
84
+ * Lint all acceptance criteria
85
+ *
86
+ * @param criteria - Array of acceptance criteria to lint
87
+ * @param patterns - Optional custom patterns
88
+ * @returns Complete lint results with summary
89
+ */
90
+ export declare function lintAcceptanceCriteria(criteria: AcceptanceCriterion[], patterns?: LintPattern[]): ACLintResults;
91
+ /**
92
+ * Format lint results as markdown for the spec output
93
+ *
94
+ * @param results - Lint results to format
95
+ * @returns Markdown-formatted output string
96
+ */
97
+ export declare function formatACLintResults(results: ACLintResults): string;
98
+ /**
99
+ * Get the default lint patterns
100
+ *
101
+ * @returns Copy of the default lint patterns
102
+ */
103
+ export declare function getDefaultLintPatterns(): LintPattern[];
104
+ /**
105
+ * Create custom lint patterns from a simplified configuration
106
+ *
107
+ * @param config - Array of pattern configurations
108
+ * @returns Array of LintPattern objects
109
+ */
110
+ export declare function createLintPatterns(config: Array<{
111
+ pattern: string;
112
+ type: ACLintIssueType;
113
+ problem: string;
114
+ suggestion: string;
115
+ }>): LintPattern[];
116
+ export {};