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.
- package/README.md +10 -8
- package/dist/bin/cli.js +19 -9
- package/dist/src/commands/doctor.js +42 -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 +122 -32
- package/dist/src/commands/stats.js +67 -48
- package/dist/src/commands/status.js +30 -12
- package/dist/src/commands/sync.d.ts +28 -0
- package/dist/src/commands/sync.js +102 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +4 -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/phase-spinner.d.ts +146 -0
- package/dist/src/lib/phase-spinner.js +255 -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 +52 -0
- package/dist/src/lib/stacks.js +173 -0
- package/dist/src/lib/templates.d.ts +2 -0
- package/dist/src/lib/templates.js +9 -2
- 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/skills/qa/scripts/quality-checks.sh +509 -53
- package/templates/skills/solve/SKILL.md +375 -83
- package/templates/skills/spec/SKILL.md +107 -5
package/dist/src/commands/run.js
CHANGED
|
@@ -20,6 +20,8 @@ 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";
|
|
24
|
+
import { PhaseSpinner } from "../lib/phase-spinner.js";
|
|
23
25
|
/**
|
|
24
26
|
* Slugify a title for branch naming
|
|
25
27
|
*/
|
|
@@ -30,6 +32,33 @@ function slugify(title) {
|
|
|
30
32
|
.replace(/^-+|-+$/g, "")
|
|
31
33
|
.substring(0, 50);
|
|
32
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse QA verdict from phase output
|
|
37
|
+
*
|
|
38
|
+
* Looks for verdict patterns in the QA output:
|
|
39
|
+
* - "### Verdict: READY_FOR_MERGE"
|
|
40
|
+
* - "**Verdict:** AC_NOT_MET"
|
|
41
|
+
* - "Verdict: AC_MET_BUT_NOT_A_PLUS"
|
|
42
|
+
*
|
|
43
|
+
* @param output - The captured output from QA phase
|
|
44
|
+
* @returns The parsed verdict or null if not found
|
|
45
|
+
*/
|
|
46
|
+
export function parseQaVerdict(output) {
|
|
47
|
+
if (!output)
|
|
48
|
+
return null;
|
|
49
|
+
// Match various verdict formats:
|
|
50
|
+
// - "### Verdict: X" (markdown header)
|
|
51
|
+
// - "**Verdict:** X" (bold label with colon inside)
|
|
52
|
+
// - "**Verdict:** **X**" (bold label and bold value)
|
|
53
|
+
// - "Verdict: X" (plain)
|
|
54
|
+
// Case insensitive, handles optional markdown formatting
|
|
55
|
+
const verdictMatch = output.match(/(?:###?\s*)?(?:\*\*)?Verdict:?\*?\*?\s*\*?\*?\s*(READY_FOR_MERGE|AC_MET_BUT_NOT_A_PLUS|AC_NOT_MET|NEEDS_VERIFICATION)\*?\*?/i);
|
|
56
|
+
if (!verdictMatch)
|
|
57
|
+
return null;
|
|
58
|
+
// Normalize to uppercase with underscores
|
|
59
|
+
const verdict = verdictMatch[1].toUpperCase().replace(/-/g, "_");
|
|
60
|
+
return verdict;
|
|
61
|
+
}
|
|
33
62
|
/**
|
|
34
63
|
* Get the git repository root directory
|
|
35
64
|
*/
|
|
@@ -553,7 +582,7 @@ const ISOLATED_PHASES = ["exec", "test", "qa"];
|
|
|
553
582
|
/**
|
|
554
583
|
* Execute a single phase for an issue using Claude Agent SDK
|
|
555
584
|
*/
|
|
556
|
-
async function executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager) {
|
|
585
|
+
async function executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner) {
|
|
557
586
|
const startTime = Date.now();
|
|
558
587
|
if (config.dryRun) {
|
|
559
588
|
// Dry run - just simulate
|
|
@@ -658,7 +687,10 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath,
|
|
|
658
687
|
capturedOutput += textContent;
|
|
659
688
|
// Show streaming output in verbose mode
|
|
660
689
|
if (config.verbose) {
|
|
690
|
+
// Pause spinner during verbose streaming to avoid terminal corruption
|
|
691
|
+
spinner?.pause();
|
|
661
692
|
process.stdout.write(chalk.gray(textContent));
|
|
693
|
+
spinner?.resume();
|
|
662
694
|
}
|
|
663
695
|
}
|
|
664
696
|
}
|
|
@@ -676,6 +708,35 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath,
|
|
|
676
708
|
// Check result status
|
|
677
709
|
if (resultMessage) {
|
|
678
710
|
if (resultMessage.subtype === "success") {
|
|
711
|
+
// For QA phase, check the verdict to determine actual success
|
|
712
|
+
// SDK "success" just means the query completed - we need to parse the verdict
|
|
713
|
+
if (phase === "qa" && capturedOutput) {
|
|
714
|
+
const verdict = parseQaVerdict(capturedOutput);
|
|
715
|
+
// Only READY_FOR_MERGE and NEEDS_VERIFICATION are considered passing
|
|
716
|
+
// NEEDS_VERIFICATION is external verification, not a code quality issue
|
|
717
|
+
if (verdict &&
|
|
718
|
+
verdict !== "READY_FOR_MERGE" &&
|
|
719
|
+
verdict !== "NEEDS_VERIFICATION") {
|
|
720
|
+
return {
|
|
721
|
+
phase,
|
|
722
|
+
success: false,
|
|
723
|
+
durationSeconds,
|
|
724
|
+
error: `QA verdict: ${verdict}`,
|
|
725
|
+
sessionId: resultSessionId,
|
|
726
|
+
output: capturedOutput,
|
|
727
|
+
verdict, // Include parsed verdict
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
// Pass case - include verdict for logging
|
|
731
|
+
return {
|
|
732
|
+
phase,
|
|
733
|
+
success: true,
|
|
734
|
+
durationSeconds,
|
|
735
|
+
sessionId: resultSessionId,
|
|
736
|
+
output: capturedOutput,
|
|
737
|
+
verdict: verdict ?? undefined, // Include if found
|
|
738
|
+
};
|
|
739
|
+
}
|
|
679
740
|
return {
|
|
680
741
|
phase,
|
|
681
742
|
success: true,
|
|
@@ -924,7 +985,7 @@ function parseBatches(batchArgs) {
|
|
|
924
985
|
* Main run command
|
|
925
986
|
*/
|
|
926
987
|
export async function runCommand(issues, options) {
|
|
927
|
-
console.log(
|
|
988
|
+
console.log(ui.headerBox("SEQUANT WORKFLOW"));
|
|
928
989
|
// Version freshness check (cached, non-blocking, respects --quiet)
|
|
929
990
|
if (!options.quiet) {
|
|
930
991
|
try {
|
|
@@ -1337,28 +1398,30 @@ export async function runCommand(issues, options) {
|
|
|
1337
1398
|
}
|
|
1338
1399
|
}
|
|
1339
1400
|
// Summary
|
|
1340
|
-
console.log(
|
|
1341
|
-
console.log(
|
|
1342
|
-
console.log(
|
|
1343
|
-
console.log(
|
|
1401
|
+
console.log("\n" + ui.divider());
|
|
1402
|
+
console.log(colors.info(" Summary"));
|
|
1403
|
+
console.log(ui.divider());
|
|
1404
|
+
console.log(colors.muted(`\n Results: ${colors.success(`${passed} passed`)}, ${colors.error(`${failed} failed`)}`));
|
|
1344
1405
|
for (const result of results) {
|
|
1345
|
-
const status = result.success
|
|
1406
|
+
const status = result.success
|
|
1407
|
+
? ui.statusIcon("success")
|
|
1408
|
+
: ui.statusIcon("error");
|
|
1346
1409
|
const duration = result.durationSeconds
|
|
1347
|
-
?
|
|
1410
|
+
? colors.muted(` (${formatDuration(result.durationSeconds)})`)
|
|
1348
1411
|
: "";
|
|
1349
1412
|
const phases = result.phaseResults
|
|
1350
|
-
.map((p) =>
|
|
1413
|
+
.map((p) => p.success ? colors.success(p.phase) : colors.error(p.phase))
|
|
1351
1414
|
.join(" ā ");
|
|
1352
|
-
const loopInfo = result.loopTriggered ?
|
|
1415
|
+
const loopInfo = result.loopTriggered ? colors.warning(" [loop]") : "";
|
|
1353
1416
|
console.log(` ${status} #${result.issueNumber}: ${phases}${loopInfo}${duration}`);
|
|
1354
1417
|
}
|
|
1355
1418
|
console.log("");
|
|
1356
1419
|
if (logPath) {
|
|
1357
|
-
console.log(
|
|
1420
|
+
console.log(colors.muted(` š Log: ${logPath}`));
|
|
1358
1421
|
console.log("");
|
|
1359
1422
|
}
|
|
1360
1423
|
if (config.dryRun) {
|
|
1361
|
-
console.log(
|
|
1424
|
+
console.log(colors.warning(" ā¹ļø This was a dry run. Use without --dry-run to execute."));
|
|
1362
1425
|
console.log("");
|
|
1363
1426
|
}
|
|
1364
1427
|
// Set exit code if any failed
|
|
@@ -1457,7 +1520,14 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1457
1520
|
else {
|
|
1458
1521
|
// Run spec first to get recommended workflow
|
|
1459
1522
|
console.log(chalk.gray(` Running spec to determine workflow...`));
|
|
1460
|
-
|
|
1523
|
+
// Create spinner for spec phase (1 of estimated 3: spec, exec, qa)
|
|
1524
|
+
const specSpinner = new PhaseSpinner({
|
|
1525
|
+
phase: "spec",
|
|
1526
|
+
phaseIndex: 1,
|
|
1527
|
+
totalPhases: 3, // Estimate; will be refined after spec
|
|
1528
|
+
shutdownManager,
|
|
1529
|
+
});
|
|
1530
|
+
specSpinner.start();
|
|
1461
1531
|
// Track spec phase start in state
|
|
1462
1532
|
if (stateManager) {
|
|
1463
1533
|
try {
|
|
@@ -1470,7 +1540,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1470
1540
|
const specStartTime = new Date();
|
|
1471
1541
|
// Note: spec runs in main repo (not worktree) for planning
|
|
1472
1542
|
const specResult = await executePhase(issueNumber, "spec", config, sessionId, worktreePath, // Will be ignored for spec (non-isolated phase)
|
|
1473
|
-
shutdownManager);
|
|
1543
|
+
shutdownManager, specSpinner);
|
|
1474
1544
|
const specEndTime = new Date();
|
|
1475
1545
|
if (specResult.sessionId) {
|
|
1476
1546
|
sessionId = specResult.sessionId;
|
|
@@ -1508,7 +1578,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1508
1578
|
}
|
|
1509
1579
|
}
|
|
1510
1580
|
if (!specResult.success) {
|
|
1511
|
-
|
|
1581
|
+
specSpinner.fail(specResult.error);
|
|
1512
1582
|
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
1513
1583
|
return {
|
|
1514
1584
|
issueNumber,
|
|
@@ -1518,10 +1588,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1518
1588
|
loopTriggered: false,
|
|
1519
1589
|
};
|
|
1520
1590
|
}
|
|
1521
|
-
|
|
1522
|
-
? ` (${formatDuration(specResult.durationSeconds)})`
|
|
1523
|
-
: "";
|
|
1524
|
-
console.log(chalk.green(` ā spec${duration}`));
|
|
1591
|
+
specSpinner.succeed();
|
|
1525
1592
|
// Parse recommended workflow from spec output
|
|
1526
1593
|
const parsedWorkflow = specResult.output
|
|
1527
1594
|
? parseRecommendedWorkflow(specResult.output)
|
|
@@ -1575,8 +1642,22 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1575
1642
|
loopTriggered = true;
|
|
1576
1643
|
}
|
|
1577
1644
|
let phasesFailed = false;
|
|
1578
|
-
|
|
1579
|
-
|
|
1645
|
+
// Calculate total phases for progress indicator
|
|
1646
|
+
// If spec already ran in auto-detect mode, it's counted separately
|
|
1647
|
+
const totalPhases = specAlreadyRan ? phases.length + 1 : phases.length;
|
|
1648
|
+
const phaseIndexOffset = specAlreadyRan ? 1 : 0;
|
|
1649
|
+
for (let phaseIdx = 0; phaseIdx < phases.length; phaseIdx++) {
|
|
1650
|
+
const phase = phases[phaseIdx];
|
|
1651
|
+
const phaseNumber = phaseIdx + 1 + phaseIndexOffset;
|
|
1652
|
+
// Create spinner for this phase
|
|
1653
|
+
const phaseSpinner = new PhaseSpinner({
|
|
1654
|
+
phase,
|
|
1655
|
+
phaseIndex: phaseNumber,
|
|
1656
|
+
totalPhases,
|
|
1657
|
+
shutdownManager,
|
|
1658
|
+
iteration: useQualityLoop ? iteration : undefined,
|
|
1659
|
+
});
|
|
1660
|
+
phaseSpinner.start();
|
|
1580
1661
|
// Track phase start in state
|
|
1581
1662
|
if (stateManager) {
|
|
1582
1663
|
try {
|
|
@@ -1587,7 +1668,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1587
1668
|
}
|
|
1588
1669
|
}
|
|
1589
1670
|
const phaseStartTime = new Date();
|
|
1590
|
-
const result = await executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager);
|
|
1671
|
+
const result = await executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, phaseSpinner);
|
|
1591
1672
|
const phaseEndTime = new Date();
|
|
1592
1673
|
// Capture session ID for subsequent phases
|
|
1593
1674
|
if (result.sessionId) {
|
|
@@ -1609,7 +1690,11 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1609
1690
|
? "success"
|
|
1610
1691
|
: result.error?.includes("Timeout")
|
|
1611
1692
|
? "timeout"
|
|
1612
|
-
: "failure", {
|
|
1693
|
+
: "failure", {
|
|
1694
|
+
error: result.error,
|
|
1695
|
+
// Include verdict for QA phase (AC-6)
|
|
1696
|
+
verdict: result.verdict,
|
|
1697
|
+
});
|
|
1613
1698
|
logWriter.logPhase(phaseLog);
|
|
1614
1699
|
}
|
|
1615
1700
|
// Track phase completion in state
|
|
@@ -1627,29 +1712,34 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1627
1712
|
}
|
|
1628
1713
|
}
|
|
1629
1714
|
if (result.success) {
|
|
1630
|
-
|
|
1631
|
-
? ` (${formatDuration(result.durationSeconds)})`
|
|
1632
|
-
: "";
|
|
1633
|
-
console.log(chalk.green(` ā ${phase}${duration}`));
|
|
1715
|
+
phaseSpinner.succeed();
|
|
1634
1716
|
}
|
|
1635
1717
|
else {
|
|
1636
|
-
|
|
1718
|
+
phaseSpinner.fail(result.error);
|
|
1637
1719
|
phasesFailed = true;
|
|
1638
1720
|
// If quality loop enabled, run loop phase to fix issues
|
|
1639
1721
|
if (useQualityLoop && iteration < maxIterations) {
|
|
1640
|
-
|
|
1641
|
-
const
|
|
1722
|
+
// Create spinner for loop phase
|
|
1723
|
+
const loopSpinner = new PhaseSpinner({
|
|
1724
|
+
phase: "loop",
|
|
1725
|
+
phaseIndex: phaseNumber,
|
|
1726
|
+
totalPhases,
|
|
1727
|
+
shutdownManager,
|
|
1728
|
+
iteration,
|
|
1729
|
+
});
|
|
1730
|
+
loopSpinner.start();
|
|
1731
|
+
const loopResult = await executePhase(issueNumber, "loop", config, sessionId, worktreePath, shutdownManager, loopSpinner);
|
|
1642
1732
|
phaseResults.push(loopResult);
|
|
1643
1733
|
if (loopResult.sessionId) {
|
|
1644
1734
|
sessionId = loopResult.sessionId;
|
|
1645
1735
|
}
|
|
1646
1736
|
if (loopResult.success) {
|
|
1647
|
-
|
|
1737
|
+
loopSpinner.succeed();
|
|
1648
1738
|
// Continue to next iteration
|
|
1649
1739
|
break;
|
|
1650
1740
|
}
|
|
1651
1741
|
else {
|
|
1652
|
-
|
|
1742
|
+
loopSpinner.fail(loopResult.error);
|
|
1653
1743
|
}
|
|
1654
1744
|
}
|
|
1655
1745
|
// Stop on first failure (if not in quality loop or loop failed)
|
|
@@ -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
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sequant sync - Fast, non-interactive template sync
|
|
3
|
+
*
|
|
4
|
+
* Syncs skills and other templates from the package to the local project.
|
|
5
|
+
* Designed for plugin users who need to update after upgrading sequant.
|
|
6
|
+
*/
|
|
7
|
+
interface SyncOptions {
|
|
8
|
+
force?: boolean;
|
|
9
|
+
quiet?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get the version of skills currently installed
|
|
13
|
+
*/
|
|
14
|
+
export declare function getSkillsVersion(): Promise<string | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Check if skills are outdated compared to package version
|
|
17
|
+
*/
|
|
18
|
+
export declare function areSkillsOutdated(): Promise<{
|
|
19
|
+
outdated: boolean;
|
|
20
|
+
currentVersion: string | null;
|
|
21
|
+
packageVersion: string;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function syncCommand(options?: SyncOptions): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Check and warn if skills are outdated (for use by other commands)
|
|
26
|
+
*/
|
|
27
|
+
export declare function checkAndWarnSkillsOutdated(): Promise<boolean>;
|
|
28
|
+
export {};
|