sequant 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +7 -6
- package/dist/bin/cli.js +2 -1
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/.mcp.json +6 -0
- package/dist/marketplace/external_plugins/sequant/README.md +58 -8
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +19 -8
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +36 -49
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +158 -48
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +354 -352
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +1155 -33
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +35 -4
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +2157 -104
- package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +386 -0
- package/dist/marketplace/external_plugins/sequant/skills/solve/SKILL.md +38 -664
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +505 -120
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +246 -1
- package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +138 -1
- package/dist/src/commands/dashboard.js +1 -1
- package/dist/src/commands/doctor.js +1 -1
- package/dist/src/commands/init.js +10 -10
- package/dist/src/commands/logs.js +1 -1
- package/dist/src/commands/run.js +49 -39
- package/dist/src/commands/state.js +3 -3
- package/dist/src/commands/status.js +5 -5
- package/dist/src/commands/sync.js +8 -8
- package/dist/src/commands/update.js +16 -16
- package/dist/src/lib/cli-ui.js +20 -19
- package/dist/src/lib/merge-check/index.js +2 -2
- package/dist/src/lib/settings.d.ts +8 -0
- package/dist/src/lib/settings.js +1 -0
- package/dist/src/lib/shutdown.js +1 -1
- package/dist/src/lib/templates.js +2 -0
- package/dist/src/lib/wizard.js +6 -4
- package/dist/src/lib/workflow/batch-executor.d.ts +9 -1
- package/dist/src/lib/workflow/batch-executor.js +39 -2
- package/dist/src/lib/workflow/log-writer.js +6 -6
- package/dist/src/lib/workflow/metrics-writer.js +5 -3
- package/dist/src/lib/workflow/phase-executor.d.ts +1 -1
- package/dist/src/lib/workflow/phase-executor.js +52 -22
- package/dist/src/lib/workflow/platforms/github.js +5 -1
- package/dist/src/lib/workflow/state-cleanup.js +1 -1
- package/dist/src/lib/workflow/state-manager.js +15 -13
- package/dist/src/lib/workflow/state-rebuild.js +2 -2
- package/dist/src/lib/workflow/types.d.ts +27 -0
- package/dist/src/lib/workflow/worktree-manager.js +40 -41
- package/dist/src/lib/worktree-isolation.d.ts +130 -0
- package/dist/src/lib/worktree-isolation.js +310 -0
- package/package.json +24 -14
- package/templates/agents/sequant-explorer.md +23 -0
- package/templates/agents/sequant-implementer.md +18 -0
- package/templates/agents/sequant-qa-checker.md +24 -0
- package/templates/agents/sequant-testgen.md +25 -0
- package/templates/scripts/cleanup-worktree.sh +18 -0
- package/templates/skills/_shared/references/subagent-types.md +158 -48
- package/templates/skills/exec/SKILL.md +72 -6
- package/templates/skills/qa/SKILL.md +8 -217
- package/templates/skills/spec/SKILL.md +446 -120
- package/templates/skills/testgen/SKILL.md +138 -1
package/dist/src/lib/settings.js
CHANGED
package/dist/src/lib/shutdown.js
CHANGED
|
@@ -138,7 +138,7 @@ export class ShutdownManager {
|
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
this._isShuttingDown = true;
|
|
141
|
-
this.output(chalk.yellow(`\n
|
|
141
|
+
this.output(chalk.yellow(`\n! Received ${signal}, shutting down gracefully...`));
|
|
142
142
|
// Abort all in-flight phases immediately
|
|
143
143
|
if (this.abortControllers.size > 0) {
|
|
144
144
|
const count = this.abortControllers.size;
|
|
@@ -205,6 +205,8 @@ export async function copyTemplates(stack, tokens, options = {}) {
|
|
|
205
205
|
}
|
|
206
206
|
// Copy skills
|
|
207
207
|
await copyDir(join(templatesDir, "skills"), ".claude/skills");
|
|
208
|
+
// Copy agent definitions
|
|
209
|
+
await copyDir(join(templatesDir, "agents"), ".claude/agents");
|
|
208
210
|
// Copy hooks
|
|
209
211
|
await copyDir(join(templatesDir, "hooks"), ".claude/hooks");
|
|
210
212
|
// Copy memory (constitution, etc.)
|
package/dist/src/lib/wizard.js
CHANGED
|
@@ -66,11 +66,13 @@ export function displayDependencyStatus(result) {
|
|
|
66
66
|
console.log(chalk.yellow(` ${chalk.yellow("!")} ${dep.displayName} - installed but not authenticated`));
|
|
67
67
|
}
|
|
68
68
|
else {
|
|
69
|
-
console.log(chalk.green(` ${chalk.green("
|
|
69
|
+
console.log(chalk.green(` ${chalk.green("\u2714")} ${dep.displayName} - installed`));
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
else {
|
|
73
|
-
const marker = dep.required
|
|
73
|
+
const marker = dep.required
|
|
74
|
+
? chalk.red("\u2716")
|
|
75
|
+
: chalk.yellow("\u00B7");
|
|
74
76
|
const status = dep.required
|
|
75
77
|
? "not installed (required)"
|
|
76
78
|
: "not installed (optional)";
|
|
@@ -135,7 +137,7 @@ export async function runSetupWizard(result, options = {}) {
|
|
|
135
137
|
};
|
|
136
138
|
}
|
|
137
139
|
// Ask if user wants to set up missing dependencies
|
|
138
|
-
let setupDeps
|
|
140
|
+
let setupDeps;
|
|
139
141
|
try {
|
|
140
142
|
const response = await inquirer.prompt([
|
|
141
143
|
{
|
|
@@ -181,7 +183,7 @@ export async function runSetupWizard(result, options = {}) {
|
|
|
181
183
|
console.log(` ${instruction}`);
|
|
182
184
|
}
|
|
183
185
|
console.log();
|
|
184
|
-
let action
|
|
186
|
+
let action;
|
|
185
187
|
try {
|
|
186
188
|
const response = await inquirer.prompt([
|
|
187
189
|
{
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* (including quality-loop retries, checkpoint commits, rebasing, and PR
|
|
8
8
|
* creation).
|
|
9
9
|
*/
|
|
10
|
-
import { IssueResult, type RunOptions, type IssueExecutionContext, type BatchExecutionContext } from "./types.js";
|
|
10
|
+
import { PhaseResult, IssueResult, type RunOptions, type IssueExecutionContext, type BatchExecutionContext } from "./types.js";
|
|
11
11
|
export type { RunOptions, ProgressCallback, IssueExecutionContext, BatchExecutionContext, } from "./types.js";
|
|
12
12
|
/**
|
|
13
13
|
* Emit a structured progress line to stderr for MCP progress notifications.
|
|
@@ -19,6 +19,14 @@ export type { RunOptions, ProgressCallback, IssueExecutionContext, BatchExecutio
|
|
|
19
19
|
* @param event - Phase lifecycle event: "start", "complete", or "failed"
|
|
20
20
|
* @param extra - Optional fields: durationSeconds (on complete), error (on failed)
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* Build enriched prompt context for the /loop phase from a failed phase result (#488).
|
|
24
|
+
* Passes QA verdict, failed ACs, and error directly so the /loop skill doesn't need
|
|
25
|
+
* to reconstruct context from GitHub comments (which fails in subprocess).
|
|
26
|
+
*
|
|
27
|
+
* @internal Exported for testing only
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildLoopContext(failedResult: PhaseResult): string;
|
|
22
30
|
export declare function emitProgressLine(issue: number, phase: string, event?: "start" | "complete" | "failed", extra?: {
|
|
23
31
|
durationSeconds?: number;
|
|
24
32
|
error?: string;
|
|
@@ -26,6 +26,34 @@ import { detectPhasesFromLabels, parseRecommendedWorkflow, determinePhasesForIss
|
|
|
26
26
|
* @param event - Phase lifecycle event: "start", "complete", or "failed"
|
|
27
27
|
* @param extra - Optional fields: durationSeconds (on complete), error (on failed)
|
|
28
28
|
*/
|
|
29
|
+
/**
|
|
30
|
+
* Build enriched prompt context for the /loop phase from a failed phase result (#488).
|
|
31
|
+
* Passes QA verdict, failed ACs, and error directly so the /loop skill doesn't need
|
|
32
|
+
* to reconstruct context from GitHub comments (which fails in subprocess).
|
|
33
|
+
*
|
|
34
|
+
* @internal Exported for testing only
|
|
35
|
+
*/
|
|
36
|
+
export function buildLoopContext(failedResult) {
|
|
37
|
+
const parts = [`Previous phase "${failedResult.phase}" failed.`];
|
|
38
|
+
if (failedResult.verdict) {
|
|
39
|
+
parts.push(`QA Verdict: ${failedResult.verdict}`);
|
|
40
|
+
}
|
|
41
|
+
if (failedResult.summary?.gaps?.length) {
|
|
42
|
+
parts.push(`QA Gaps:\n${failedResult.summary.gaps.map((gap) => `- ${gap}`).join("\n")}`);
|
|
43
|
+
}
|
|
44
|
+
if (failedResult.summary?.suggestions?.length) {
|
|
45
|
+
parts.push(`Suggestions:\n${failedResult.summary.suggestions.map((s) => `- ${s}`).join("\n")}`);
|
|
46
|
+
}
|
|
47
|
+
if (failedResult.error) {
|
|
48
|
+
parts.push(`Error: ${failedResult.error}`);
|
|
49
|
+
}
|
|
50
|
+
// Include tail of output for additional context (truncated to avoid prompt bloat)
|
|
51
|
+
if (failedResult.output) {
|
|
52
|
+
const tail = failedResult.output.slice(-2000);
|
|
53
|
+
parts.push(`Last output:\n${tail}`);
|
|
54
|
+
}
|
|
55
|
+
return parts.join("\n\n");
|
|
56
|
+
}
|
|
29
57
|
export function emitProgressLine(issue, phase, event = "start", extra) {
|
|
30
58
|
if (!process.env.SEQUANT_ORCHESTRATOR)
|
|
31
59
|
return;
|
|
@@ -271,7 +299,7 @@ export async function runIssueWithLogging(ctx) {
|
|
|
271
299
|
catch (error) {
|
|
272
300
|
// State tracking errors shouldn't stop execution
|
|
273
301
|
if (config.verbose) {
|
|
274
|
-
log(chalk.yellow(`
|
|
302
|
+
log(chalk.yellow(` ! State tracking error: ${error}`));
|
|
275
303
|
}
|
|
276
304
|
}
|
|
277
305
|
}
|
|
@@ -634,8 +662,17 @@ export async function runIssueWithLogging(ctx) {
|
|
|
634
662
|
catch {
|
|
635
663
|
/* progress errors must not halt */
|
|
636
664
|
}
|
|
665
|
+
// Build enriched config for loop phase with QA context (#488).
|
|
666
|
+
// Pass verdict, failed ACs, and error directly so the /loop skill
|
|
667
|
+
// doesn't need to reconstruct context from GitHub comments.
|
|
668
|
+
const loopConfig = {
|
|
669
|
+
...issueConfig,
|
|
670
|
+
lastVerdict: result.verdict ?? undefined,
|
|
671
|
+
failedAcs: result.summary?.gaps?.join("; ") ?? undefined,
|
|
672
|
+
promptContext: buildLoopContext(result),
|
|
673
|
+
};
|
|
637
674
|
const loopStartTime = new Date();
|
|
638
|
-
const loopResult = await executePhaseWithRetry(issueNumber, "loop",
|
|
675
|
+
const loopResult = await executePhaseWithRetry(issueNumber, "loop", loopConfig, sessionId, worktreePath, shutdownManager, loopSpinner);
|
|
639
676
|
const loopEndTime = new Date();
|
|
640
677
|
phaseResults.push(loopResult);
|
|
641
678
|
// Emit loop completion/failure progress event (AC-8)
|
|
@@ -53,7 +53,7 @@ export class LogWriter {
|
|
|
53
53
|
await this.ensureLogDirectory(userPath);
|
|
54
54
|
}
|
|
55
55
|
if (this.verbose && this.runLog) {
|
|
56
|
-
console.log(
|
|
56
|
+
console.log(`Log initialized: ${this.runLog.runId}`);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
@@ -79,7 +79,7 @@ export class LogWriter {
|
|
|
79
79
|
// Keep currentIssue in sync for callers that don't pass issueNumber
|
|
80
80
|
this.currentIssue = issueData;
|
|
81
81
|
if (this.verbose) {
|
|
82
|
-
console.log(
|
|
82
|
+
console.log(`Log started: issue #${issueNumber}`);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
@@ -102,7 +102,7 @@ export class LogWriter {
|
|
|
102
102
|
issue.status = "partial";
|
|
103
103
|
}
|
|
104
104
|
if (this.verbose) {
|
|
105
|
-
console.log(
|
|
105
|
+
console.log(`Log phase: ${phaseLog.phase} (${phaseLog.status}) - ${phaseLog.durationSeconds.toFixed(1)}s`);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
/**
|
|
@@ -160,7 +160,7 @@ export class LogWriter {
|
|
|
160
160
|
this.currentIssue = null;
|
|
161
161
|
}
|
|
162
162
|
if (this.verbose) {
|
|
163
|
-
console.log(
|
|
163
|
+
console.log(`Log complete: issue #${issueLog.issueNumber} (${issueLog.status})`);
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
@@ -197,13 +197,13 @@ export class LogWriter {
|
|
|
197
197
|
await this.writeLogFile(userPath, finalLog);
|
|
198
198
|
}
|
|
199
199
|
if (this.verbose) {
|
|
200
|
-
console.log(
|
|
200
|
+
console.log(`Log written: ${projectPath}`);
|
|
201
201
|
}
|
|
202
202
|
// Auto-rotate if needed
|
|
203
203
|
if (this.rotation.enabled) {
|
|
204
204
|
const result = rotateIfNeeded(this.logPath, this.rotation);
|
|
205
205
|
if (result.rotated && this.verbose) {
|
|
206
|
-
console.log(
|
|
206
|
+
console.log(`Log rotated: ${result.deletedCount} old log(s), reclaimed ${(result.bytesReclaimed / 1024).toFixed(1)} KB`);
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
return projectPath;
|
|
@@ -66,7 +66,9 @@ export class MetricsWriter {
|
|
|
66
66
|
}
|
|
67
67
|
catch (error) {
|
|
68
68
|
if (error instanceof SyntaxError) {
|
|
69
|
-
throw new Error(`Invalid JSON in metrics file: ${error.message}
|
|
69
|
+
throw new Error(`Invalid JSON in metrics file: ${error.message}`, {
|
|
70
|
+
cause: error,
|
|
71
|
+
});
|
|
70
72
|
}
|
|
71
73
|
throw error;
|
|
72
74
|
}
|
|
@@ -119,7 +121,7 @@ export class MetricsWriter {
|
|
|
119
121
|
metrics.runs.push(run);
|
|
120
122
|
await this.saveMetrics(metrics);
|
|
121
123
|
if (this.verbose) {
|
|
122
|
-
console.log(
|
|
124
|
+
console.log(`Metrics recorded: ${run.id.slice(0, 8)}... (${run.outcome})`);
|
|
123
125
|
}
|
|
124
126
|
return run;
|
|
125
127
|
}
|
|
@@ -162,7 +164,7 @@ export class MetricsWriter {
|
|
|
162
164
|
fs.unlinkSync(this.metricsPath);
|
|
163
165
|
this.cachedMetrics = null;
|
|
164
166
|
if (this.verbose) {
|
|
165
|
-
console.log(
|
|
167
|
+
console.log(`Metrics deleted: ${this.metricsPath}`);
|
|
166
168
|
}
|
|
167
169
|
}
|
|
168
170
|
}
|
|
@@ -48,7 +48,7 @@ export declare function formatDuration(seconds: number): string;
|
|
|
48
48
|
*
|
|
49
49
|
* @internal Exported for testing only
|
|
50
50
|
*/
|
|
51
|
-
export declare function getPhasePrompt(phase: Phase, issueNumber: number, agent?: string): Promise<string>;
|
|
51
|
+
export declare function getPhasePrompt(phase: Phase, issueNumber: number, agent?: string, promptContext?: string): Promise<string>;
|
|
52
52
|
/**
|
|
53
53
|
* Execute a single phase for an issue using the configured AgentDriver.
|
|
54
54
|
*/
|
|
@@ -224,9 +224,13 @@ export function formatDuration(seconds) {
|
|
|
224
224
|
*
|
|
225
225
|
* @internal Exported for testing only
|
|
226
226
|
*/
|
|
227
|
-
export async function getPhasePrompt(phase, issueNumber, agent) {
|
|
227
|
+
export async function getPhasePrompt(phase, issueNumber, agent, promptContext) {
|
|
228
228
|
const prompts = agent && agent !== "claude-code" ? AIDER_PHASE_PROMPTS : PHASE_PROMPTS;
|
|
229
|
-
|
|
229
|
+
let basePrompt = prompts[phase].replace(/\{issue\}/g, String(issueNumber));
|
|
230
|
+
// Append phase-specific context (e.g., QA findings for loop phase)
|
|
231
|
+
if (promptContext) {
|
|
232
|
+
basePrompt += `\n\n---\n\n${promptContext}`;
|
|
233
|
+
}
|
|
230
234
|
// Include AGENTS.md content in the prompt context for non-Claude agent compatibility.
|
|
231
235
|
// Claude reads CLAUDE.md natively, but other agents (Aider, Codex, Gemini CLI)
|
|
232
236
|
// rely on AGENTS.md for project context.
|
|
@@ -241,7 +245,7 @@ export async function getPhasePrompt(phase, issueNumber, agent) {
|
|
|
241
245
|
*/
|
|
242
246
|
async function executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner) {
|
|
243
247
|
const startTime = Date.now();
|
|
244
|
-
const prompt = await getPhasePrompt(phase, issueNumber, config.agent);
|
|
248
|
+
const prompt = await getPhasePrompt(phase, issueNumber, config.agent, config.promptContext);
|
|
245
249
|
if (config.dryRun) {
|
|
246
250
|
// Dry run - show the prompt that would be sent, then return
|
|
247
251
|
if (config.verbose) {
|
|
@@ -318,6 +322,17 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath,
|
|
|
318
322
|
if (config.issueType) {
|
|
319
323
|
env.SEQUANT_ISSUE_TYPE = config.issueType;
|
|
320
324
|
}
|
|
325
|
+
// Pass QA context to loop phase so it doesn't need to reconstruct from GitHub (#488)
|
|
326
|
+
if (config.lastVerdict) {
|
|
327
|
+
env.SEQUANT_LAST_VERDICT = config.lastVerdict;
|
|
328
|
+
}
|
|
329
|
+
if (config.failedAcs) {
|
|
330
|
+
env.SEQUANT_FAILED_ACS = config.failedAcs;
|
|
331
|
+
}
|
|
332
|
+
// Propagate parallel isolation mode to exec skill (#485)
|
|
333
|
+
if (config.isolateParallel) {
|
|
334
|
+
env.SEQUANT_ISOLATE_PARALLEL = "true";
|
|
335
|
+
}
|
|
321
336
|
// Track whether we're actively streaming verbose output
|
|
322
337
|
// Pausing spinner once per streaming session prevents truncation from rapid pause/resume cycles
|
|
323
338
|
// (Issue #283: ora's stop() clears the current line, which can truncate output when
|
|
@@ -452,37 +467,52 @@ delayFn = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
|
452
467
|
if (config.retry === false) {
|
|
453
468
|
return executePhaseFn(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner);
|
|
454
469
|
}
|
|
470
|
+
// Skip cold-start retries for `loop` phase (#488).
|
|
471
|
+
// Loop is always a re-run after a failed QA — never a first boot.
|
|
472
|
+
// Failures at 47-51s are genuine skill failures, not cold-start issues.
|
|
473
|
+
// Without this guard, 2 cold-start retries + 1 MCP fallback = 3 wasted spawns per loop.
|
|
474
|
+
const skipColdStartRetry = phase === "loop";
|
|
455
475
|
let lastResult;
|
|
456
|
-
|
|
457
|
-
|
|
476
|
+
if (skipColdStartRetry) {
|
|
477
|
+
// Single attempt — no cold-start retry loop
|
|
458
478
|
lastResult = await executePhaseFn(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner);
|
|
459
|
-
const duration = lastResult.durationSeconds ?? 0;
|
|
460
|
-
// Success → return immediately
|
|
461
479
|
if (lastResult.success) {
|
|
462
480
|
return lastResult;
|
|
463
481
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
//
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
// Phase 1: Cold-start retry attempts (with MCP enabled if configured)
|
|
485
|
+
for (let attempt = 0; attempt <= COLD_START_MAX_RETRIES; attempt++) {
|
|
486
|
+
lastResult = await executePhaseFn(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner);
|
|
487
|
+
const duration = lastResult.durationSeconds ?? 0;
|
|
488
|
+
// Success → return immediately
|
|
489
|
+
if (lastResult.success) {
|
|
490
|
+
return lastResult;
|
|
470
491
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
492
|
+
// Genuine failure (took long enough to be real work) → skip cold-start retries.
|
|
493
|
+
// For spec phase, break to allow Phase 3 (spec-specific retry) to run.
|
|
494
|
+
// For other phases, return immediately — no further retries.
|
|
495
|
+
if (duration >= COLD_START_THRESHOLD_SECONDS) {
|
|
496
|
+
if (phase === "spec") {
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
return lastResult;
|
|
500
|
+
}
|
|
501
|
+
// Cold-start failure detected — retry
|
|
502
|
+
if (attempt < COLD_START_MAX_RETRIES) {
|
|
503
|
+
if (config.verbose) {
|
|
504
|
+
console.log(chalk.yellow(`\n ⟳ Cold-start failure detected (${duration.toFixed(1)}s), retrying... (attempt ${attempt + 2}/${COLD_START_MAX_RETRIES + 1})`));
|
|
505
|
+
}
|
|
477
506
|
}
|
|
478
507
|
}
|
|
479
508
|
}
|
|
480
509
|
// Capture the original error for better diagnostics
|
|
481
510
|
const originalError = lastResult.error;
|
|
482
511
|
// Phase 2: MCP fallback - if MCP is enabled and we're still failing, try without MCP
|
|
483
|
-
// This handles npx-based MCP servers that fail on first run due to cold-cache issues
|
|
484
|
-
|
|
485
|
-
|
|
512
|
+
// This handles npx-based MCP servers that fail on first run due to cold-cache issues.
|
|
513
|
+
// Skip for `loop` phase — MCP is never the cause of loop failures (#488).
|
|
514
|
+
if (config.mcp && !lastResult.success && !skipColdStartRetry) {
|
|
515
|
+
console.log(chalk.yellow(`\n ! Phase failed with MCP enabled, retrying without MCP...`));
|
|
486
516
|
// Create config copy with MCP disabled
|
|
487
517
|
const configWithoutMcp = {
|
|
488
518
|
...config,
|
|
@@ -344,8 +344,12 @@ export class GitHubProvider {
|
|
|
344
344
|
stdio: ["pipe", "pipe", "pipe"],
|
|
345
345
|
timeout: 30000,
|
|
346
346
|
});
|
|
347
|
-
if (result.status !== 0 || !result.stdout)
|
|
347
|
+
if (result.status !== 0 || !result.stdout) {
|
|
348
|
+
if (result.stderr) {
|
|
349
|
+
console.error(`gh issue create failed: ${result.stderr.trim()}`);
|
|
350
|
+
}
|
|
348
351
|
return null;
|
|
352
|
+
}
|
|
349
353
|
const url = result.stdout.trim();
|
|
350
354
|
const numberMatch = url.match(/\/issues\/(\d+)$/);
|
|
351
355
|
const number = numberMatch ? parseInt(numberMatch[1], 10) : 0;
|
|
@@ -73,7 +73,7 @@ export async function cleanupStaleEntries(options = {}) {
|
|
|
73
73
|
if (issueState.worktree &&
|
|
74
74
|
!activeWorktrees.includes(issueState.worktree)) {
|
|
75
75
|
if (options.verbose) {
|
|
76
|
-
console.log(
|
|
76
|
+
console.log(`Orphaned: #${issueNum} (worktree not found: ${issueState.worktree})`);
|
|
77
77
|
}
|
|
78
78
|
// Check if this issue has a PR and if it's merged
|
|
79
79
|
let prMerged = false;
|
|
@@ -100,7 +100,7 @@ export class StateManager {
|
|
|
100
100
|
continue;
|
|
101
101
|
}
|
|
102
102
|
if (Date.now() - start > this.lockTimeout) {
|
|
103
|
-
throw new Error(`Timeout acquiring state lock after ${this.lockTimeout}ms
|
|
103
|
+
throw new Error(`Timeout acquiring state lock after ${this.lockTimeout}ms`, { cause: err });
|
|
104
104
|
}
|
|
105
105
|
// Wait and retry
|
|
106
106
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
@@ -150,7 +150,9 @@ export class StateManager {
|
|
|
150
150
|
}
|
|
151
151
|
catch (error) {
|
|
152
152
|
if (error instanceof SyntaxError) {
|
|
153
|
-
throw new Error(`Invalid JSON in state file: ${error.message}
|
|
153
|
+
throw new Error(`Invalid JSON in state file: ${error.message}`, {
|
|
154
|
+
cause: error,
|
|
155
|
+
});
|
|
154
156
|
}
|
|
155
157
|
throw error;
|
|
156
158
|
}
|
|
@@ -182,7 +184,7 @@ export class StateManager {
|
|
|
182
184
|
}
|
|
183
185
|
}
|
|
184
186
|
if (pruned.length > 0 && this.verbose) {
|
|
185
|
-
console.log(
|
|
187
|
+
console.log(`State: Pruned ${pruned.length} expired issue(s): ${pruned.map((k) => `#${k}`).join(", ")}`);
|
|
186
188
|
}
|
|
187
189
|
}
|
|
188
190
|
catch {
|
|
@@ -272,7 +274,7 @@ export class StateManager {
|
|
|
272
274
|
await this.saveState(state);
|
|
273
275
|
});
|
|
274
276
|
if (this.verbose) {
|
|
275
|
-
console.log(
|
|
277
|
+
console.log(`State: Initialized issue #${issueNumber}: ${title}`);
|
|
276
278
|
}
|
|
277
279
|
}
|
|
278
280
|
/**
|
|
@@ -313,7 +315,7 @@ export class StateManager {
|
|
|
313
315
|
await this.saveState(state);
|
|
314
316
|
});
|
|
315
317
|
if (this.verbose) {
|
|
316
|
-
console.log(
|
|
318
|
+
console.log(`State: Phase ${phase} → ${status} for issue #${issueNumber}`);
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
/**
|
|
@@ -335,7 +337,7 @@ export class StateManager {
|
|
|
335
337
|
await this.saveState(state);
|
|
336
338
|
});
|
|
337
339
|
if (this.verbose) {
|
|
338
|
-
console.log(
|
|
340
|
+
console.log(`State: Issue #${issueNumber} status → ${status}`);
|
|
339
341
|
}
|
|
340
342
|
}
|
|
341
343
|
/**
|
|
@@ -353,7 +355,7 @@ export class StateManager {
|
|
|
353
355
|
await this.saveState(state);
|
|
354
356
|
});
|
|
355
357
|
if (this.verbose) {
|
|
356
|
-
console.log(
|
|
358
|
+
console.log(`State: PR #${pr.number} linked to issue #${issueNumber}`);
|
|
357
359
|
}
|
|
358
360
|
}
|
|
359
361
|
/**
|
|
@@ -372,7 +374,7 @@ export class StateManager {
|
|
|
372
374
|
await this.saveState(state);
|
|
373
375
|
});
|
|
374
376
|
if (this.verbose) {
|
|
375
|
-
console.log(
|
|
377
|
+
console.log(`State: Worktree updated for issue #${issueNumber}: ${worktree}`);
|
|
376
378
|
}
|
|
377
379
|
}
|
|
378
380
|
/**
|
|
@@ -407,7 +409,7 @@ export class StateManager {
|
|
|
407
409
|
await this.saveState(state);
|
|
408
410
|
});
|
|
409
411
|
if (this.verbose) {
|
|
410
|
-
console.log(
|
|
412
|
+
console.log(`State: Loop iteration ${iteration} for issue #${issueNumber}`);
|
|
411
413
|
}
|
|
412
414
|
}
|
|
413
415
|
/**
|
|
@@ -423,7 +425,7 @@ export class StateManager {
|
|
|
423
425
|
await this.saveState(state);
|
|
424
426
|
});
|
|
425
427
|
if (this.verbose) {
|
|
426
|
-
console.log(
|
|
428
|
+
console.log(`State: Removed issue #${issueNumber} from state`);
|
|
427
429
|
}
|
|
428
430
|
}
|
|
429
431
|
// === Acceptance Criteria Operations ===
|
|
@@ -444,7 +446,7 @@ export class StateManager {
|
|
|
444
446
|
await this.saveState(state);
|
|
445
447
|
});
|
|
446
448
|
if (this.verbose) {
|
|
447
|
-
console.log(
|
|
449
|
+
console.log(`State: AC updated for issue #${issueNumber}: ${acceptanceCriteria.items.length} items`);
|
|
448
450
|
}
|
|
449
451
|
}
|
|
450
452
|
/**
|
|
@@ -487,7 +489,7 @@ export class StateManager {
|
|
|
487
489
|
await this.saveState(state);
|
|
488
490
|
});
|
|
489
491
|
if (this.verbose) {
|
|
490
|
-
console.log(
|
|
492
|
+
console.log(`State: AC ${acId} → ${status} for issue #${issueNumber}`);
|
|
491
493
|
}
|
|
492
494
|
}
|
|
493
495
|
// === Scope Assessment Operations ===
|
|
@@ -508,7 +510,7 @@ export class StateManager {
|
|
|
508
510
|
await this.saveState(state);
|
|
509
511
|
});
|
|
510
512
|
if (this.verbose) {
|
|
511
|
-
console.log(
|
|
513
|
+
console.log(`State: Scope assessment updated for issue #${issueNumber}: ${scopeAssessment.verdict}`);
|
|
512
514
|
}
|
|
513
515
|
}
|
|
514
516
|
/**
|
|
@@ -55,7 +55,7 @@ export async function rebuildStateFromLogs(options = {}) {
|
|
|
55
55
|
const log = RunLogSchema.safeParse(logData);
|
|
56
56
|
if (!log.success) {
|
|
57
57
|
if (options.verbose) {
|
|
58
|
-
console.log(
|
|
58
|
+
console.log(`! Invalid log format: ${file}`);
|
|
59
59
|
}
|
|
60
60
|
continue;
|
|
61
61
|
}
|
|
@@ -109,7 +109,7 @@ export async function rebuildStateFromLogs(options = {}) {
|
|
|
109
109
|
}
|
|
110
110
|
catch (err) {
|
|
111
111
|
if (options.verbose) {
|
|
112
|
-
console.log(
|
|
112
|
+
console.log(`! Error reading ${file}: ${err}`);
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
}
|
|
@@ -71,6 +71,11 @@ export interface ExecutionConfig {
|
|
|
71
71
|
* Default: "claude-code"
|
|
72
72
|
*/
|
|
73
73
|
agent?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Isolate parallel agent groups in separate worktrees.
|
|
76
|
+
* Propagated as SEQUANT_ISOLATE_PARALLEL env var to exec skill.
|
|
77
|
+
*/
|
|
78
|
+
isolateParallel?: boolean;
|
|
74
79
|
/**
|
|
75
80
|
* Aider-specific configuration. Passed to AiderDriver when agent is "aider".
|
|
76
81
|
*/
|
|
@@ -80,6 +85,22 @@ export interface ExecutionConfig {
|
|
|
80
85
|
* Propagated as SEQUANT_ISSUE_TYPE env var to skills.
|
|
81
86
|
*/
|
|
82
87
|
issueType?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Additional context appended to the phase prompt.
|
|
90
|
+
* Used by the quality loop to pass QA findings directly to the /loop skill
|
|
91
|
+
* so it doesn't need to reconstruct context from GitHub comments.
|
|
92
|
+
*/
|
|
93
|
+
promptContext?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Last QA verdict from a preceding phase.
|
|
96
|
+
* Propagated as SEQUANT_LAST_VERDICT env var to skills.
|
|
97
|
+
*/
|
|
98
|
+
lastVerdict?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Failed AC descriptions from a preceding QA phase.
|
|
101
|
+
* Propagated as SEQUANT_FAILED_ACS env var to skills.
|
|
102
|
+
*/
|
|
103
|
+
failedAcs?: string;
|
|
83
104
|
}
|
|
84
105
|
/**
|
|
85
106
|
* Default execution configuration
|
|
@@ -212,6 +233,12 @@ export interface RunOptions {
|
|
|
212
233
|
* Default: "claude-code"
|
|
213
234
|
*/
|
|
214
235
|
agent?: string;
|
|
236
|
+
/**
|
|
237
|
+
* Isolate parallel agent groups in separate worktrees.
|
|
238
|
+
* When true, each agent in a parallel group gets its own sub-worktree.
|
|
239
|
+
* Resolution priority: CLI flag → settings.agents.isolateParallel → false
|
|
240
|
+
*/
|
|
241
|
+
isolateParallel?: boolean;
|
|
215
242
|
}
|
|
216
243
|
/**
|
|
217
244
|
* CLI arguments for run command
|