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.
- package/README.md +93 -7
- package/dist/bin/cli.js +12 -9
- package/dist/src/commands/doctor.js +25 -20
- package/dist/src/commands/init.js +152 -65
- package/dist/src/commands/logs.js +7 -6
- package/dist/src/commands/run.d.ts +13 -1
- package/dist/src/commands/run.js +75 -12
- package/dist/src/commands/stats.js +67 -48
- package/dist/src/commands/status.js +30 -12
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +4 -0
- package/dist/src/lib/ac-linter.d.ts +116 -0
- package/dist/src/lib/ac-linter.js +304 -0
- package/dist/src/lib/cli-ui.d.ts +196 -0
- package/dist/src/lib/cli-ui.js +544 -0
- package/dist/src/lib/content-analyzer.d.ts +89 -0
- package/dist/src/lib/content-analyzer.js +437 -0
- package/dist/src/lib/phase-signal.d.ts +94 -0
- package/dist/src/lib/phase-signal.js +171 -0
- package/dist/src/lib/plugin-version-sync.d.ts +26 -0
- package/dist/src/lib/plugin-version-sync.js +91 -0
- package/dist/src/lib/project-name.d.ts +40 -0
- package/dist/src/lib/project-name.js +191 -0
- package/dist/src/lib/semgrep.d.ts +136 -0
- package/dist/src/lib/semgrep.js +406 -0
- package/dist/src/lib/solve-comment-parser.d.ts +84 -0
- package/dist/src/lib/solve-comment-parser.js +200 -0
- package/dist/src/lib/stack-config.d.ts +51 -0
- package/dist/src/lib/stack-config.js +77 -0
- package/dist/src/lib/stacks.d.ts +66 -0
- package/dist/src/lib/stacks.js +332 -0
- package/dist/src/lib/templates.d.ts +2 -0
- package/dist/src/lib/templates.js +12 -3
- package/dist/src/lib/upstream/assessment.d.ts +70 -0
- package/dist/src/lib/upstream/assessment.js +385 -0
- package/dist/src/lib/upstream/index.d.ts +11 -0
- package/dist/src/lib/upstream/index.js +14 -0
- package/dist/src/lib/upstream/issues.d.ts +38 -0
- package/dist/src/lib/upstream/issues.js +267 -0
- package/dist/src/lib/upstream/relevance.d.ts +50 -0
- package/dist/src/lib/upstream/relevance.js +209 -0
- package/dist/src/lib/upstream/report.d.ts +29 -0
- package/dist/src/lib/upstream/report.js +391 -0
- package/dist/src/lib/upstream/types.d.ts +207 -0
- package/dist/src/lib/upstream/types.js +5 -0
- package/dist/src/lib/workflow/log-writer.d.ts +1 -1
- package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
- package/dist/src/lib/workflow/qa-cache.d.ts +199 -0
- package/dist/src/lib/workflow/qa-cache.js +440 -0
- package/dist/src/lib/workflow/run-log-schema.d.ts +34 -6
- package/dist/src/lib/workflow/run-log-schema.js +12 -1
- package/dist/src/lib/workflow/state-schema.d.ts +4 -4
- package/dist/src/lib/workflow/types.d.ts +4 -0
- package/package.json +6 -1
- package/templates/hooks/pre-tool.sh +6 -0
- package/templates/memory/constitution.md +1 -5
- package/templates/skills/_shared/references/prompt-templates.md +350 -0
- package/templates/skills/_shared/references/subagent-types.md +131 -0
- package/templates/skills/exec/SKILL.md +82 -0
- package/templates/skills/fullsolve/SKILL.md +19 -2
- package/templates/skills/loop/SKILL.md +3 -1
- package/templates/skills/qa/SKILL.md +79 -9
- package/templates/skills/qa/references/quality-gates.md +85 -1
- package/templates/skills/qa/references/semgrep-rules.md +207 -0
- package/templates/skills/qa/scripts/quality-checks.sh +525 -15
- 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
|
*/
|
package/dist/src/commands/run.js
CHANGED
|
@@ -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(
|
|
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(
|
|
1341
|
-
console.log(
|
|
1342
|
-
console.log(
|
|
1343
|
-
console.log(
|
|
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
|
|
1402
|
+
const status = result.success
|
|
1403
|
+
? ui.statusIcon("success")
|
|
1404
|
+
: ui.statusIcon("error");
|
|
1346
1405
|
const duration = result.durationSeconds
|
|
1347
|
-
?
|
|
1406
|
+
? colors.muted(` (${formatDuration(result.durationSeconds)})`)
|
|
1348
1407
|
: "";
|
|
1349
1408
|
const phases = result.phaseResults
|
|
1350
|
-
.map((p) =>
|
|
1409
|
+
.map((p) => p.success ? colors.success(p.phase) : colors.error(p.phase))
|
|
1351
1410
|
.join(" → ");
|
|
1352
|
-
const loopInfo = result.loopTriggered ?
|
|
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(
|
|
1416
|
+
console.log(colors.muted(` 📝 Log: ${logPath}`));
|
|
1358
1417
|
console.log("");
|
|
1359
1418
|
}
|
|
1360
1419
|
if (config.dryRun) {
|
|
1361
|
-
console.log(
|
|
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", {
|
|
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(
|
|
165
|
-
console.log(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
console.log(
|
|
174
|
-
|
|
175
|
-
console.log(
|
|
176
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
350
|
-
|
|
366
|
+
// Averages table
|
|
367
|
+
console.log(ui.sectionHeader("Averages"));
|
|
368
|
+
const avgData = {};
|
|
351
369
|
if (analytics.avgTokensPerRun > 0) {
|
|
352
|
-
|
|
370
|
+
avgData["Tokens/run"] = analytics.avgTokensPerRun.toLocaleString();
|
|
353
371
|
}
|
|
354
|
-
|
|
372
|
+
avgData["Files changed"] = analytics.avgFilesChanged.toFixed(1);
|
|
355
373
|
if (analytics.avgLinesAdded > 0) {
|
|
356
|
-
|
|
374
|
+
avgData["Lines added"] = analytics.avgLinesAdded.toFixed(0);
|
|
357
375
|
}
|
|
358
|
-
|
|
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(
|
|
362
|
-
console.log(chalk.gray(` ─────────────────────────────────`));
|
|
380
|
+
console.log(ui.sectionHeader("Insights"));
|
|
363
381
|
for (const insight of analytics.insights) {
|
|
364
|
-
console.log(
|
|
382
|
+
console.log(` ${colors.accent("\u2022")} ${insight}`);
|
|
365
383
|
}
|
|
366
384
|
}
|
|
367
|
-
console.log(
|
|
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(
|
|
461
|
-
console.log(
|
|
462
|
-
console.log(
|
|
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(
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
?
|
|
179
|
+
? colors.info(`In Progress: ${byStatus.in_progress.length}`)
|
|
163
180
|
: null,
|
|
164
181
|
byStatus.waiting_for_qa_gate.length > 0
|
|
165
|
-
?
|
|
182
|
+
? colors.warning(`QA Gate: ${byStatus.waiting_for_qa_gate.length}`)
|
|
166
183
|
: null,
|
|
167
184
|
byStatus.ready_for_merge.length > 0
|
|
168
|
-
?
|
|
185
|
+
? colors.success(`Ready: ${byStatus.ready_for_merge.length}`)
|
|
169
186
|
: null,
|
|
170
187
|
byStatus.blocked.length > 0
|
|
171
|
-
?
|
|
188
|
+
? colors.warning(`Blocked: ${byStatus.blocked.length}`)
|
|
172
189
|
: null,
|
|
173
190
|
]
|
|
174
191
|
.filter(Boolean)
|
|
175
192
|
.join(" ");
|
|
176
|
-
console.log(
|
|
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(
|
|
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
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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 {};
|