sequant 1.13.0 → 1.13.2
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/.claude-plugin/marketplace.json +22 -0
- package/.claude-plugin/plugin.json +9 -0
- package/dist/bin/cli.js +7 -0
- package/dist/src/commands/doctor.js +17 -0
- package/dist/src/commands/run.js +47 -20
- package/dist/src/commands/sync.d.ts +28 -0
- package/dist/src/commands/sync.js +102 -0
- package/dist/src/lib/phase-spinner.d.ts +146 -0
- package/dist/src/lib/phase-spinner.js +255 -0
- package/dist/src/lib/templates.js +4 -0
- package/package.json +3 -2
- package/templates/skills/solve/SKILL.md +375 -83
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
|
+
"name": "sequant",
|
|
4
|
+
"description": "Sequant plugin marketplace - structured workflow system for Claude Code",
|
|
5
|
+
"owner": {
|
|
6
|
+
"name": "admarble",
|
|
7
|
+
"email": "github@admarble.com"
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "sequant",
|
|
12
|
+
"description": "Structured workflow system for Claude Code - GitHub issue resolution with spec, exec, test, and QA phases. Includes 16 skills for planning, implementation, testing, code review, and quality assurance.",
|
|
13
|
+
"version": "1.13.0",
|
|
14
|
+
"author": {
|
|
15
|
+
"name": "admarble",
|
|
16
|
+
"email": "github@admarble.com"
|
|
17
|
+
},
|
|
18
|
+
"source": "./",
|
|
19
|
+
"category": "workflow"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
package/dist/bin/cli.js
CHANGED
|
@@ -43,6 +43,7 @@ import { logsCommand } from "../src/commands/logs.js";
|
|
|
43
43
|
import { statsCommand } from "../src/commands/stats.js";
|
|
44
44
|
import { dashboardCommand } from "../src/commands/dashboard.js";
|
|
45
45
|
import { stateInitCommand, stateRebuildCommand, stateCleanCommand, } from "../src/commands/state.js";
|
|
46
|
+
import { syncCommand } from "../src/commands/sync.js";
|
|
46
47
|
const program = new Command();
|
|
47
48
|
// Handle --no-color before parsing
|
|
48
49
|
if (process.argv.includes("--no-color")) {
|
|
@@ -85,6 +86,12 @@ program
|
|
|
85
86
|
.option("-d, --dry-run", "Show what would be updated without making changes")
|
|
86
87
|
.option("-f, --force", "Overwrite local modifications")
|
|
87
88
|
.action(updateCommand);
|
|
89
|
+
program
|
|
90
|
+
.command("sync")
|
|
91
|
+
.description("Sync skills and templates from the Sequant package (non-interactive)")
|
|
92
|
+
.option("-f, --force", "Sync even if versions match")
|
|
93
|
+
.option("-q, --quiet", "Suppress output")
|
|
94
|
+
.action(syncCommand);
|
|
88
95
|
program
|
|
89
96
|
.command("doctor")
|
|
90
97
|
.description("Check your Sequant installation for issues")
|
|
@@ -8,6 +8,7 @@ import { fileExists, isExecutable } from "../lib/fs.js";
|
|
|
8
8
|
import { getManifest } from "../lib/manifest.js";
|
|
9
9
|
import { commandExists, isGhAuthenticated, isNativeWindows, isWSL, checkOptionalMcpServers, getMcpServersConfig, OPTIONAL_MCP_SERVERS, } from "../lib/system.js";
|
|
10
10
|
import { checkVersionThorough, getVersionWarning, } from "../lib/version-check.js";
|
|
11
|
+
import { areSkillsOutdated } from "./sync.js";
|
|
11
12
|
/**
|
|
12
13
|
* Labels that indicate an issue should be skipped from closed-issue verification
|
|
13
14
|
* (case-insensitive matching)
|
|
@@ -157,6 +158,22 @@ export async function doctorCommand(options = {}) {
|
|
|
157
158
|
message: `Missing skills: ${missingSkills.join(", ")}`,
|
|
158
159
|
});
|
|
159
160
|
}
|
|
161
|
+
// Check 3.5: Skills version (are skills in sync with package?)
|
|
162
|
+
const skillsStatus = await areSkillsOutdated();
|
|
163
|
+
if (skillsStatus.outdated) {
|
|
164
|
+
checks.push({
|
|
165
|
+
name: "Skills Version",
|
|
166
|
+
status: "warn",
|
|
167
|
+
message: `Outdated (${skillsStatus.currentVersion || "unknown"} → ${skillsStatus.packageVersion}) - run: npx sequant sync`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
checks.push({
|
|
172
|
+
name: "Skills Version",
|
|
173
|
+
status: "pass",
|
|
174
|
+
message: `Up to date (${skillsStatus.packageVersion})`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
160
177
|
// Check 4: Hooks directory
|
|
161
178
|
const hooksExist = await fileExists(".claude/hooks");
|
|
162
179
|
if (hooksExist) {
|
package/dist/src/commands/run.js
CHANGED
|
@@ -21,6 +21,7 @@ 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
23
|
import { ui, colors } from "../lib/cli-ui.js";
|
|
24
|
+
import { PhaseSpinner } from "../lib/phase-spinner.js";
|
|
24
25
|
/**
|
|
25
26
|
* Slugify a title for branch naming
|
|
26
27
|
*/
|
|
@@ -581,7 +582,7 @@ const ISOLATED_PHASES = ["exec", "test", "qa"];
|
|
|
581
582
|
/**
|
|
582
583
|
* Execute a single phase for an issue using Claude Agent SDK
|
|
583
584
|
*/
|
|
584
|
-
async function executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager) {
|
|
585
|
+
async function executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner) {
|
|
585
586
|
const startTime = Date.now();
|
|
586
587
|
if (config.dryRun) {
|
|
587
588
|
// Dry run - just simulate
|
|
@@ -686,7 +687,10 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath,
|
|
|
686
687
|
capturedOutput += textContent;
|
|
687
688
|
// Show streaming output in verbose mode
|
|
688
689
|
if (config.verbose) {
|
|
690
|
+
// Pause spinner during verbose streaming to avoid terminal corruption
|
|
691
|
+
spinner?.pause();
|
|
689
692
|
process.stdout.write(chalk.gray(textContent));
|
|
693
|
+
spinner?.resume();
|
|
690
694
|
}
|
|
691
695
|
}
|
|
692
696
|
}
|
|
@@ -1516,7 +1520,14 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1516
1520
|
else {
|
|
1517
1521
|
// Run spec first to get recommended workflow
|
|
1518
1522
|
console.log(chalk.gray(` Running spec to determine workflow...`));
|
|
1519
|
-
|
|
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();
|
|
1520
1531
|
// Track spec phase start in state
|
|
1521
1532
|
if (stateManager) {
|
|
1522
1533
|
try {
|
|
@@ -1529,7 +1540,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1529
1540
|
const specStartTime = new Date();
|
|
1530
1541
|
// Note: spec runs in main repo (not worktree) for planning
|
|
1531
1542
|
const specResult = await executePhase(issueNumber, "spec", config, sessionId, worktreePath, // Will be ignored for spec (non-isolated phase)
|
|
1532
|
-
shutdownManager);
|
|
1543
|
+
shutdownManager, specSpinner);
|
|
1533
1544
|
const specEndTime = new Date();
|
|
1534
1545
|
if (specResult.sessionId) {
|
|
1535
1546
|
sessionId = specResult.sessionId;
|
|
@@ -1567,7 +1578,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1567
1578
|
}
|
|
1568
1579
|
}
|
|
1569
1580
|
if (!specResult.success) {
|
|
1570
|
-
|
|
1581
|
+
specSpinner.fail(specResult.error);
|
|
1571
1582
|
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
1572
1583
|
return {
|
|
1573
1584
|
issueNumber,
|
|
@@ -1577,10 +1588,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1577
1588
|
loopTriggered: false,
|
|
1578
1589
|
};
|
|
1579
1590
|
}
|
|
1580
|
-
|
|
1581
|
-
? ` (${formatDuration(specResult.durationSeconds)})`
|
|
1582
|
-
: "";
|
|
1583
|
-
console.log(chalk.green(` ✓ spec${duration}`));
|
|
1591
|
+
specSpinner.succeed();
|
|
1584
1592
|
// Parse recommended workflow from spec output
|
|
1585
1593
|
const parsedWorkflow = specResult.output
|
|
1586
1594
|
? parseRecommendedWorkflow(specResult.output)
|
|
@@ -1634,8 +1642,22 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1634
1642
|
loopTriggered = true;
|
|
1635
1643
|
}
|
|
1636
1644
|
let phasesFailed = false;
|
|
1637
|
-
|
|
1638
|
-
|
|
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();
|
|
1639
1661
|
// Track phase start in state
|
|
1640
1662
|
if (stateManager) {
|
|
1641
1663
|
try {
|
|
@@ -1646,7 +1668,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1646
1668
|
}
|
|
1647
1669
|
}
|
|
1648
1670
|
const phaseStartTime = new Date();
|
|
1649
|
-
const result = await executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager);
|
|
1671
|
+
const result = await executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, phaseSpinner);
|
|
1650
1672
|
const phaseEndTime = new Date();
|
|
1651
1673
|
// Capture session ID for subsequent phases
|
|
1652
1674
|
if (result.sessionId) {
|
|
@@ -1690,29 +1712,34 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1690
1712
|
}
|
|
1691
1713
|
}
|
|
1692
1714
|
if (result.success) {
|
|
1693
|
-
|
|
1694
|
-
? ` (${formatDuration(result.durationSeconds)})`
|
|
1695
|
-
: "";
|
|
1696
|
-
console.log(chalk.green(` ✓ ${phase}${duration}`));
|
|
1715
|
+
phaseSpinner.succeed();
|
|
1697
1716
|
}
|
|
1698
1717
|
else {
|
|
1699
|
-
|
|
1718
|
+
phaseSpinner.fail(result.error);
|
|
1700
1719
|
phasesFailed = true;
|
|
1701
1720
|
// If quality loop enabled, run loop phase to fix issues
|
|
1702
1721
|
if (useQualityLoop && iteration < maxIterations) {
|
|
1703
|
-
|
|
1704
|
-
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);
|
|
1705
1732
|
phaseResults.push(loopResult);
|
|
1706
1733
|
if (loopResult.sessionId) {
|
|
1707
1734
|
sessionId = loopResult.sessionId;
|
|
1708
1735
|
}
|
|
1709
1736
|
if (loopResult.success) {
|
|
1710
|
-
|
|
1737
|
+
loopSpinner.succeed();
|
|
1711
1738
|
// Continue to next iteration
|
|
1712
1739
|
break;
|
|
1713
1740
|
}
|
|
1714
1741
|
else {
|
|
1715
|
-
|
|
1742
|
+
loopSpinner.fail(loopResult.error);
|
|
1716
1743
|
}
|
|
1717
1744
|
}
|
|
1718
1745
|
// Stop on first failure (if not in quality loop or loop failed)
|
|
@@ -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 {};
|
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
import chalk from "chalk";
|
|
8
|
+
import { getManifest, updateManifest, getPackageVersion, } from "../lib/manifest.js";
|
|
9
|
+
import { copyTemplates } from "../lib/templates.js";
|
|
10
|
+
import { getConfig } from "../lib/config.js";
|
|
11
|
+
import { writeFile, readFile, fileExists } from "../lib/fs.js";
|
|
12
|
+
const SKILLS_VERSION_PATH = ".claude/skills/.sequant-version";
|
|
13
|
+
/**
|
|
14
|
+
* Get the version of skills currently installed
|
|
15
|
+
*/
|
|
16
|
+
export async function getSkillsVersion() {
|
|
17
|
+
if (!(await fileExists(SKILLS_VERSION_PATH))) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const content = await readFile(SKILLS_VERSION_PATH);
|
|
22
|
+
return content.trim();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if skills are outdated compared to package version
|
|
30
|
+
*/
|
|
31
|
+
export async function areSkillsOutdated() {
|
|
32
|
+
const currentVersion = await getSkillsVersion();
|
|
33
|
+
const packageVersion = getPackageVersion();
|
|
34
|
+
return {
|
|
35
|
+
outdated: currentVersion !== packageVersion,
|
|
36
|
+
currentVersion,
|
|
37
|
+
packageVersion,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Update the skills version marker
|
|
42
|
+
*/
|
|
43
|
+
async function updateSkillsVersion() {
|
|
44
|
+
await writeFile(SKILLS_VERSION_PATH, getPackageVersion());
|
|
45
|
+
}
|
|
46
|
+
export async function syncCommand(options = {}) {
|
|
47
|
+
const { force = false, quiet = false } = options;
|
|
48
|
+
if (!quiet) {
|
|
49
|
+
console.log(chalk.blue("\n🔄 Syncing templates...\n"));
|
|
50
|
+
}
|
|
51
|
+
// Check if initialized
|
|
52
|
+
const manifest = await getManifest();
|
|
53
|
+
if (!manifest) {
|
|
54
|
+
console.log(chalk.red("❌ Sequant is not initialized. Run `sequant init` first."));
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const packageVersion = getPackageVersion();
|
|
59
|
+
const skillsVersion = await getSkillsVersion();
|
|
60
|
+
if (!quiet) {
|
|
61
|
+
console.log(chalk.gray(`Skills version: ${skillsVersion || "(unknown)"}`));
|
|
62
|
+
console.log(chalk.gray(`Package version: ${packageVersion}`));
|
|
63
|
+
console.log(chalk.gray(`Stack: ${manifest.stack}\n`));
|
|
64
|
+
}
|
|
65
|
+
// Check if sync is needed
|
|
66
|
+
if (!force && skillsVersion === packageVersion) {
|
|
67
|
+
if (!quiet) {
|
|
68
|
+
console.log(chalk.green("✅ Skills are already up to date!"));
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Get config tokens for template processing
|
|
73
|
+
const config = await getConfig();
|
|
74
|
+
const tokens = config?.tokens || {};
|
|
75
|
+
// Copy templates with force to overwrite existing files
|
|
76
|
+
const copyOptions = {
|
|
77
|
+
force: true, // Always overwrite when syncing
|
|
78
|
+
};
|
|
79
|
+
if (!quiet) {
|
|
80
|
+
console.log(chalk.blue("📥 Copying templates..."));
|
|
81
|
+
}
|
|
82
|
+
await copyTemplates(manifest.stack, tokens, copyOptions);
|
|
83
|
+
// Update version markers
|
|
84
|
+
await updateSkillsVersion();
|
|
85
|
+
await updateManifest();
|
|
86
|
+
if (!quiet) {
|
|
87
|
+
console.log(chalk.green(`\n✅ Synced to v${packageVersion}`));
|
|
88
|
+
console.log(chalk.gray("\nSkills, hooks, and memory files have been updated."));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check and warn if skills are outdated (for use by other commands)
|
|
93
|
+
*/
|
|
94
|
+
export async function checkAndWarnSkillsOutdated() {
|
|
95
|
+
const { outdated, currentVersion, packageVersion } = await areSkillsOutdated();
|
|
96
|
+
if (outdated) {
|
|
97
|
+
console.log(chalk.yellow(`\n⚠️ Skills are outdated (${currentVersion || "unknown"} → ${packageVersion})`));
|
|
98
|
+
console.log(chalk.yellow(" Run: npx sequant sync\n"));
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase Spinner - Animated spinner with elapsed time for phase execution
|
|
3
|
+
*
|
|
4
|
+
* Wraps the cli-ui spinner with:
|
|
5
|
+
* - Elapsed time tracking (updates every 5 seconds)
|
|
6
|
+
* - Phase progress indicator (e.g., "spec (1/3)")
|
|
7
|
+
* - Integration with ShutdownManager for graceful cleanup
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const spinner = new PhaseSpinner({
|
|
12
|
+
* phase: 'exec',
|
|
13
|
+
* phaseIndex: 2,
|
|
14
|
+
* totalPhases: 3,
|
|
15
|
+
* shutdownManager,
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* spinner.start();
|
|
19
|
+
* // ... phase execution
|
|
20
|
+
* spinner.succeed(); // Shows "✓ exec (2/3) (45s)"
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import type { ShutdownManager } from "./shutdown.js";
|
|
24
|
+
/**
|
|
25
|
+
* Options for creating a PhaseSpinner
|
|
26
|
+
*/
|
|
27
|
+
export interface PhaseSpinnerOptions {
|
|
28
|
+
/** Phase name (e.g., "spec", "exec", "qa") */
|
|
29
|
+
phase: string;
|
|
30
|
+
/** Current phase index (1-based) */
|
|
31
|
+
phaseIndex: number;
|
|
32
|
+
/** Total number of phases */
|
|
33
|
+
totalPhases: number;
|
|
34
|
+
/** Optional ShutdownManager for graceful cleanup */
|
|
35
|
+
shutdownManager?: ShutdownManager;
|
|
36
|
+
/** Optional prefix for indentation (default: " ") */
|
|
37
|
+
prefix?: string;
|
|
38
|
+
/** Optional quality loop iteration (shows "iteration N" if > 1) */
|
|
39
|
+
iteration?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Format elapsed time in human-readable format
|
|
43
|
+
*
|
|
44
|
+
* @param seconds - Elapsed time in seconds
|
|
45
|
+
* @returns Formatted string (e.g., "45s", "2m 15s", "1h 5m")
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatElapsedTime(seconds: number): string;
|
|
48
|
+
/**
|
|
49
|
+
* PhaseSpinner - Animated spinner with elapsed time and phase progress
|
|
50
|
+
*
|
|
51
|
+
* Features:
|
|
52
|
+
* - Animated spinner (or text fallback) via cli-ui
|
|
53
|
+
* - Elapsed time tracking with automatic updates
|
|
54
|
+
* - Phase progress indicator (e.g., "exec (2/3)")
|
|
55
|
+
* - ShutdownManager integration for graceful Ctrl+C cleanup
|
|
56
|
+
* - Pause/resume for verbose streaming output
|
|
57
|
+
*/
|
|
58
|
+
export declare class PhaseSpinner {
|
|
59
|
+
private spinner;
|
|
60
|
+
private startTime;
|
|
61
|
+
private intervalId;
|
|
62
|
+
private disposed;
|
|
63
|
+
private paused;
|
|
64
|
+
private readonly phase;
|
|
65
|
+
private readonly phaseIndex;
|
|
66
|
+
private readonly totalPhases;
|
|
67
|
+
private readonly shutdownManager?;
|
|
68
|
+
private readonly prefix;
|
|
69
|
+
private readonly iteration?;
|
|
70
|
+
private readonly cleanupName;
|
|
71
|
+
constructor(options: PhaseSpinnerOptions);
|
|
72
|
+
/**
|
|
73
|
+
* Format the spinner text with phase, progress, and elapsed time
|
|
74
|
+
*/
|
|
75
|
+
private formatText;
|
|
76
|
+
/**
|
|
77
|
+
* Update the spinner text with current elapsed time
|
|
78
|
+
*/
|
|
79
|
+
private updateElapsedTime;
|
|
80
|
+
/**
|
|
81
|
+
* Start the spinner
|
|
82
|
+
*
|
|
83
|
+
* Begins the animation and elapsed time tracking.
|
|
84
|
+
*/
|
|
85
|
+
start(): PhaseSpinner;
|
|
86
|
+
/**
|
|
87
|
+
* Mark the phase as succeeded
|
|
88
|
+
*
|
|
89
|
+
* Stops the spinner and shows a checkmark with total duration.
|
|
90
|
+
*/
|
|
91
|
+
succeed(customText?: string): PhaseSpinner;
|
|
92
|
+
/**
|
|
93
|
+
* Mark the phase as failed
|
|
94
|
+
*
|
|
95
|
+
* Stops the spinner and shows an error indicator with message.
|
|
96
|
+
*/
|
|
97
|
+
fail(error?: string): PhaseSpinner;
|
|
98
|
+
/**
|
|
99
|
+
* Stop the spinner without success/fail indication
|
|
100
|
+
*
|
|
101
|
+
* Use this for cleanup or when the phase is interrupted.
|
|
102
|
+
*/
|
|
103
|
+
stop(): PhaseSpinner;
|
|
104
|
+
/**
|
|
105
|
+
* Pause the spinner (for verbose streaming output)
|
|
106
|
+
*
|
|
107
|
+
* Temporarily stops the animation without disposing.
|
|
108
|
+
* Call resume() to continue.
|
|
109
|
+
*/
|
|
110
|
+
pause(): PhaseSpinner;
|
|
111
|
+
/**
|
|
112
|
+
* Resume the spinner after pause
|
|
113
|
+
*
|
|
114
|
+
* Restarts the animation with updated elapsed time.
|
|
115
|
+
*/
|
|
116
|
+
resume(): PhaseSpinner;
|
|
117
|
+
/**
|
|
118
|
+
* Check if the spinner is currently active
|
|
119
|
+
*/
|
|
120
|
+
get isSpinning(): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Get the total elapsed time in seconds
|
|
123
|
+
*/
|
|
124
|
+
get elapsedSeconds(): number;
|
|
125
|
+
/**
|
|
126
|
+
* Dispose of the spinner and clean up resources
|
|
127
|
+
*
|
|
128
|
+
* Called automatically by succeed(), fail(), or stop().
|
|
129
|
+
* Safe to call multiple times.
|
|
130
|
+
*/
|
|
131
|
+
dispose(): void;
|
|
132
|
+
/**
|
|
133
|
+
* Clear the elapsed time update interval
|
|
134
|
+
*/
|
|
135
|
+
private clearInterval;
|
|
136
|
+
/**
|
|
137
|
+
* Unregister from ShutdownManager
|
|
138
|
+
*/
|
|
139
|
+
private unregisterCleanup;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Create a PhaseSpinner with the given options
|
|
143
|
+
*
|
|
144
|
+
* Convenience function matching the pattern of cli-ui.spinner().
|
|
145
|
+
*/
|
|
146
|
+
export declare function phaseSpinner(options: PhaseSpinnerOptions): PhaseSpinner;
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase Spinner - Animated spinner with elapsed time for phase execution
|
|
3
|
+
*
|
|
4
|
+
* Wraps the cli-ui spinner with:
|
|
5
|
+
* - Elapsed time tracking (updates every 5 seconds)
|
|
6
|
+
* - Phase progress indicator (e.g., "spec (1/3)")
|
|
7
|
+
* - Integration with ShutdownManager for graceful cleanup
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const spinner = new PhaseSpinner({
|
|
12
|
+
* phase: 'exec',
|
|
13
|
+
* phaseIndex: 2,
|
|
14
|
+
* totalPhases: 3,
|
|
15
|
+
* shutdownManager,
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* spinner.start();
|
|
19
|
+
* // ... phase execution
|
|
20
|
+
* spinner.succeed(); // Shows "✓ exec (2/3) (45s)"
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { spinner as createSpinner } from "./cli-ui.js";
|
|
24
|
+
/**
|
|
25
|
+
* Elapsed time update interval in milliseconds
|
|
26
|
+
*/
|
|
27
|
+
const ELAPSED_UPDATE_INTERVAL_MS = 5000;
|
|
28
|
+
/**
|
|
29
|
+
* Format elapsed time in human-readable format
|
|
30
|
+
*
|
|
31
|
+
* @param seconds - Elapsed time in seconds
|
|
32
|
+
* @returns Formatted string (e.g., "45s", "2m 15s", "1h 5m")
|
|
33
|
+
*/
|
|
34
|
+
export function formatElapsedTime(seconds) {
|
|
35
|
+
if (seconds < 60) {
|
|
36
|
+
return `${Math.floor(seconds)}s`;
|
|
37
|
+
}
|
|
38
|
+
if (seconds < 3600) {
|
|
39
|
+
const minutes = Math.floor(seconds / 60);
|
|
40
|
+
const remainingSeconds = Math.floor(seconds % 60);
|
|
41
|
+
return remainingSeconds > 0
|
|
42
|
+
? `${minutes}m ${remainingSeconds}s`
|
|
43
|
+
: `${minutes}m`;
|
|
44
|
+
}
|
|
45
|
+
const hours = Math.floor(seconds / 3600);
|
|
46
|
+
const remainingMinutes = Math.floor((seconds % 3600) / 60);
|
|
47
|
+
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* PhaseSpinner - Animated spinner with elapsed time and phase progress
|
|
51
|
+
*
|
|
52
|
+
* Features:
|
|
53
|
+
* - Animated spinner (or text fallback) via cli-ui
|
|
54
|
+
* - Elapsed time tracking with automatic updates
|
|
55
|
+
* - Phase progress indicator (e.g., "exec (2/3)")
|
|
56
|
+
* - ShutdownManager integration for graceful Ctrl+C cleanup
|
|
57
|
+
* - Pause/resume for verbose streaming output
|
|
58
|
+
*/
|
|
59
|
+
export class PhaseSpinner {
|
|
60
|
+
spinner;
|
|
61
|
+
startTime = 0;
|
|
62
|
+
intervalId = null;
|
|
63
|
+
disposed = false;
|
|
64
|
+
paused = false;
|
|
65
|
+
phase;
|
|
66
|
+
phaseIndex;
|
|
67
|
+
totalPhases;
|
|
68
|
+
shutdownManager;
|
|
69
|
+
prefix;
|
|
70
|
+
iteration;
|
|
71
|
+
cleanupName;
|
|
72
|
+
constructor(options) {
|
|
73
|
+
this.phase = options.phase;
|
|
74
|
+
this.phaseIndex = options.phaseIndex;
|
|
75
|
+
this.totalPhases = options.totalPhases;
|
|
76
|
+
this.shutdownManager = options.shutdownManager;
|
|
77
|
+
this.prefix = options.prefix ?? " ";
|
|
78
|
+
this.iteration = options.iteration;
|
|
79
|
+
this.cleanupName = `phase-spinner-${options.phase}`;
|
|
80
|
+
// Create the underlying spinner with initial text
|
|
81
|
+
this.spinner = createSpinner(this.formatText(0));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Format the spinner text with phase, progress, and elapsed time
|
|
85
|
+
*/
|
|
86
|
+
formatText(elapsedSeconds) {
|
|
87
|
+
const progress = `(${this.phaseIndex}/${this.totalPhases})`;
|
|
88
|
+
const elapsed = elapsedSeconds > 0 ? ` ${formatElapsedTime(elapsedSeconds)}` : "";
|
|
89
|
+
const iterationSuffix = this.iteration && this.iteration > 1
|
|
90
|
+
? ` [iteration ${this.iteration}]`
|
|
91
|
+
: "";
|
|
92
|
+
return `${this.prefix}${this.phase} ${progress}...${elapsed}${iterationSuffix}`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Update the spinner text with current elapsed time
|
|
96
|
+
*/
|
|
97
|
+
updateElapsedTime() {
|
|
98
|
+
if (this.disposed || this.paused)
|
|
99
|
+
return;
|
|
100
|
+
const elapsedSeconds = (Date.now() - this.startTime) / 1000;
|
|
101
|
+
this.spinner.text = this.formatText(elapsedSeconds);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Start the spinner
|
|
105
|
+
*
|
|
106
|
+
* Begins the animation and elapsed time tracking.
|
|
107
|
+
*/
|
|
108
|
+
start() {
|
|
109
|
+
if (this.disposed)
|
|
110
|
+
return this;
|
|
111
|
+
this.startTime = Date.now();
|
|
112
|
+
this.spinner.start(this.formatText(0));
|
|
113
|
+
// Start elapsed time update interval
|
|
114
|
+
this.intervalId = setInterval(() => {
|
|
115
|
+
this.updateElapsedTime();
|
|
116
|
+
}, ELAPSED_UPDATE_INTERVAL_MS);
|
|
117
|
+
// Register with ShutdownManager for graceful cleanup
|
|
118
|
+
if (this.shutdownManager) {
|
|
119
|
+
this.shutdownManager.registerCleanup(this.cleanupName, async () => {
|
|
120
|
+
this.stop();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Mark the phase as succeeded
|
|
127
|
+
*
|
|
128
|
+
* Stops the spinner and shows a checkmark with total duration.
|
|
129
|
+
*/
|
|
130
|
+
succeed(customText) {
|
|
131
|
+
if (this.disposed)
|
|
132
|
+
return this;
|
|
133
|
+
this.clearInterval();
|
|
134
|
+
this.unregisterCleanup();
|
|
135
|
+
const elapsedSeconds = (Date.now() - this.startTime) / 1000;
|
|
136
|
+
const duration = formatElapsedTime(elapsedSeconds);
|
|
137
|
+
const progress = `(${this.phaseIndex}/${this.totalPhases})`;
|
|
138
|
+
const text = customText ?? `${this.prefix}${this.phase} ${progress} (${duration})`;
|
|
139
|
+
this.spinner.succeed(text);
|
|
140
|
+
this.disposed = true;
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Mark the phase as failed
|
|
145
|
+
*
|
|
146
|
+
* Stops the spinner and shows an error indicator with message.
|
|
147
|
+
*/
|
|
148
|
+
fail(error) {
|
|
149
|
+
if (this.disposed)
|
|
150
|
+
return this;
|
|
151
|
+
this.clearInterval();
|
|
152
|
+
this.unregisterCleanup();
|
|
153
|
+
const elapsedSeconds = (Date.now() - this.startTime) / 1000;
|
|
154
|
+
const duration = formatElapsedTime(elapsedSeconds);
|
|
155
|
+
const progress = `(${this.phaseIndex}/${this.totalPhases})`;
|
|
156
|
+
const errorSuffix = error ? `: ${error}` : "";
|
|
157
|
+
const text = `${this.prefix}${this.phase} ${progress} (${duration})${errorSuffix}`;
|
|
158
|
+
this.spinner.fail(text);
|
|
159
|
+
this.disposed = true;
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Stop the spinner without success/fail indication
|
|
164
|
+
*
|
|
165
|
+
* Use this for cleanup or when the phase is interrupted.
|
|
166
|
+
*/
|
|
167
|
+
stop() {
|
|
168
|
+
if (this.disposed)
|
|
169
|
+
return this;
|
|
170
|
+
this.clearInterval();
|
|
171
|
+
this.unregisterCleanup();
|
|
172
|
+
this.spinner.stop();
|
|
173
|
+
this.disposed = true;
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Pause the spinner (for verbose streaming output)
|
|
178
|
+
*
|
|
179
|
+
* Temporarily stops the animation without disposing.
|
|
180
|
+
* Call resume() to continue.
|
|
181
|
+
*/
|
|
182
|
+
pause() {
|
|
183
|
+
if (this.disposed || this.paused)
|
|
184
|
+
return this;
|
|
185
|
+
this.paused = true;
|
|
186
|
+
this.spinner.stop();
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Resume the spinner after pause
|
|
191
|
+
*
|
|
192
|
+
* Restarts the animation with updated elapsed time.
|
|
193
|
+
*/
|
|
194
|
+
resume() {
|
|
195
|
+
if (this.disposed || !this.paused)
|
|
196
|
+
return this;
|
|
197
|
+
this.paused = false;
|
|
198
|
+
const elapsedSeconds = (Date.now() - this.startTime) / 1000;
|
|
199
|
+
this.spinner.start(this.formatText(elapsedSeconds));
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Check if the spinner is currently active
|
|
204
|
+
*/
|
|
205
|
+
get isSpinning() {
|
|
206
|
+
return this.spinner.isSpinning && !this.disposed && !this.paused;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get the total elapsed time in seconds
|
|
210
|
+
*/
|
|
211
|
+
get elapsedSeconds() {
|
|
212
|
+
if (this.startTime === 0)
|
|
213
|
+
return 0;
|
|
214
|
+
return (Date.now() - this.startTime) / 1000;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Dispose of the spinner and clean up resources
|
|
218
|
+
*
|
|
219
|
+
* Called automatically by succeed(), fail(), or stop().
|
|
220
|
+
* Safe to call multiple times.
|
|
221
|
+
*/
|
|
222
|
+
dispose() {
|
|
223
|
+
if (this.disposed)
|
|
224
|
+
return;
|
|
225
|
+
this.clearInterval();
|
|
226
|
+
this.unregisterCleanup();
|
|
227
|
+
this.spinner.stop();
|
|
228
|
+
this.disposed = true;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Clear the elapsed time update interval
|
|
232
|
+
*/
|
|
233
|
+
clearInterval() {
|
|
234
|
+
if (this.intervalId !== null) {
|
|
235
|
+
clearInterval(this.intervalId);
|
|
236
|
+
this.intervalId = null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Unregister from ShutdownManager
|
|
241
|
+
*/
|
|
242
|
+
unregisterCleanup() {
|
|
243
|
+
if (this.shutdownManager) {
|
|
244
|
+
this.shutdownManager.unregisterCleanup(this.cleanupName);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Create a PhaseSpinner with the given options
|
|
250
|
+
*
|
|
251
|
+
* Convenience function matching the pattern of cli-ui.spinner().
|
|
252
|
+
*/
|
|
253
|
+
export function phaseSpinner(options) {
|
|
254
|
+
return new PhaseSpinner(options);
|
|
255
|
+
}
|
|
@@ -5,6 +5,8 @@ import { readdir, chmod } from "fs/promises";
|
|
|
5
5
|
import { join, dirname, relative, isAbsolute } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { readFile, writeFile, ensureDir, fileExists, isSymlink, createSymlink, removeFileOrSymlink, } from "./fs.js";
|
|
8
|
+
import { getPackageVersion } from "./manifest.js";
|
|
9
|
+
const SKILLS_VERSION_PATH = ".claude/skills/.sequant-version";
|
|
8
10
|
import { getStackConfig, getStackNotes, getMultiStackNotes } from "./stacks.js";
|
|
9
11
|
import { isNativeWindows } from "./system.js";
|
|
10
12
|
import { getProjectName } from "./project-name.js";
|
|
@@ -227,5 +229,7 @@ export async function copyTemplates(stack, tokens, options = {}) {
|
|
|
227
229
|
const content = await readFile(settingsPath);
|
|
228
230
|
await writeFile(".claude/settings.json", processTemplate(content, variables));
|
|
229
231
|
}
|
|
232
|
+
// Write skills version marker for sync detection
|
|
233
|
+
await writeFile(SKILLS_VERSION_PATH, getPackageVersion());
|
|
230
234
|
return { scriptsSymlinked, symlinkResults };
|
|
231
235
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sequant",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.2",
|
|
4
4
|
"description": "Quantize your development workflow - Sequential AI phases with quality gates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
19
|
"templates",
|
|
20
|
-
"stacks"
|
|
20
|
+
"stacks",
|
|
21
|
+
".claude-plugin"
|
|
21
22
|
],
|
|
22
23
|
"scripts": {
|
|
23
24
|
"build": "tsc",
|
|
@@ -50,71 +50,299 @@ gh issue view <issue-number> --json labels --jq '.labels[].name'
|
|
|
50
50
|
- Recommend `--quality-loop` flag for auto-retry on failures
|
|
51
51
|
- Quality loop auto-enables for these labels in `sequant run`
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
**New Features** (labels: `enhancement`, `feature`):
|
|
54
|
+
- Include `testgen` phase when ACs need automated tests
|
|
55
|
+
- Workflow: `spec → testgen → exec → qa`
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
### Quality Loop Detection
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
- Issue number
|
|
59
|
-
- Title
|
|
60
|
-
- Labels
|
|
61
|
-
- Recommended workflow
|
|
59
|
+
Quality loop (`--quality-loop` or `-q`) provides automatic fix iterations when phases fail. **Recommend quality loop broadly** for any non-trivial work.
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
**Always recommend `--quality-loop` when:**
|
|
62
|
+
- Labels include: `complex`, `refactor`, `breaking`, `major` (auto-enabled)
|
|
63
|
+
- Labels include: `enhancement`, `feature` (new functionality)
|
|
64
|
+
- Issue involves multiple files or components
|
|
65
|
+
- Issue title contains: "add", "implement", "create", "refactor", "update"
|
|
66
|
+
- Issue is NOT a simple bug fix with `bug` or `fix` label only
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
**Skip quality loop recommendation only when:**
|
|
69
|
+
- Simple bug fix (only `bug` or `fix` label, no other labels)
|
|
70
|
+
- Documentation-only changes (`docs` label only)
|
|
71
|
+
- Issue explicitly marked as trivial
|
|
66
72
|
|
|
67
|
-
|
|
73
|
+
**Quality loop benefits:**
|
|
74
|
+
- Auto-retries failed phases up to 3 times
|
|
75
|
+
- Catches intermittent test failures
|
|
76
|
+
- Handles build issues from dependency changes
|
|
77
|
+
- Reduces manual intervention for recoverable errors
|
|
68
78
|
|
|
69
|
-
###
|
|
79
|
+
### Feature Branch Detection
|
|
70
80
|
|
|
71
|
-
|
|
72
|
-
## Solve Workflow for Issues: 152, 153
|
|
81
|
+
When analyzing issues, check if `--base` flag should be recommended.
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
**Check for feature branch indicators:**
|
|
75
84
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
| #153 | Refactor auth module | backend, refactor | Standard + quality loop |
|
|
85
|
+
```bash
|
|
86
|
+
# Check for feature branch references in issue body
|
|
87
|
+
gh issue view <issue-number> --json body --jq '.body' | grep -iE "(feature/|branch from|based on|part of.*feature)"
|
|
80
88
|
|
|
81
|
-
|
|
89
|
+
# Check issue labels for feature context
|
|
90
|
+
gh issue view <issue-number> --json labels --jq '.labels[].name' | grep -iE "(dashboard|feature-|epic-)"
|
|
82
91
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
/spec 152 # Plan the implementation
|
|
86
|
-
/exec 152 # Implement the feature
|
|
87
|
-
/test 152 # Browser-based UI testing
|
|
88
|
-
/qa 152 # Quality review
|
|
92
|
+
# Check if project has defaultBase configured
|
|
93
|
+
cat .sequant/settings.json 2>/dev/null | jq -r '.run.defaultBase // empty'
|
|
89
94
|
```
|
|
90
95
|
|
|
91
|
-
**
|
|
96
|
+
**Recommend `--base <branch>` when:**
|
|
97
|
+
- Issue body references a feature branch (e.g., "Part of dashboard feature")
|
|
98
|
+
- Issue is labeled with a feature epic label (e.g., `dashboard`, `epic-auth`)
|
|
99
|
+
- Multiple related issues reference the same parent feature
|
|
100
|
+
- Project has `run.defaultBase` configured in settings
|
|
101
|
+
|
|
102
|
+
**Do NOT recommend `--base` when:**
|
|
103
|
+
- Issue should branch from main (default, most common)
|
|
104
|
+
- No feature branch context detected
|
|
105
|
+
- Issue is a standalone bug fix or independent feature
|
|
106
|
+
|
|
107
|
+
### Chain Mode Detection
|
|
108
|
+
|
|
109
|
+
When analyzing multiple issues, determine if `--chain` flag should be recommended.
|
|
110
|
+
|
|
111
|
+
**Check for chain indicators:**
|
|
112
|
+
|
|
92
113
|
```bash
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
114
|
+
# Check for dependency keywords in issue body
|
|
115
|
+
gh issue view <issue-number> --json body --jq '.body' | grep -iE "(depends on|blocked by|requires|after #|builds on)"
|
|
116
|
+
|
|
117
|
+
# Check for sequence labels
|
|
118
|
+
gh issue view <issue-number> --json labels --jq '.labels[].name' | grep -iE "(part-[0-9]|step-[0-9]|phase-[0-9])"
|
|
119
|
+
|
|
120
|
+
# Check for related issue references
|
|
121
|
+
gh issue view <issue-number> --json body --jq '.body' | grep -oE "#[0-9]+"
|
|
96
122
|
```
|
|
97
123
|
|
|
98
|
-
|
|
124
|
+
**Recommend `--chain` when:**
|
|
125
|
+
- Multiple issues have explicit dependencies (e.g., "depends on #123")
|
|
126
|
+
- Issues are labeled as parts of a sequence (e.g., `part-1`, `part-2`)
|
|
127
|
+
- Issue titles indicate sequence (e.g., "Part 1:", "Step 2:")
|
|
128
|
+
- Issues reference each other in their bodies
|
|
129
|
+
- Issues modify the same files in a specific order
|
|
99
130
|
|
|
100
|
-
|
|
131
|
+
**Do NOT recommend `--chain` when:**
|
|
132
|
+
- Single issue (chain requires 2+ issues)
|
|
133
|
+
- Issues are independent (no shared files or dependencies)
|
|
134
|
+
- Issues touch completely different areas of codebase
|
|
135
|
+
- Parallel batch mode is more appropriate (unrelated issues)
|
|
101
136
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
137
|
+
### QA Gate Detection
|
|
138
|
+
|
|
139
|
+
When recommending `--chain`, also consider if `--qa-gate` should be added.
|
|
140
|
+
|
|
141
|
+
**Recommend `--qa-gate` when:**
|
|
142
|
+
- Chain has 3+ issues (longer chains have higher stale code risk)
|
|
143
|
+
- Issues have tight dependencies (later issues heavily rely on earlier ones)
|
|
144
|
+
- Issues modify the same files across the chain
|
|
145
|
+
- Production-critical or high-risk changes
|
|
146
|
+
|
|
147
|
+
**Do NOT recommend `--qa-gate` when:**
|
|
148
|
+
- Chain has only 2 issues (lower risk)
|
|
149
|
+
- Issues are mostly independent despite chain structure
|
|
150
|
+
- Speed is prioritized over safety
|
|
151
|
+
- Simple, low-risk changes
|
|
152
|
+
|
|
153
|
+
**Chain structure visualization:**
|
|
105
154
|
```
|
|
155
|
+
origin/main → #10 → #11 → #12
|
|
156
|
+
│ │ │
|
|
157
|
+
└──────┴──────┴── Each branch builds on previous
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Output Format
|
|
161
|
+
|
|
162
|
+
**Design Principles:**
|
|
163
|
+
- Lead with the recommendation (command first, top-down)
|
|
164
|
+
- Show all flag decisions explicitly with reasoning
|
|
165
|
+
- Be concise — signal over prose
|
|
166
|
+
- Visual hierarchy using ASCII boxes and lines
|
|
167
|
+
- Max ~25 lines (excluding conflict warnings)
|
|
168
|
+
|
|
169
|
+
**Required Sections (in order):**
|
|
170
|
+
|
|
171
|
+
1. **Header Box** — Command recommendation prominently displayed
|
|
172
|
+
2. **Issues List** — Compact: `#N Title ··· labels → workflow`
|
|
173
|
+
3. **Flags Table** — ALL flags with ✓/✗ and one-line reasoning
|
|
174
|
+
4. **Why Section** — 3-5 bullet points explaining key decisions
|
|
175
|
+
5. **Also Consider** — Conditional curated alternatives (0-3 items)
|
|
176
|
+
6. **Conflict Warning** — Only if in-flight work overlaps (conditional)
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Conflict Detection
|
|
181
|
+
|
|
182
|
+
Before generating output, check for in-flight work that may conflict:
|
|
106
183
|
|
|
107
|
-
For issue #153 (or any complex work), quality loop is recommended:
|
|
108
184
|
```bash
|
|
109
|
-
|
|
185
|
+
# List open worktrees
|
|
186
|
+
git worktree list --porcelain 2>/dev/null | grep "^worktree" | cut -d' ' -f2
|
|
187
|
+
|
|
188
|
+
# For each worktree, get changed files
|
|
189
|
+
git -C <worktree-path> diff --name-only main...HEAD 2>/dev/null
|
|
110
190
|
```
|
|
111
191
|
|
|
112
|
-
|
|
192
|
+
**If overlap detected** with files this issue likely touches, include warning:
|
|
193
|
+
```
|
|
194
|
+
⚠ Conflict risk: #45 (open) modifies lib/auth/* — coordinate or wait
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## "Also Consider" Logic
|
|
200
|
+
|
|
201
|
+
Only show alternatives representing genuine trade-offs. Max 2-3 items.
|
|
113
202
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
-
|
|
203
|
+
| Condition | Show Alternative |
|
|
204
|
+
|-----------|------------------|
|
|
205
|
+
| Complex issues OR user unfamiliar with sequant | `--dry-run` (preview before executing) |
|
|
206
|
+
| UI-adjacent AND test phase not included | `--phases +test` (add browser testing) |
|
|
207
|
+
| Mild dependency risk between issues | `--sequential` (run one at a time) |
|
|
208
|
+
| Dependencies ambiguous | Show both parallel and `--chain` options |
|
|
209
|
+
|
|
210
|
+
**Rules:**
|
|
211
|
+
- Omit section entirely if nothing worth showing
|
|
212
|
+
- Never list every flag — only curated, contextual options
|
|
213
|
+
- Each alternative needs one-line explanation
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Output Template
|
|
218
|
+
|
|
219
|
+
You MUST use this exact structure:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
╭──────────────────────────────────────────────────────────────╮
|
|
223
|
+
│ sequant solve │
|
|
224
|
+
│ │
|
|
225
|
+
│ npx sequant run <ISSUES> <FLAGS> │
|
|
226
|
+
╰──────────────────────────────────────────────────────────────╯
|
|
227
|
+
|
|
228
|
+
#<N> <Title truncated to ~35 chars> ·········· <labels> → <workflow>
|
|
229
|
+
#<N> <Title truncated to ~35 chars> ·········· <labels> → <workflow>
|
|
230
|
+
|
|
231
|
+
┌─ Flags ──────────────────────────────────────────────────────┐
|
|
232
|
+
│ -q quality-loop ✓/✗ <one-line reasoning> │
|
|
233
|
+
│ --chain ✓/✗ <one-line reasoning> │
|
|
234
|
+
│ --qa-gate ✓/✗ <one-line reasoning> │
|
|
235
|
+
│ --base ✓/✗ <one-line reasoning> │
|
|
236
|
+
│ --testgen ✓/✗ <one-line reasoning> │
|
|
237
|
+
└──────────────────────────────────────────────────────────────┘
|
|
238
|
+
|
|
239
|
+
Why this workflow:
|
|
240
|
+
• <reason 1>
|
|
241
|
+
• <reason 2>
|
|
242
|
+
• <reason 3>
|
|
243
|
+
|
|
244
|
+
<!-- CONDITIONAL: Only if alternatives worth showing -->
|
|
245
|
+
Also consider:
|
|
246
|
+
<flag> <one-line explanation>
|
|
247
|
+
<flag> <one-line explanation>
|
|
248
|
+
|
|
249
|
+
<!-- CONDITIONAL: Only if conflict detected -->
|
|
250
|
+
⚠ Conflict risk: #<N> (open) modifies <path> — coordinate or wait
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### Example Output (Independent Issues)
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
╭──────────────────────────────────────────────────────────────╮
|
|
259
|
+
│ sequant solve │
|
|
260
|
+
│ │
|
|
261
|
+
│ npx sequant run 152 153 -q │
|
|
262
|
+
╰──────────────────────────────────────────────────────────────╯
|
|
263
|
+
|
|
264
|
+
#152 Add user dashboard ······················ ui → spec → testgen → exec → test → qa
|
|
265
|
+
#153 Refactor auth module ···················· backend → spec → exec → qa
|
|
266
|
+
|
|
267
|
+
┌─ Flags ──────────────────────────────────────────────────────┐
|
|
268
|
+
│ -q quality-loop ✓ refactor label auto-enables retry │
|
|
269
|
+
│ --chain ✗ independent (different codepaths) │
|
|
270
|
+
│ --qa-gate ✗ no chain mode │
|
|
271
|
+
│ --base ✗ branching from main │
|
|
272
|
+
│ --testgen ✓ #152 has testable ACs (Unit Tests) │
|
|
273
|
+
└──────────────────────────────────────────────────────────────┘
|
|
274
|
+
|
|
275
|
+
Why this workflow:
|
|
276
|
+
• #152 has ui label → includes /test for browser verification
|
|
277
|
+
• #152 has testable ACs → includes /testgen for test stubs
|
|
278
|
+
• #153 has refactor label → quality loop auto-enabled
|
|
279
|
+
• No shared files → safe to parallelize
|
|
280
|
+
|
|
281
|
+
Also consider:
|
|
282
|
+
--dry-run Preview execution before running
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### Example Output (Dependent Issues — Chain)
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
╭──────────────────────────────────────────────────────────────╮
|
|
291
|
+
│ sequant solve │
|
|
292
|
+
│ │
|
|
293
|
+
│ npx sequant run 10 11 12 --sequential --chain --qa-gate -q │
|
|
294
|
+
╰──────────────────────────────────────────────────────────────╯
|
|
295
|
+
|
|
296
|
+
#10 Add auth middleware ······················ backend → spec → testgen → exec → qa
|
|
297
|
+
#11 Add login page ··························· ui → spec → testgen → exec → test → qa
|
|
298
|
+
#12 Add logout functionality ················· ui → spec → exec → test → qa
|
|
299
|
+
|
|
300
|
+
┌─ Flags ──────────────────────────────────────────────────────┐
|
|
301
|
+
│ -q quality-loop ✓ multi-step implementation │
|
|
302
|
+
│ --chain ✓ #11 depends on #10, #12 depends on #11│
|
|
303
|
+
│ --qa-gate ✓ 3 issues with tight dependencies │
|
|
304
|
+
│ --base ✗ branching from main │
|
|
305
|
+
│ --testgen ✓ #10, #11 have Unit Test ACs │
|
|
306
|
+
└──────────────────────────────────────────────────────────────┘
|
|
307
|
+
|
|
308
|
+
Chain structure:
|
|
309
|
+
main → #10 → #11 → #12
|
|
310
|
+
|
|
311
|
+
Why this workflow:
|
|
312
|
+
• Explicit dependencies detected in issue bodies
|
|
313
|
+
• Chain ensures each branch builds on previous
|
|
314
|
+
• QA gate prevents stale code in downstream issues
|
|
315
|
+
• UI issues (#11, #12) include /test phase
|
|
316
|
+
• #10, #11 have testable ACs → include /testgen
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
### Example Output (With Conflict Warning)
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
╭──────────────────────────────────────────────────────────────╮
|
|
325
|
+
│ sequant solve │
|
|
326
|
+
│ │
|
|
327
|
+
│ npx sequant run 85 -q │
|
|
328
|
+
╰──────────────────────────────────────────────────────────────╯
|
|
329
|
+
|
|
330
|
+
#85 Update auth cookie handling ·············· bug → exec → qa
|
|
331
|
+
|
|
332
|
+
┌─ Flags ──────────────────────────────────────────────────────┐
|
|
333
|
+
│ -q quality-loop ✓ auth changes benefit from retry │
|
|
334
|
+
│ --chain ✗ single issue │
|
|
335
|
+
│ --qa-gate ✗ no chain mode │
|
|
336
|
+
│ --base ✗ branching from main │
|
|
337
|
+
│ --testgen ✗ bug fix (targeted tests in exec) │
|
|
338
|
+
└──────────────────────────────────────────────────────────────┘
|
|
339
|
+
|
|
340
|
+
Why this workflow:
|
|
341
|
+
• Bug fix with clear AC → skip /spec
|
|
342
|
+
• Bug fix → skip /testgen (targeted tests added during exec)
|
|
343
|
+
• Single issue → no chain needed
|
|
344
|
+
|
|
345
|
+
⚠ Conflict risk: #82 (open) modifies lib/auth/cookies.ts — coordinate or wait
|
|
118
346
|
```
|
|
119
347
|
|
|
120
348
|
## Workflow Selection Logic
|
|
@@ -128,17 +356,30 @@ npx sequant run 153 --quality-loop # Explicit (auto-enabled for refactor label
|
|
|
128
356
|
- UI/frontend changes → Add `test` phase
|
|
129
357
|
- Complex refactors → Enable quality loop
|
|
130
358
|
- Security-sensitive → Add `security-review` phase
|
|
359
|
+
- New features with testable ACs → Add `testgen` phase
|
|
131
360
|
|
|
132
361
|
### Standard Workflow (Most Issues)
|
|
133
362
|
```
|
|
134
363
|
/spec → /exec → /qa
|
|
135
364
|
```
|
|
136
365
|
|
|
366
|
+
### Feature with Testable ACs
|
|
367
|
+
```
|
|
368
|
+
/spec → /testgen → /exec → /qa
|
|
369
|
+
```
|
|
370
|
+
Include `testgen` when ACs have Unit Test or Integration Test verification methods.
|
|
371
|
+
|
|
137
372
|
### UI Feature Workflow
|
|
138
373
|
```
|
|
139
374
|
/spec → /exec → /test → /qa
|
|
140
375
|
```
|
|
141
376
|
|
|
377
|
+
### UI Feature with Tests
|
|
378
|
+
```
|
|
379
|
+
/spec → /testgen → /exec → /test → /qa
|
|
380
|
+
```
|
|
381
|
+
Combine `testgen` and `test` for UI features with testable ACs.
|
|
382
|
+
|
|
142
383
|
### Bug Fix Workflow (Simple)
|
|
143
384
|
```
|
|
144
385
|
/exec → /qa
|
|
@@ -156,11 +397,25 @@ Runs complete workflow with automatic fix iterations.
|
|
|
156
397
|
| Issue Type | Labels | Workflow |
|
|
157
398
|
|------------|--------|----------|
|
|
158
399
|
| UI Feature | ui, frontend, admin | spec → exec → test → qa |
|
|
400
|
+
| UI Feature with Tests | ui + enhancement | spec → testgen → exec → test → qa |
|
|
159
401
|
| Backend Feature | backend, api | spec → exec → qa |
|
|
402
|
+
| New Feature (testable) | enhancement, feature | spec → testgen → exec → qa |
|
|
160
403
|
| Bug Fix | bug, fix | exec → qa (or full if complex) |
|
|
161
404
|
| Complex Feature | complex, refactor | `--quality-loop` or fullsolve |
|
|
162
405
|
| Documentation | docs | exec → qa |
|
|
163
406
|
|
|
407
|
+
### Testgen Phase Detection
|
|
408
|
+
|
|
409
|
+
**Include `testgen` in workflow when:**
|
|
410
|
+
- Issue has `enhancement` or `feature` label AND
|
|
411
|
+
- Issue is NOT a simple bug fix or docs-only change AND
|
|
412
|
+
- Project has test infrastructure (Jest, Vitest, etc.)
|
|
413
|
+
|
|
414
|
+
**Skip `testgen` when:**
|
|
415
|
+
- Issue is `bug` or `fix` only (targeted tests added during exec)
|
|
416
|
+
- Issue is `docs` only (no code tests needed)
|
|
417
|
+
- All ACs use Manual Test or Browser Test verification
|
|
418
|
+
|
|
164
419
|
**Quality Loop vs Fullsolve:**
|
|
165
420
|
- `--quality-loop`: Enables auto-retry within `sequant run` (good for CI/automation)
|
|
166
421
|
- `/fullsolve`: Interactive single-issue resolution with inline loops (good for manual work)
|
|
@@ -179,6 +434,18 @@ npx sequant run 152 153 154
|
|
|
179
434
|
# Sequential execution (respects dependencies)
|
|
180
435
|
npx sequant run 152 153 --sequential
|
|
181
436
|
|
|
437
|
+
# Chain mode (each issue branches from previous completed issue)
|
|
438
|
+
npx sequant run 10 11 12 --sequential --chain
|
|
439
|
+
|
|
440
|
+
# Chain mode with QA gate (pause if QA fails, prevent stale code)
|
|
441
|
+
npx sequant run 10 11 12 --sequential --chain --qa-gate
|
|
442
|
+
|
|
443
|
+
# Custom base branch (branch from feature branch instead of main)
|
|
444
|
+
npx sequant run 117 --base feature/dashboard
|
|
445
|
+
|
|
446
|
+
# Chain mode with custom base branch
|
|
447
|
+
npx sequant run 117 118 119 --sequential --chain --base feature/dashboard
|
|
448
|
+
|
|
182
449
|
# Custom phases
|
|
183
450
|
npx sequant run 152 --phases spec,exec,qa
|
|
184
451
|
|
|
@@ -192,6 +459,50 @@ npx sequant run 152 --quality-loop --max-iterations 5
|
|
|
192
459
|
npx sequant run 152 --dry-run
|
|
193
460
|
```
|
|
194
461
|
|
|
462
|
+
### Custom Base Branch
|
|
463
|
+
|
|
464
|
+
The `--base` flag specifies which branch to create worktrees from:
|
|
465
|
+
|
|
466
|
+
```
|
|
467
|
+
Without --base: With --base feature/dashboard:
|
|
468
|
+
origin/main feature/dashboard
|
|
469
|
+
├── #117 ├── #117
|
|
470
|
+
├── #118 ├── #118
|
|
471
|
+
└── #119 └── #119
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Use `--base` when:**
|
|
475
|
+
- Working on issues for a feature integration branch
|
|
476
|
+
- Issue references a parent branch (e.g., "Part of dashboard feature")
|
|
477
|
+
- Project uses `.sequant/settings.json` with `run.defaultBase` configured
|
|
478
|
+
- Issues should build on an existing feature branch
|
|
479
|
+
|
|
480
|
+
**Do NOT use `--base` when:**
|
|
481
|
+
- Issues should branch from main (default behavior)
|
|
482
|
+
- Working on independent bug fixes or features
|
|
483
|
+
|
|
484
|
+
### Chain Mode Explained
|
|
485
|
+
|
|
486
|
+
The `--chain` flag (requires `--sequential`) creates dependent branches:
|
|
487
|
+
|
|
488
|
+
```
|
|
489
|
+
Without --chain: With --chain:
|
|
490
|
+
origin/main origin/main
|
|
491
|
+
├── #10 └── #10 (merged)
|
|
492
|
+
├── #11 └── #11 (merged)
|
|
493
|
+
└── #12 └── #12
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
**Use `--chain` when:**
|
|
497
|
+
- Issues have explicit dependencies
|
|
498
|
+
- Later issues build on earlier implementations
|
|
499
|
+
- Order matters for correctness
|
|
500
|
+
|
|
501
|
+
**Do NOT use `--chain` when:**
|
|
502
|
+
- Issues are independent
|
|
503
|
+
- Parallel execution is appropriate
|
|
504
|
+
- Issues can be merged in any order
|
|
505
|
+
|
|
195
506
|
> **Tip:** Install globally with `npm install -g sequant` to omit the `npx` prefix.
|
|
196
507
|
|
|
197
508
|
## Edge Cases
|
|
@@ -224,56 +535,37 @@ If issues depend on each other:
|
|
|
224
535
|
|
|
225
536
|
---
|
|
226
537
|
|
|
227
|
-
##
|
|
228
|
-
|
|
229
|
-
**Before responding, verify your output includes ALL of these:**
|
|
230
|
-
|
|
231
|
-
- [ ] **Issue Summary Table** - Table with Issue, Title, Labels, Workflow columns
|
|
232
|
-
- [ ] **Recommended Workflow** - Slash commands in order for each issue
|
|
233
|
-
- [ ] **CLI Command** - `npx sequant run <issue-numbers>` command (REQUIRED)
|
|
234
|
-
- [ ] **Explanation** - Brief notes explaining workflow choices
|
|
235
|
-
|
|
236
|
-
**DO NOT respond until all items are verified.**
|
|
237
|
-
|
|
238
|
-
## Output Template
|
|
538
|
+
## State Tracking
|
|
239
539
|
|
|
240
|
-
|
|
540
|
+
**IMPORTANT:** `/solve` initializes issue state when analyzing issues.
|
|
241
541
|
|
|
242
|
-
|
|
243
|
-
## Solve Workflow for Issues: <ISSUE_NUMBERS>
|
|
542
|
+
### State Updates
|
|
244
543
|
|
|
245
|
-
|
|
544
|
+
When analyzing issues, initialize state tracking so the dashboard can show planned work:
|
|
246
545
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
### Recommended Workflow
|
|
546
|
+
**Initialize each issue being analyzed:**
|
|
547
|
+
```bash
|
|
548
|
+
# Get issue title
|
|
549
|
+
TITLE=$(gh issue view <issue-number> --json title -q '.title')
|
|
252
550
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
\`\`\`
|
|
551
|
+
# Initialize state (if not already tracked)
|
|
552
|
+
npx tsx scripts/state/update.ts init <issue-number> "$TITLE"
|
|
553
|
+
```
|
|
257
554
|
|
|
258
|
-
|
|
259
|
-
> **Note:** Issue #<N> has `<label>` label. Quality loop will **auto-enable** when using `sequant run`, providing automatic fix iterations if phases fail.
|
|
555
|
+
**Note:** `/solve` only initializes issues - actual phase tracking happens during workflow execution (`/fullsolve`, `sequant run`, or individual skills).
|
|
260
556
|
|
|
261
|
-
|
|
557
|
+
---
|
|
262
558
|
|
|
263
|
-
|
|
264
|
-
\`\`\`bash
|
|
265
|
-
npx sequant run <ISSUE_NUMBERS>
|
|
266
|
-
\`\`\`
|
|
559
|
+
## Output Verification
|
|
267
560
|
|
|
268
|
-
|
|
269
|
-
For complex issues, quality loop is recommended:
|
|
270
|
-
\`\`\`bash
|
|
271
|
-
npx sequant run <ISSUE_NUMBER> --quality-loop # Explicit (auto-enabled for <label> label)
|
|
272
|
-
\`\`\`
|
|
561
|
+
**Before responding, verify your output includes ALL of these:**
|
|
273
562
|
|
|
274
|
-
|
|
563
|
+
- [ ] **Header Box** — ASCII box with `sequant solve` and full command
|
|
564
|
+
- [ ] **Issues List** — Each issue with dot leaders: `#N Title ··· labels → workflow`
|
|
565
|
+
- [ ] **Flags Table** — ALL five flags (-q, --chain, --qa-gate, --base, --testgen) with ✓/✗ and reasoning
|
|
566
|
+
- [ ] **Why Section** — 3-5 bullet points explaining decisions
|
|
567
|
+
- [ ] **Also Consider** — (conditional) Curated alternatives if applicable
|
|
568
|
+
- [ ] **Conflict Warning** — (conditional) If in-flight work overlaps
|
|
569
|
+
- [ ] **Line Count** — Total ≤25 lines (excluding conflict warnings)
|
|
275
570
|
|
|
276
|
-
|
|
277
|
-
<!-- FILL: explanation of workflow choices -->
|
|
278
|
-
<!-- Include note about quality loop auto-enabling if applicable -->
|
|
279
|
-
```
|
|
571
|
+
**DO NOT respond until all items are verified.**
|