sequant 1.1.1 → 1.1.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/dist/bin/cli.js +9 -3
- package/dist/bin/cli.js.map +1 -1
- package/dist/src/commands/run.d.ts +8 -1
- package/dist/src/commands/run.d.ts.map +1 -1
- package/dist/src/commands/run.js +348 -102
- package/dist/src/commands/run.js.map +1 -1
- package/dist/src/commands/update.d.ts.map +1 -1
- package/dist/src/commands/update.js +16 -0
- package/dist/src/commands/update.js.map +1 -1
- package/dist/src/lib/workflow/log-writer.test.d.ts +7 -0
- package/dist/src/lib/workflow/log-writer.test.d.ts.map +1 -0
- package/dist/src/lib/workflow/log-writer.test.js +451 -0
- package/dist/src/lib/workflow/log-writer.test.js.map +1 -0
- package/dist/src/lib/workflow/run-log-schema.test.d.ts +2 -0
- package/dist/src/lib/workflow/run-log-schema.test.d.ts.map +1 -0
- package/dist/src/lib/workflow/run-log-schema.test.js +455 -0
- package/dist/src/lib/workflow/run-log-schema.test.js.map +1 -0
- package/package.json +2 -1
- package/templates/hooks/pre-tool.sh +14 -2
- package/templates/scripts/cleanup-worktree.sh +23 -1
- package/templates/skills/exec/SKILL.md +18 -0
- package/templates/skills/fullsolve/SKILL.md +26 -0
package/dist/bin/cli.js
CHANGED
|
@@ -45,15 +45,21 @@ program
|
|
|
45
45
|
.action(statusCommand);
|
|
46
46
|
program
|
|
47
47
|
.command("run")
|
|
48
|
-
.description("Execute workflow for GitHub issues")
|
|
49
|
-
.argument("
|
|
48
|
+
.description("Execute workflow for GitHub issues using Claude Agent SDK")
|
|
49
|
+
.argument("[issues...]", "Issue numbers to process")
|
|
50
50
|
.option("--phases <list>", "Phases to run (default: spec,exec,qa)")
|
|
51
51
|
.option("--sequential", "Run issues sequentially")
|
|
52
52
|
.option("-d, --dry-run", "Preview without execution")
|
|
53
|
-
.option("-v, --verbose", "Verbose output")
|
|
53
|
+
.option("-v, --verbose", "Verbose output with streaming")
|
|
54
54
|
.option("--timeout <seconds>", "Timeout per phase in seconds", parseInt)
|
|
55
55
|
.option("--log-json", "Enable structured JSON logging")
|
|
56
56
|
.option("--log-path <path>", "Custom log directory path")
|
|
57
|
+
.option("-q, --quality-loop", "Enable quality loop with auto-retry")
|
|
58
|
+
.option("--max-iterations <n>", "Max iterations for quality loop (default: 3)", parseInt)
|
|
59
|
+
.option("--batch <issues>", 'Group of issues to run together (e.g., --batch "1 2" --batch "3")', (value, prev) => prev.concat([value]), [])
|
|
60
|
+
.option("--smart-tests", "Enable smart test detection (default)")
|
|
61
|
+
.option("--no-smart-tests", "Disable smart test detection")
|
|
62
|
+
.option("--testgen", "Run testgen phase after spec")
|
|
57
63
|
.action(runCommand);
|
|
58
64
|
program
|
|
59
65
|
.command("logs")
|
package/dist/bin/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":";AACA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,mCAAmC;AACnC,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,WAAW,GAAG,GAAG,CAAC;AAChC,CAAC;AAED,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CACV,8EAA8E,CAC/E;KACA,OAAO,CAAC,OAAO,CAAC;KAChB,MAAM,CAAC,YAAY,EAAE,wBAAwB,CAAC,CAAC;AAElD,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,oCAAoC,CAAC;KACjD,MAAM,CAAC,qBAAqB,EAAE,0CAA0C,CAAC;KACzE,MAAM,CAAC,WAAW,EAAE,2BAA2B,CAAC;KAChD,MAAM,CAAC,aAAa,EAAE,kCAAkC,CAAC;KACzD,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,2CAA2C,CAAC;KACxD,MAAM,CAAC,eAAe,EAAE,mDAAmD,CAAC;KAC5E,MAAM,CAAC,aAAa,EAAE,+BAA+B,CAAC;KACtD,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,4CAA4C,CAAC;KACzD,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,+CAA+C,CAAC;KAC5D,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* sequant run - Execute workflow for GitHub issues
|
|
3
3
|
*
|
|
4
|
-
* Runs the Sequant workflow (/spec → /exec → /qa) for one or more issues
|
|
4
|
+
* Runs the Sequant workflow (/spec → /exec → /qa) for one or more issues
|
|
5
|
+
* using the Claude Agent SDK for proper skill invocation.
|
|
5
6
|
*/
|
|
6
7
|
interface RunOptions {
|
|
7
8
|
phases?: string;
|
|
@@ -11,6 +12,12 @@ interface RunOptions {
|
|
|
11
12
|
timeout?: number;
|
|
12
13
|
logJson?: boolean;
|
|
13
14
|
logPath?: string;
|
|
15
|
+
qualityLoop?: boolean;
|
|
16
|
+
maxIterations?: number;
|
|
17
|
+
batch?: string[];
|
|
18
|
+
smartTests?: boolean;
|
|
19
|
+
noSmartTests?: boolean;
|
|
20
|
+
testgen?: boolean;
|
|
14
21
|
}
|
|
15
22
|
/**
|
|
16
23
|
* Main run command
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/commands/run.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/commands/run.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwCH,UAAU,UAAU;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA2SD;;GAEG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CA6Pf"}
|
package/dist/src/commands/run.js
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* sequant run - Execute workflow for GitHub issues
|
|
3
3
|
*
|
|
4
|
-
* Runs the Sequant workflow (/spec → /exec → /qa) for one or more issues
|
|
4
|
+
* Runs the Sequant workflow (/spec → /exec → /qa) for one or more issues
|
|
5
|
+
* using the Claude Agent SDK for proper skill invocation.
|
|
5
6
|
*/
|
|
6
7
|
import chalk from "chalk";
|
|
7
|
-
import {
|
|
8
|
+
import { spawnSync } from "child_process";
|
|
9
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
8
10
|
import { getManifest } from "../lib/manifest.js";
|
|
9
11
|
import { LogWriter, createPhaseLogFromTiming, } from "../lib/workflow/log-writer.js";
|
|
12
|
+
import { DEFAULT_PHASES, DEFAULT_CONFIG, } from "../lib/workflow/types.js";
|
|
10
13
|
/**
|
|
11
|
-
*
|
|
14
|
+
* Natural language prompts for each phase
|
|
15
|
+
* These prompts will invoke the corresponding skills via natural language
|
|
12
16
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
17
|
+
const PHASE_PROMPTS = {
|
|
18
|
+
spec: "Review GitHub issue #{issue} and create an implementation plan with verification criteria. Run the /spec {issue} workflow.",
|
|
19
|
+
testgen: "Generate test stubs for GitHub issue #{issue} based on the specification. Run the /testgen {issue} workflow.",
|
|
20
|
+
exec: "Implement the feature for GitHub issue #{issue} following the spec. Run the /exec {issue} workflow.",
|
|
21
|
+
test: "Execute structured browser-based testing for GitHub issue #{issue}. Run the /test {issue} workflow.",
|
|
22
|
+
qa: "Review the implementation for GitHub issue #{issue} against acceptance criteria. Run the /qa {issue} workflow.",
|
|
23
|
+
loop: "Parse test/QA findings for GitHub issue #{issue} and iterate until quality gates pass. Run the /loop {issue} workflow.",
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* UI-related labels that trigger automatic test phase
|
|
27
|
+
*/
|
|
28
|
+
const UI_LABELS = ["ui", "frontend", "admin", "web", "browser"];
|
|
21
29
|
/**
|
|
22
30
|
* Format duration in human-readable format
|
|
23
31
|
*/
|
|
@@ -30,9 +38,15 @@ function formatDuration(seconds) {
|
|
|
30
38
|
return `${mins}m ${secs.toFixed(0)}s`;
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
33
|
-
*
|
|
41
|
+
* Get the prompt for a phase with the issue number substituted
|
|
34
42
|
*/
|
|
35
|
-
|
|
43
|
+
function getPhasePrompt(phase, issueNumber) {
|
|
44
|
+
return PHASE_PROMPTS[phase].replace(/\{issue\}/g, String(issueNumber));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Execute a single phase for an issue using Claude Agent SDK
|
|
48
|
+
*/
|
|
49
|
+
async function executePhase(issueNumber, phase, config, sessionId) {
|
|
36
50
|
const startTime = Date.now();
|
|
37
51
|
if (config.dryRun) {
|
|
38
52
|
// Dry run - just simulate
|
|
@@ -45,61 +59,130 @@ async function executePhase(issueNumber, phase, config) {
|
|
|
45
59
|
durationSeconds: 0,
|
|
46
60
|
};
|
|
47
61
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
const prompt = getPhasePrompt(phase, issueNumber);
|
|
63
|
+
if (config.verbose) {
|
|
64
|
+
console.log(chalk.gray(` Prompt: ${prompt}`));
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
// Create abort controller for timeout
|
|
68
|
+
const abortController = new AbortController();
|
|
69
|
+
const timeoutId = setTimeout(() => {
|
|
70
|
+
abortController.abort();
|
|
71
|
+
}, config.phaseTimeout * 1000);
|
|
72
|
+
let resultSessionId;
|
|
73
|
+
let resultMessage;
|
|
74
|
+
let lastError;
|
|
75
|
+
// Execute using Claude Agent SDK
|
|
76
|
+
const queryInstance = query({
|
|
77
|
+
prompt,
|
|
78
|
+
options: {
|
|
79
|
+
abortController,
|
|
80
|
+
cwd: process.cwd(),
|
|
81
|
+
// Load project settings including skills
|
|
82
|
+
settingSources: ["project"],
|
|
83
|
+
// Use Claude Code's system prompt and tools
|
|
84
|
+
systemPrompt: { type: "preset", preset: "claude_code" },
|
|
85
|
+
tools: { type: "preset", preset: "claude_code" },
|
|
86
|
+
// Bypass permissions for headless execution
|
|
87
|
+
permissionMode: "bypassPermissions",
|
|
88
|
+
allowDangerouslySkipPermissions: true,
|
|
89
|
+
// Resume from previous session if provided
|
|
90
|
+
...(sessionId ? { resume: sessionId } : {}),
|
|
91
|
+
// Configure smart tests via environment
|
|
92
|
+
env: {
|
|
93
|
+
...process.env,
|
|
94
|
+
CLAUDE_HOOKS_SMART_TESTS: config.noSmartTests ? "false" : "true",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
59
97
|
});
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
98
|
+
// Stream and process messages
|
|
99
|
+
for await (const message of queryInstance) {
|
|
100
|
+
// Capture session ID from system init message
|
|
101
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
102
|
+
resultSessionId = message.session_id;
|
|
103
|
+
}
|
|
104
|
+
// Show streaming output in verbose mode
|
|
105
|
+
if (config.verbose && message.type === "assistant") {
|
|
106
|
+
// Extract text content from the message
|
|
107
|
+
const content = message.message.content;
|
|
108
|
+
const textContent = content
|
|
109
|
+
.filter((c) => c.type === "text" && c.text)
|
|
110
|
+
.map((c) => c.text)
|
|
111
|
+
.join("");
|
|
112
|
+
if (textContent) {
|
|
113
|
+
process.stdout.write(chalk.gray(textContent));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Capture the final result
|
|
117
|
+
if (message.type === "result") {
|
|
118
|
+
resultMessage = message;
|
|
75
119
|
}
|
|
76
|
-
|
|
77
|
-
|
|
120
|
+
}
|
|
121
|
+
clearTimeout(timeoutId);
|
|
122
|
+
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
123
|
+
// Check result status
|
|
124
|
+
if (resultMessage) {
|
|
125
|
+
if (resultMessage.subtype === "success") {
|
|
126
|
+
return {
|
|
78
127
|
phase,
|
|
79
128
|
success: true,
|
|
80
129
|
durationSeconds,
|
|
81
|
-
|
|
130
|
+
sessionId: resultSessionId,
|
|
131
|
+
};
|
|
82
132
|
}
|
|
83
133
|
else {
|
|
84
|
-
|
|
134
|
+
// Handle error subtypes
|
|
135
|
+
const errorSubtype = resultMessage.subtype;
|
|
136
|
+
if (errorSubtype === "error_max_turns") {
|
|
137
|
+
lastError = "Max turns reached";
|
|
138
|
+
}
|
|
139
|
+
else if (errorSubtype === "error_during_execution") {
|
|
140
|
+
lastError =
|
|
141
|
+
resultMessage.errors?.join(", ") || "Error during execution";
|
|
142
|
+
}
|
|
143
|
+
else if (errorSubtype === "error_max_budget_usd") {
|
|
144
|
+
lastError = "Budget limit exceeded";
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
lastError = `Error: ${errorSubtype}`;
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
85
150
|
phase,
|
|
86
151
|
success: false,
|
|
87
152
|
durationSeconds,
|
|
88
|
-
error:
|
|
89
|
-
|
|
153
|
+
error: lastError,
|
|
154
|
+
sessionId: resultSessionId,
|
|
155
|
+
};
|
|
90
156
|
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
157
|
+
}
|
|
158
|
+
// No result message received
|
|
159
|
+
return {
|
|
160
|
+
phase,
|
|
161
|
+
success: false,
|
|
162
|
+
durationSeconds: (Date.now() - startTime) / 1000,
|
|
163
|
+
error: "No result received from Claude",
|
|
164
|
+
sessionId: resultSessionId,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
169
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
170
|
+
// Check if it was an abort (timeout)
|
|
171
|
+
if (error.includes("abort") || error.includes("AbortError")) {
|
|
172
|
+
return {
|
|
96
173
|
phase,
|
|
97
174
|
success: false,
|
|
98
175
|
durationSeconds,
|
|
99
|
-
error:
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
176
|
+
error: `Timeout after ${config.phaseTimeout}s`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
phase,
|
|
181
|
+
success: false,
|
|
182
|
+
durationSeconds,
|
|
183
|
+
error,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
103
186
|
}
|
|
104
187
|
/**
|
|
105
188
|
* Fetch issue info from GitHub
|
|
@@ -129,6 +212,68 @@ async function getIssueInfo(issueNumber) {
|
|
|
129
212
|
}
|
|
130
213
|
return { title: `Issue #${issueNumber}`, labels: [] };
|
|
131
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Check if an issue has UI-related labels
|
|
217
|
+
*/
|
|
218
|
+
function hasUILabels(labels) {
|
|
219
|
+
return labels.some((label) => UI_LABELS.some((uiLabel) => label.toLowerCase().includes(uiLabel)));
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Determine phases to run based on options and issue labels
|
|
223
|
+
*/
|
|
224
|
+
function determinePhasesForIssue(basePhases, labels, options) {
|
|
225
|
+
let phases = [...basePhases];
|
|
226
|
+
// Add testgen phase after spec if requested
|
|
227
|
+
if (options.testgen && phases.includes("spec")) {
|
|
228
|
+
const specIndex = phases.indexOf("spec");
|
|
229
|
+
if (!phases.includes("testgen")) {
|
|
230
|
+
phases.splice(specIndex + 1, 0, "testgen");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Auto-detect UI issues and add test phase
|
|
234
|
+
if (hasUILabels(labels) && !phases.includes("test")) {
|
|
235
|
+
// Add test phase before qa if present, otherwise at the end
|
|
236
|
+
const qaIndex = phases.indexOf("qa");
|
|
237
|
+
if (qaIndex !== -1) {
|
|
238
|
+
phases.splice(qaIndex, 0, "test");
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
phases.push("test");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return phases;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Parse environment variables for CI configuration
|
|
248
|
+
*/
|
|
249
|
+
function getEnvConfig() {
|
|
250
|
+
const config = {};
|
|
251
|
+
if (process.env.SEQUANT_QUALITY_LOOP === "true") {
|
|
252
|
+
config.qualityLoop = true;
|
|
253
|
+
}
|
|
254
|
+
if (process.env.SEQUANT_MAX_ITERATIONS) {
|
|
255
|
+
const maxIter = parseInt(process.env.SEQUANT_MAX_ITERATIONS, 10);
|
|
256
|
+
if (!isNaN(maxIter)) {
|
|
257
|
+
config.maxIterations = maxIter;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (process.env.SEQUANT_SMART_TESTS === "false") {
|
|
261
|
+
config.noSmartTests = true;
|
|
262
|
+
}
|
|
263
|
+
if (process.env.SEQUANT_TESTGEN === "true") {
|
|
264
|
+
config.testgen = true;
|
|
265
|
+
}
|
|
266
|
+
return config;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Parse batch arguments into groups of issues
|
|
270
|
+
*/
|
|
271
|
+
function parseBatches(batchArgs) {
|
|
272
|
+
return batchArgs.map((batch) => batch
|
|
273
|
+
.split(/\s+/)
|
|
274
|
+
.map((n) => parseInt(n, 10))
|
|
275
|
+
.filter((n) => !isNaN(n)));
|
|
276
|
+
}
|
|
132
277
|
/**
|
|
133
278
|
* Main run command
|
|
134
279
|
*/
|
|
@@ -140,36 +285,44 @@ export async function runCommand(issues, options) {
|
|
|
140
285
|
console.log(chalk.red("❌ Sequant is not initialized. Run `sequant init` first."));
|
|
141
286
|
return;
|
|
142
287
|
}
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
288
|
+
// Merge environment config with CLI options
|
|
289
|
+
const envConfig = getEnvConfig();
|
|
290
|
+
const mergedOptions = { ...envConfig, ...options };
|
|
291
|
+
// Parse issue numbers (or use batch mode)
|
|
292
|
+
let issueNumbers;
|
|
293
|
+
let batches = null;
|
|
294
|
+
if (mergedOptions.batch && mergedOptions.batch.length > 0) {
|
|
295
|
+
batches = parseBatches(mergedOptions.batch);
|
|
296
|
+
issueNumbers = batches.flat();
|
|
297
|
+
console.log(chalk.gray(` Batch mode: ${batches.map((b) => `[${b.join(", ")}]`).join(" → ")}`));
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
issueNumbers = issues.map((i) => parseInt(i, 10)).filter((n) => !isNaN(n));
|
|
148
301
|
}
|
|
149
|
-
// Parse issue numbers
|
|
150
|
-
const issueNumbers = issues
|
|
151
|
-
.map((i) => parseInt(i, 10))
|
|
152
|
-
.filter((n) => !isNaN(n));
|
|
153
302
|
if (issueNumbers.length === 0) {
|
|
154
303
|
console.log(chalk.red("❌ No valid issue numbers provided."));
|
|
155
304
|
console.log(chalk.gray("\nUsage: sequant run <issues...> [options]"));
|
|
156
305
|
console.log(chalk.gray("Example: sequant run 1 2 3 --sequential"));
|
|
306
|
+
console.log(chalk.gray('Batch example: sequant run --batch "1 2" --batch "3"'));
|
|
157
307
|
return;
|
|
158
308
|
}
|
|
159
309
|
// Build config
|
|
160
310
|
const config = {
|
|
161
311
|
...DEFAULT_CONFIG,
|
|
162
|
-
phases:
|
|
163
|
-
?
|
|
312
|
+
phases: mergedOptions.phases
|
|
313
|
+
? mergedOptions.phases.split(",").map((p) => p.trim())
|
|
164
314
|
: DEFAULT_PHASES,
|
|
165
|
-
sequential:
|
|
166
|
-
dryRun:
|
|
167
|
-
verbose:
|
|
168
|
-
phaseTimeout:
|
|
315
|
+
sequential: mergedOptions.sequential ?? false,
|
|
316
|
+
dryRun: mergedOptions.dryRun ?? false,
|
|
317
|
+
verbose: mergedOptions.verbose ?? false,
|
|
318
|
+
phaseTimeout: mergedOptions.timeout ?? DEFAULT_CONFIG.phaseTimeout,
|
|
319
|
+
qualityLoop: mergedOptions.qualityLoop ?? false,
|
|
320
|
+
maxIterations: mergedOptions.maxIterations ?? DEFAULT_CONFIG.maxIterations,
|
|
321
|
+
noSmartTests: mergedOptions.noSmartTests ?? false,
|
|
169
322
|
};
|
|
170
323
|
// Initialize log writer if JSON logging enabled
|
|
171
324
|
let logWriter = null;
|
|
172
|
-
if (
|
|
325
|
+
if (mergedOptions.logJson && !config.dryRun) {
|
|
173
326
|
const runConfig = {
|
|
174
327
|
phases: config.phases,
|
|
175
328
|
sequential: config.sequential,
|
|
@@ -177,7 +330,7 @@ export async function runCommand(issues, options) {
|
|
|
177
330
|
maxIterations: config.maxIterations,
|
|
178
331
|
};
|
|
179
332
|
logWriter = new LogWriter({
|
|
180
|
-
logPath:
|
|
333
|
+
logPath: mergedOptions.logPath,
|
|
181
334
|
verbose: config.verbose,
|
|
182
335
|
});
|
|
183
336
|
await logWriter.initialize(runConfig);
|
|
@@ -186,6 +339,15 @@ export async function runCommand(issues, options) {
|
|
|
186
339
|
console.log(chalk.gray(` Stack: ${manifest.stack}`));
|
|
187
340
|
console.log(chalk.gray(` Phases: ${config.phases.join(" → ")}`));
|
|
188
341
|
console.log(chalk.gray(` Mode: ${config.sequential ? "sequential" : "parallel"}`));
|
|
342
|
+
if (config.qualityLoop) {
|
|
343
|
+
console.log(chalk.gray(` Quality loop: enabled (max ${config.maxIterations} iterations)`));
|
|
344
|
+
}
|
|
345
|
+
if (mergedOptions.testgen) {
|
|
346
|
+
console.log(chalk.gray(` Testgen: enabled`));
|
|
347
|
+
}
|
|
348
|
+
if (config.noSmartTests) {
|
|
349
|
+
console.log(chalk.gray(` Smart tests: disabled`));
|
|
350
|
+
}
|
|
189
351
|
if (config.dryRun) {
|
|
190
352
|
console.log(chalk.yellow(` ⚠️ DRY RUN - no actual execution`));
|
|
191
353
|
}
|
|
@@ -195,15 +357,30 @@ export async function runCommand(issues, options) {
|
|
|
195
357
|
console.log(chalk.gray(` Issues: ${issueNumbers.map((n) => `#${n}`).join(", ")}`));
|
|
196
358
|
// Execute
|
|
197
359
|
const results = [];
|
|
198
|
-
if (
|
|
360
|
+
if (batches) {
|
|
361
|
+
// Batch execution: run batches sequentially, issues within batch based on mode
|
|
362
|
+
for (let batchIdx = 0; batchIdx < batches.length; batchIdx++) {
|
|
363
|
+
const batch = batches[batchIdx];
|
|
364
|
+
console.log(chalk.blue(`\n Batch ${batchIdx + 1}/${batches.length}: Issues ${batch.map((n) => `#${n}`).join(", ")}`));
|
|
365
|
+
const batchResults = await executeBatch(batch, config, logWriter, mergedOptions);
|
|
366
|
+
results.push(...batchResults);
|
|
367
|
+
// Check if batch failed and we should stop
|
|
368
|
+
const batchFailed = batchResults.some((r) => !r.success);
|
|
369
|
+
if (batchFailed && config.sequential) {
|
|
370
|
+
console.log(chalk.yellow(`\n ⚠️ Batch ${batchIdx + 1} failed, stopping batch execution`));
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
else if (config.sequential) {
|
|
199
376
|
// Sequential execution
|
|
200
377
|
for (const issueNumber of issueNumbers) {
|
|
378
|
+
const issueInfo = await getIssueInfo(issueNumber);
|
|
201
379
|
// Start issue logging
|
|
202
380
|
if (logWriter) {
|
|
203
|
-
const issueInfo = await getIssueInfo(issueNumber);
|
|
204
381
|
logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
|
|
205
382
|
}
|
|
206
|
-
const result = await runIssueWithLogging(issueNumber, config, logWriter);
|
|
383
|
+
const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, mergedOptions);
|
|
207
384
|
results.push(result);
|
|
208
385
|
// Complete issue logging
|
|
209
386
|
if (logWriter) {
|
|
@@ -219,12 +396,12 @@ export async function runCommand(issues, options) {
|
|
|
219
396
|
// Parallel execution (for now, just run sequentially but don't stop on failure)
|
|
220
397
|
// TODO: Add proper parallel execution with listr2
|
|
221
398
|
for (const issueNumber of issueNumbers) {
|
|
399
|
+
const issueInfo = await getIssueInfo(issueNumber);
|
|
222
400
|
// Start issue logging
|
|
223
401
|
if (logWriter) {
|
|
224
|
-
const issueInfo = await getIssueInfo(issueNumber);
|
|
225
402
|
logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
|
|
226
403
|
}
|
|
227
|
-
const result = await runIssueWithLogging(issueNumber, config, logWriter);
|
|
404
|
+
const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, mergedOptions);
|
|
228
405
|
results.push(result);
|
|
229
406
|
// Complete issue logging
|
|
230
407
|
if (logWriter) {
|
|
@@ -252,7 +429,8 @@ export async function runCommand(issues, options) {
|
|
|
252
429
|
const phases = result.phaseResults
|
|
253
430
|
.map((p) => (p.success ? chalk.green(p.phase) : chalk.red(p.phase)))
|
|
254
431
|
.join(" → ");
|
|
255
|
-
|
|
432
|
+
const loopInfo = result.loopTriggered ? chalk.yellow(" [loop]") : "";
|
|
433
|
+
console.log(` ${status} #${result.issueNumber}: ${phases}${loopInfo}${duration}`);
|
|
256
434
|
}
|
|
257
435
|
console.log("");
|
|
258
436
|
if (logPath) {
|
|
@@ -269,46 +447,114 @@ export async function runCommand(issues, options) {
|
|
|
269
447
|
}
|
|
270
448
|
}
|
|
271
449
|
/**
|
|
272
|
-
* Execute
|
|
450
|
+
* Execute a batch of issues
|
|
273
451
|
*/
|
|
274
|
-
async function
|
|
452
|
+
async function executeBatch(issueNumbers, config, logWriter, options) {
|
|
453
|
+
const results = [];
|
|
454
|
+
for (const issueNumber of issueNumbers) {
|
|
455
|
+
const issueInfo = await getIssueInfo(issueNumber);
|
|
456
|
+
// Start issue logging
|
|
457
|
+
if (logWriter) {
|
|
458
|
+
logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
|
|
459
|
+
}
|
|
460
|
+
const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, options);
|
|
461
|
+
results.push(result);
|
|
462
|
+
// Complete issue logging
|
|
463
|
+
if (logWriter) {
|
|
464
|
+
logWriter.completeIssue();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return results;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Execute all phases for a single issue with logging and quality loop
|
|
471
|
+
*/
|
|
472
|
+
async function runIssueWithLogging(issueNumber, config, logWriter, labels, options) {
|
|
275
473
|
const startTime = Date.now();
|
|
276
474
|
const phaseResults = [];
|
|
475
|
+
let loopTriggered = false;
|
|
476
|
+
let sessionId;
|
|
277
477
|
console.log(chalk.blue(`\n Issue #${issueNumber}`));
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
: "failure", { error: result.error });
|
|
291
|
-
logWriter.logPhase(phaseLog);
|
|
478
|
+
// Determine phases for this specific issue
|
|
479
|
+
const phases = determinePhasesForIssue(config.phases, labels, options);
|
|
480
|
+
if (phases.length !== config.phases.length) {
|
|
481
|
+
console.log(chalk.gray(` Phases adjusted: ${phases.join(" → ")}`));
|
|
482
|
+
}
|
|
483
|
+
let iteration = 0;
|
|
484
|
+
const maxIterations = config.qualityLoop ? config.maxIterations : 1;
|
|
485
|
+
while (iteration < maxIterations) {
|
|
486
|
+
iteration++;
|
|
487
|
+
if (config.qualityLoop && iteration > 1) {
|
|
488
|
+
console.log(chalk.yellow(` Quality loop iteration ${iteration}/${maxIterations}`));
|
|
489
|
+
loopTriggered = true;
|
|
292
490
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
491
|
+
let phasesFailed = false;
|
|
492
|
+
for (const phase of phases) {
|
|
493
|
+
console.log(chalk.gray(` ⏳ ${phase}...`));
|
|
494
|
+
const phaseStartTime = new Date();
|
|
495
|
+
const result = await executePhase(issueNumber, phase, config, sessionId);
|
|
496
|
+
const phaseEndTime = new Date();
|
|
497
|
+
// Capture session ID for subsequent phases
|
|
498
|
+
if (result.sessionId) {
|
|
499
|
+
sessionId = result.sessionId;
|
|
500
|
+
}
|
|
501
|
+
phaseResults.push(result);
|
|
502
|
+
// Log phase result
|
|
503
|
+
if (logWriter) {
|
|
504
|
+
const phaseLog = createPhaseLogFromTiming(phase, issueNumber, phaseStartTime, phaseEndTime, result.success
|
|
505
|
+
? "success"
|
|
506
|
+
: result.error?.includes("Timeout")
|
|
507
|
+
? "timeout"
|
|
508
|
+
: "failure", { error: result.error });
|
|
509
|
+
logWriter.logPhase(phaseLog);
|
|
510
|
+
}
|
|
511
|
+
if (result.success) {
|
|
512
|
+
const duration = result.durationSeconds
|
|
513
|
+
? ` (${formatDuration(result.durationSeconds)})`
|
|
514
|
+
: "";
|
|
515
|
+
console.log(chalk.green(` ✓ ${phase}${duration}`));
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
console.log(chalk.red(` ✗ ${phase}: ${result.error}`));
|
|
519
|
+
phasesFailed = true;
|
|
520
|
+
// If quality loop enabled, run loop phase to fix issues
|
|
521
|
+
if (config.qualityLoop && iteration < maxIterations) {
|
|
522
|
+
console.log(chalk.yellow(` Running /loop to fix issues...`));
|
|
523
|
+
const loopResult = await executePhase(issueNumber, "loop", config, sessionId);
|
|
524
|
+
phaseResults.push(loopResult);
|
|
525
|
+
if (loopResult.sessionId) {
|
|
526
|
+
sessionId = loopResult.sessionId;
|
|
527
|
+
}
|
|
528
|
+
if (loopResult.success) {
|
|
529
|
+
console.log(chalk.green(` ✓ loop - retrying phases`));
|
|
530
|
+
// Continue to next iteration
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
console.log(chalk.red(` ✗ loop: ${loopResult.error}`));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
// Stop on first failure (if not in quality loop or loop failed)
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
298
540
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
541
|
+
// If all phases passed, exit the loop
|
|
542
|
+
if (!phasesFailed) {
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
// If we're not in quality loop mode, don't retry
|
|
546
|
+
if (!config.qualityLoop) {
|
|
302
547
|
break;
|
|
303
548
|
}
|
|
304
549
|
}
|
|
305
550
|
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
306
|
-
const success = phaseResults.every((r) => r.success);
|
|
551
|
+
const success = phaseResults.length > 0 && phaseResults.every((r) => r.success);
|
|
307
552
|
return {
|
|
308
553
|
issueNumber,
|
|
309
554
|
success,
|
|
310
555
|
phaseResults,
|
|
311
556
|
durationSeconds,
|
|
557
|
+
loopTriggered,
|
|
312
558
|
};
|
|
313
559
|
}
|
|
314
560
|
//# sourceMappingURL=run.js.map
|