sequant 2.1.0 → 2.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/.claude-plugin/plugin.json +1 -1
- package/dist/src/commands/init.js +4 -3
- package/dist/src/commands/run-compat.d.ts +14 -0
- package/dist/src/commands/run-compat.js +12 -0
- package/dist/src/commands/run.d.ts +4 -26
- package/dist/src/commands/run.js +92 -765
- package/dist/src/commands/status.js +9 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +9 -0
- package/dist/src/lib/errors.d.ts +93 -0
- package/dist/src/lib/errors.js +97 -0
- package/dist/src/lib/mcp-config.js +1 -1
- package/dist/src/lib/settings.d.ts +236 -0
- package/dist/src/lib/settings.js +482 -37
- package/dist/src/lib/workflow/batch-executor.js +12 -4
- package/dist/src/lib/workflow/config-resolver.d.ts +50 -0
- package/dist/src/lib/workflow/config-resolver.js +167 -0
- package/dist/src/lib/workflow/error-classifier.d.ts +17 -7
- package/dist/src/lib/workflow/error-classifier.js +113 -15
- package/dist/src/lib/workflow/phase-executor.js +14 -2
- package/dist/src/lib/workflow/run-log-schema.d.ts +12 -0
- package/dist/src/lib/workflow/run-log-schema.js +7 -1
- package/dist/src/lib/workflow/run-orchestrator.d.ts +124 -0
- package/dist/src/lib/workflow/run-orchestrator.js +482 -0
- package/package.json +1 -1
- package/templates/skills/assess/SKILL.md +84 -35
- package/templates/skills/exec/SKILL.md +7 -27
- package/templates/skills/fullsolve/SKILL.md +329 -137
- package/templates/skills/qa/SKILL.md +23 -46
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConfigResolver — 4-layer configuration merge for sequant run.
|
|
3
|
+
*
|
|
4
|
+
* Priority: defaults < settings < env < explicit (CLI flags).
|
|
5
|
+
* Handles Commander.js --no-X boolean negation at the CLI boundary.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { DEFAULT_CONFIG, DEFAULT_PHASES, } from "./types.js";
|
|
10
|
+
import { getEnvConfig } from "./batch-executor.js";
|
|
11
|
+
/**
|
|
12
|
+
* Coerce an env-var string to the type of the default value.
|
|
13
|
+
* Returns the string as-is if no default exists for type inference.
|
|
14
|
+
*/
|
|
15
|
+
function coerceEnvValue(value, defaultValue) {
|
|
16
|
+
if (typeof value !== "string")
|
|
17
|
+
return value;
|
|
18
|
+
if (typeof defaultValue === "number") {
|
|
19
|
+
const n = Number(value);
|
|
20
|
+
return isNaN(n) ? value : n;
|
|
21
|
+
}
|
|
22
|
+
if (typeof defaultValue === "boolean") {
|
|
23
|
+
return value === "true";
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Generic 4-layer priority merge.
|
|
29
|
+
* For each key across all layers: explicit > env > settings > defaults.
|
|
30
|
+
* Env strings are coerced to match the type of the default value.
|
|
31
|
+
*/
|
|
32
|
+
export class ConfigResolver {
|
|
33
|
+
layers;
|
|
34
|
+
constructor(layers) {
|
|
35
|
+
this.layers = layers;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve all layers into a single merged config object.
|
|
39
|
+
* Priority: explicit > env > settings > defaults.
|
|
40
|
+
*/
|
|
41
|
+
resolve() {
|
|
42
|
+
const { defaults, settings, env, explicit } = this.layers;
|
|
43
|
+
// Collect all keys across layers
|
|
44
|
+
const allKeys = new Set([
|
|
45
|
+
...Object.keys(defaults),
|
|
46
|
+
...Object.keys(settings),
|
|
47
|
+
...Object.keys(env),
|
|
48
|
+
...Object.keys(explicit),
|
|
49
|
+
]);
|
|
50
|
+
const result = {};
|
|
51
|
+
for (const key of allKeys) {
|
|
52
|
+
// Check each layer in reverse priority (lowest first)
|
|
53
|
+
const layers = [
|
|
54
|
+
{ value: defaults[key] },
|
|
55
|
+
{ value: settings[key] },
|
|
56
|
+
{ value: env[key] },
|
|
57
|
+
{ value: explicit[key] },
|
|
58
|
+
];
|
|
59
|
+
// Walk from highest to lowest priority, take first defined value
|
|
60
|
+
let resolved = undefined;
|
|
61
|
+
const defaultVal = defaults[key];
|
|
62
|
+
for (const layer of layers) {
|
|
63
|
+
if (layer.value !== undefined) {
|
|
64
|
+
resolved = layer.value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Coerce env values if the winning value came from env layer
|
|
68
|
+
if (resolved !== undefined &&
|
|
69
|
+
explicit[key] === undefined &&
|
|
70
|
+
env[key] !== undefined &&
|
|
71
|
+
settings[key] === undefined) {
|
|
72
|
+
// Only env contributed — coerce
|
|
73
|
+
resolved = coerceEnvValue(resolved, defaultVal);
|
|
74
|
+
}
|
|
75
|
+
else if (resolved !== undefined &&
|
|
76
|
+
explicit[key] === undefined &&
|
|
77
|
+
env[key] !== undefined) {
|
|
78
|
+
// env is present and wins over settings — coerce the env value
|
|
79
|
+
resolved = coerceEnvValue(env[key], defaultVal);
|
|
80
|
+
}
|
|
81
|
+
result[key] = resolved;
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Normalize Commander.js --no-X flags into RunOptions negation fields.
|
|
88
|
+
* This is a thin adapter at the CLI boundary — not used by programmatic callers.
|
|
89
|
+
*/
|
|
90
|
+
export function normalizeCommanderOptions(options) {
|
|
91
|
+
const raw = options;
|
|
92
|
+
return {
|
|
93
|
+
...options,
|
|
94
|
+
...(raw.log === false && { noLog: true }),
|
|
95
|
+
...(raw.smartTests === false && { noSmartTests: true }),
|
|
96
|
+
...(raw.mcp === false && { noMcp: true }),
|
|
97
|
+
...(raw.retry === false && { noRetry: true }),
|
|
98
|
+
...(raw.rebase === false && { noRebase: true }),
|
|
99
|
+
...(raw.pr === false && { noPr: true }),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolve RunOptions + settings + env into a fully merged RunOptions.
|
|
104
|
+
* This replaces the inline merging logic previously in run.ts.
|
|
105
|
+
*/
|
|
106
|
+
export function resolveRunOptions(cliOptions, settings) {
|
|
107
|
+
const normalized = normalizeCommanderOptions(cliOptions);
|
|
108
|
+
const envConfig = getEnvConfig();
|
|
109
|
+
// Strip undefined keys so programmatic callers don't clobber env/settings values
|
|
110
|
+
const defined = Object.fromEntries(Object.entries(normalized).filter(([, v]) => v !== undefined));
|
|
111
|
+
const merged = {
|
|
112
|
+
// Settings defaults
|
|
113
|
+
sequential: defined.sequential ?? settings.run.sequential,
|
|
114
|
+
concurrency: defined.concurrency ?? settings.run.concurrency,
|
|
115
|
+
timeout: defined.timeout ?? settings.run.timeout,
|
|
116
|
+
logPath: defined.logPath ?? settings.run.logPath,
|
|
117
|
+
qualityLoop: defined.qualityLoop ?? settings.run.qualityLoop,
|
|
118
|
+
maxIterations: defined.maxIterations ?? settings.run.maxIterations,
|
|
119
|
+
noSmartTests: defined.noSmartTests ?? !settings.run.smartTests,
|
|
120
|
+
// Agent settings
|
|
121
|
+
isolateParallel: defined.isolateParallel ?? settings.agents.isolateParallel,
|
|
122
|
+
// Env overrides
|
|
123
|
+
...envConfig,
|
|
124
|
+
// CLI explicit options override all
|
|
125
|
+
...defined,
|
|
126
|
+
};
|
|
127
|
+
// Auto-detect phases from labels unless --phases explicitly set
|
|
128
|
+
const autoDetectPhases = !cliOptions.phases && settings.run.autoDetectPhases;
|
|
129
|
+
merged.autoDetectPhases = autoDetectPhases;
|
|
130
|
+
return merged;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Build an ExecutionConfig from merged RunOptions and settings.
|
|
134
|
+
* Extracts the phase-timeout, MCP, retry, and mode resolution logic
|
|
135
|
+
* that was previously inline in run.ts.
|
|
136
|
+
*/
|
|
137
|
+
export function buildExecutionConfig(mergedOptions, settings, issueCount) {
|
|
138
|
+
const explicitPhases = mergedOptions.phases
|
|
139
|
+
? mergedOptions.phases.split(",").map((p) => p.trim())
|
|
140
|
+
: null;
|
|
141
|
+
const mcpEnabled = mergedOptions.noMcp
|
|
142
|
+
? false
|
|
143
|
+
: (settings.run.mcp ?? DEFAULT_CONFIG.mcp);
|
|
144
|
+
const retryEnabled = mergedOptions.noRetry
|
|
145
|
+
? false
|
|
146
|
+
: (settings.run.retry ?? true);
|
|
147
|
+
const isSequential = mergedOptions.sequential ?? false;
|
|
148
|
+
const isParallel = !isSequential && issueCount > 1;
|
|
149
|
+
return {
|
|
150
|
+
...DEFAULT_CONFIG,
|
|
151
|
+
phases: explicitPhases ?? DEFAULT_PHASES,
|
|
152
|
+
sequential: isSequential,
|
|
153
|
+
concurrency: mergedOptions.concurrency ?? DEFAULT_CONFIG.concurrency,
|
|
154
|
+
parallel: isParallel,
|
|
155
|
+
dryRun: mergedOptions.dryRun ?? false,
|
|
156
|
+
verbose: mergedOptions.verbose ?? false,
|
|
157
|
+
phaseTimeout: mergedOptions.timeout ?? DEFAULT_CONFIG.phaseTimeout,
|
|
158
|
+
qualityLoop: mergedOptions.qualityLoop ?? false,
|
|
159
|
+
maxIterations: mergedOptions.maxIterations ?? DEFAULT_CONFIG.maxIterations,
|
|
160
|
+
noSmartTests: mergedOptions.noSmartTests ?? false,
|
|
161
|
+
mcp: mcpEnabled,
|
|
162
|
+
retry: retryEnabled,
|
|
163
|
+
agent: mergedOptions.agent ?? settings.run.agent,
|
|
164
|
+
aiderSettings: settings.run.aider,
|
|
165
|
+
isolateParallel: mergedOptions.isolateParallel,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Error classifier — categorizes phase failures from stderr content.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Refactored (AC-7): Returns typed SequantError instances instead of string
|
|
5
|
+
* categories. Exit codes are the primary signal; stderr patterns are secondary.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
import { SequantError } from "../errors.js";
|
|
8
|
+
/** All recognized error categories (kept for backwards compatibility). */
|
|
8
9
|
export declare const ERROR_CATEGORIES: readonly ["context_overflow", "api_error", "hook_failure", "build_error", "timeout", "unknown"];
|
|
9
10
|
export type ErrorCategory = (typeof ERROR_CATEGORIES)[number];
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
+
* Map from error type name to legacy category string.
|
|
13
|
+
* Used for backwards-compatible log storage (AC-8).
|
|
14
|
+
*/
|
|
15
|
+
export declare function errorTypeToCategory(error: SequantError): ErrorCategory;
|
|
16
|
+
/**
|
|
17
|
+
* Classify stderr lines into a typed SequantError instance (AC-7).
|
|
18
|
+
*
|
|
19
|
+
* Exit codes are the primary signal; stderr patterns are secondary.
|
|
20
|
+
* Returns a typed error instance with structured metadata.
|
|
12
21
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
22
|
+
* @param stderrLines - Lines from stderr
|
|
23
|
+
* @param exitCode - Process exit code (primary signal)
|
|
24
|
+
* @returns Typed SequantError subclass instance
|
|
15
25
|
*/
|
|
16
|
-
export declare function classifyError(stderrLines: string[]):
|
|
26
|
+
export declare function classifyError(stderrLines: string[], exitCode?: number): SequantError;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Error classifier — categorizes phase failures from stderr content.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Refactored (AC-7): Returns typed SequantError instances instead of string
|
|
5
|
+
* categories. Exit codes are the primary signal; stderr patterns are secondary.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
import { ContextOverflowError, ApiError, HookFailureError, BuildError, TimeoutError, SubprocessError, } from "../errors.js";
|
|
8
|
+
/** All recognized error categories (kept for backwards compatibility). */
|
|
8
9
|
export const ERROR_CATEGORIES = [
|
|
9
10
|
"context_overflow",
|
|
10
11
|
"api_error",
|
|
@@ -13,6 +14,26 @@ export const ERROR_CATEGORIES = [
|
|
|
13
14
|
"timeout",
|
|
14
15
|
"unknown",
|
|
15
16
|
];
|
|
17
|
+
/**
|
|
18
|
+
* Map from error type name to legacy category string.
|
|
19
|
+
* Used for backwards-compatible log storage (AC-8).
|
|
20
|
+
*/
|
|
21
|
+
export function errorTypeToCategory(error) {
|
|
22
|
+
switch (error.name) {
|
|
23
|
+
case "ContextOverflowError":
|
|
24
|
+
return "context_overflow";
|
|
25
|
+
case "ApiError":
|
|
26
|
+
return "api_error";
|
|
27
|
+
case "HookFailureError":
|
|
28
|
+
return "hook_failure";
|
|
29
|
+
case "BuildError":
|
|
30
|
+
return "build_error";
|
|
31
|
+
case "TimeoutError":
|
|
32
|
+
return "timeout";
|
|
33
|
+
default:
|
|
34
|
+
return "unknown";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
16
37
|
/**
|
|
17
38
|
* Ordered list of classifiers. First match wins (highest priority first).
|
|
18
39
|
*/
|
|
@@ -30,6 +51,16 @@ const CLASSIFIERS = [
|
|
|
30
51
|
{
|
|
31
52
|
category: "timeout",
|
|
32
53
|
patterns: [/timeout/i, /timed?\s*out/i, /SIGTERM/, /deadline exceeded/i],
|
|
54
|
+
extract: (line) => {
|
|
55
|
+
const match = line.match(/(\d+)\s*(?:s|ms|seconds?|milliseconds?)/i);
|
|
56
|
+
if (match) {
|
|
57
|
+
const value = parseInt(match[1], 10);
|
|
58
|
+
// If the unit looks like seconds (or no unit after number), convert to ms
|
|
59
|
+
const isMs = /ms|milliseconds?/i.test(match[0]);
|
|
60
|
+
return { timeoutMs: isMs ? value : value * 1000 };
|
|
61
|
+
}
|
|
62
|
+
return {};
|
|
63
|
+
},
|
|
33
64
|
},
|
|
34
65
|
{
|
|
35
66
|
category: "api_error",
|
|
@@ -43,6 +74,10 @@ const CLASSIFIERS = [
|
|
|
43
74
|
/\b502\b/,
|
|
44
75
|
/overloaded/i,
|
|
45
76
|
],
|
|
77
|
+
extract: (line) => {
|
|
78
|
+
const statusMatch = line.match(/\b(429|502|503|401|403)\b/);
|
|
79
|
+
return statusMatch ? { statusCode: parseInt(statusMatch[1], 10) } : {};
|
|
80
|
+
},
|
|
46
81
|
},
|
|
47
82
|
{
|
|
48
83
|
category: "hook_failure",
|
|
@@ -52,6 +87,10 @@ const CLASSIFIERS = [
|
|
|
52
87
|
/HOOK_BLOCKED/i,
|
|
53
88
|
/blocked by hook/i,
|
|
54
89
|
],
|
|
90
|
+
extract: (line) => {
|
|
91
|
+
const hookMatch = line.match(/(?:hook|pre-?commit|HOOK_BLOCKED)[:\s]*(.{0,50})/i);
|
|
92
|
+
return hookMatch ? { hook: hookMatch[1]?.trim() || "unknown" } : {};
|
|
93
|
+
},
|
|
55
94
|
},
|
|
56
95
|
{
|
|
57
96
|
category: "build_error",
|
|
@@ -65,26 +104,85 @@ const CLASSIFIERS = [
|
|
|
65
104
|
/eslint/i,
|
|
66
105
|
/npm ERR!/,
|
|
67
106
|
],
|
|
107
|
+
extract: (line) => {
|
|
108
|
+
if (/TS\d{4,5}:/.test(line)) {
|
|
109
|
+
const codeMatch = line.match(/(TS\d{4,5}):/);
|
|
110
|
+
return { toolchain: "tsc", errorCode: codeMatch?.[1] };
|
|
111
|
+
}
|
|
112
|
+
if (/eslint/i.test(line))
|
|
113
|
+
return { toolchain: "eslint" };
|
|
114
|
+
if (/npm ERR!/.test(line))
|
|
115
|
+
return { toolchain: "npm" };
|
|
116
|
+
return { toolchain: "unknown" };
|
|
117
|
+
},
|
|
68
118
|
},
|
|
69
119
|
];
|
|
70
120
|
/**
|
|
71
|
-
* Classify stderr lines into
|
|
121
|
+
* Classify stderr lines into a typed SequantError instance (AC-7).
|
|
72
122
|
*
|
|
73
|
-
*
|
|
74
|
-
* Returns
|
|
123
|
+
* Exit codes are the primary signal; stderr patterns are secondary.
|
|
124
|
+
* Returns a typed error instance with structured metadata.
|
|
125
|
+
*
|
|
126
|
+
* @param stderrLines - Lines from stderr
|
|
127
|
+
* @param exitCode - Process exit code (primary signal)
|
|
128
|
+
* @returns Typed SequantError subclass instance
|
|
75
129
|
*/
|
|
76
|
-
export function classifyError(stderrLines) {
|
|
77
|
-
|
|
78
|
-
|
|
130
|
+
export function classifyError(stderrLines, exitCode) {
|
|
131
|
+
const combinedStderr = stderrLines?.join(" ") ?? "";
|
|
132
|
+
// Primary signal: exit code (AC-7)
|
|
133
|
+
if (exitCode !== undefined) {
|
|
134
|
+
// 143 = SIGTERM, often timeout
|
|
135
|
+
if (exitCode === 143 || exitCode === 137) {
|
|
136
|
+
return new TimeoutError(`Process killed with signal (exit code ${exitCode})`, { timeoutMs: undefined, phase: undefined });
|
|
137
|
+
}
|
|
79
138
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
139
|
+
// Secondary signal: stderr pattern matching
|
|
140
|
+
if (stderrLines && stderrLines.length > 0) {
|
|
141
|
+
for (const { category, patterns, extract } of CLASSIFIERS) {
|
|
142
|
+
for (const line of stderrLines) {
|
|
143
|
+
for (const pattern of patterns) {
|
|
144
|
+
if (pattern.test(line)) {
|
|
145
|
+
const metadata = extract?.(line) ?? {};
|
|
146
|
+
return createErrorForCategory(category, line, metadata, exitCode);
|
|
147
|
+
}
|
|
85
148
|
}
|
|
86
149
|
}
|
|
87
150
|
}
|
|
88
151
|
}
|
|
89
|
-
|
|
152
|
+
// Fallback: SubprocessError with exit code
|
|
153
|
+
return new SubprocessError(combinedStderr || "Unknown error", {
|
|
154
|
+
exitCode,
|
|
155
|
+
command: undefined,
|
|
156
|
+
stderr: combinedStderr || undefined,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Create a typed error instance from a legacy category string.
|
|
161
|
+
*/
|
|
162
|
+
function createErrorForCategory(category, message, metadata, exitCode) {
|
|
163
|
+
switch (category) {
|
|
164
|
+
case "context_overflow":
|
|
165
|
+
return new ContextOverflowError(message, metadata);
|
|
166
|
+
case "api_error":
|
|
167
|
+
return new ApiError(message, {
|
|
168
|
+
statusCode: metadata.statusCode,
|
|
169
|
+
endpoint: metadata.endpoint,
|
|
170
|
+
});
|
|
171
|
+
case "hook_failure":
|
|
172
|
+
return new HookFailureError(message, {
|
|
173
|
+
hook: metadata.hook,
|
|
174
|
+
reason: metadata.reason,
|
|
175
|
+
});
|
|
176
|
+
case "build_error":
|
|
177
|
+
return new BuildError(message, {
|
|
178
|
+
toolchain: metadata.toolchain,
|
|
179
|
+
errorCode: metadata.errorCode,
|
|
180
|
+
});
|
|
181
|
+
case "timeout":
|
|
182
|
+
return new TimeoutError(message, {
|
|
183
|
+
timeoutMs: metadata.timeoutMs,
|
|
184
|
+
});
|
|
185
|
+
default:
|
|
186
|
+
return new SubprocessError(message, { exitCode });
|
|
187
|
+
}
|
|
90
188
|
}
|
|
@@ -11,6 +11,8 @@ import chalk from "chalk";
|
|
|
11
11
|
import { execSync } from "child_process";
|
|
12
12
|
import { readAgentsMd } from "../agents-md.js";
|
|
13
13
|
import { getDriver } from "./drivers/index.js";
|
|
14
|
+
import { classifyError } from "./error-classifier.js";
|
|
15
|
+
import { ApiError } from "../errors.js";
|
|
14
16
|
/**
|
|
15
17
|
* Natural language prompts for each phase.
|
|
16
18
|
* Claude Code invokes the corresponding skills via natural language.
|
|
@@ -490,9 +492,19 @@ delayFn = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
|
490
492
|
return lastResult;
|
|
491
493
|
}
|
|
492
494
|
// Genuine failure (took long enough to be real work) → skip cold-start retries.
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
+
// Use error classification (AC-9): if the error is retryable (e.g., API
|
|
496
|
+
// rate limit, transient 503), allow one more attempt even for genuine failures.
|
|
495
497
|
if (duration >= COLD_START_THRESHOLD_SECONDS) {
|
|
498
|
+
const typedError = classifyError(lastResult.stderrTail ?? [], lastResult.exitCode);
|
|
499
|
+
if (typedError.isRetryable && attempt < COLD_START_MAX_RETRIES) {
|
|
500
|
+
if (config.verbose) {
|
|
501
|
+
const label = typedError instanceof ApiError
|
|
502
|
+
? `API error (status ${typedError.metadata.statusCode ?? "unknown"})`
|
|
503
|
+
: typedError.name;
|
|
504
|
+
console.log(chalk.yellow(`\n ⟳ Retryable error: ${label}, retrying... (attempt ${attempt + 2}/${COLD_START_MAX_RETRIES + 1})`));
|
|
505
|
+
}
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
496
508
|
if (phase === "spec") {
|
|
497
509
|
break;
|
|
498
510
|
}
|
|
@@ -90,6 +90,9 @@ export declare const ErrorContextSchema: z.ZodObject<{
|
|
|
90
90
|
hook_failure: "hook_failure";
|
|
91
91
|
build_error: "build_error";
|
|
92
92
|
}>;
|
|
93
|
+
errorType: z.ZodOptional<z.ZodString>;
|
|
94
|
+
errorMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
95
|
+
isRetryable: z.ZodOptional<z.ZodBoolean>;
|
|
93
96
|
}, z.core.$strip>;
|
|
94
97
|
export type ErrorContext = z.infer<typeof ErrorContextSchema>;
|
|
95
98
|
/**
|
|
@@ -177,6 +180,9 @@ export declare const PhaseLogSchema: z.ZodObject<{
|
|
|
177
180
|
hook_failure: "hook_failure";
|
|
178
181
|
build_error: "build_error";
|
|
179
182
|
}>;
|
|
183
|
+
errorType: z.ZodOptional<z.ZodString>;
|
|
184
|
+
errorMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
185
|
+
isRetryable: z.ZodOptional<z.ZodBoolean>;
|
|
180
186
|
}, z.core.$strip>>;
|
|
181
187
|
}, z.core.$strip>;
|
|
182
188
|
export type PhaseLog = z.infer<typeof PhaseLogSchema>;
|
|
@@ -260,6 +266,9 @@ export declare const IssueLogSchema: z.ZodObject<{
|
|
|
260
266
|
hook_failure: "hook_failure";
|
|
261
267
|
build_error: "build_error";
|
|
262
268
|
}>;
|
|
269
|
+
errorType: z.ZodOptional<z.ZodString>;
|
|
270
|
+
errorMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
271
|
+
isRetryable: z.ZodOptional<z.ZodBoolean>;
|
|
263
272
|
}, z.core.$strip>>;
|
|
264
273
|
}, z.core.$strip>>;
|
|
265
274
|
totalDurationSeconds: z.ZodNumber;
|
|
@@ -404,6 +413,9 @@ export declare const RunLogSchema: z.ZodObject<{
|
|
|
404
413
|
hook_failure: "hook_failure";
|
|
405
414
|
build_error: "build_error";
|
|
406
415
|
}>;
|
|
416
|
+
errorType: z.ZodOptional<z.ZodString>;
|
|
417
|
+
errorMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
418
|
+
isRetryable: z.ZodOptional<z.ZodBoolean>;
|
|
407
419
|
}, z.core.$strip>>;
|
|
408
420
|
}, z.core.$strip>>;
|
|
409
421
|
totalDurationSeconds: z.ZodNumber;
|
|
@@ -79,7 +79,7 @@ export const ErrorContextSchema = z.object({
|
|
|
79
79
|
stdoutTail: z.array(z.string()),
|
|
80
80
|
/** Process exit code */
|
|
81
81
|
exitCode: z.number().int().optional(),
|
|
82
|
-
/** Classified error category */
|
|
82
|
+
/** Classified error category (legacy, kept for backwards compatibility) */
|
|
83
83
|
category: z.enum([
|
|
84
84
|
"context_overflow",
|
|
85
85
|
"api_error",
|
|
@@ -88,6 +88,12 @@ export const ErrorContextSchema = z.object({
|
|
|
88
88
|
"timeout",
|
|
89
89
|
"unknown",
|
|
90
90
|
]),
|
|
91
|
+
/** Typed error class name (AC-8), e.g. "ApiError", "BuildError" */
|
|
92
|
+
errorType: z.string().optional(),
|
|
93
|
+
/** Structured error metadata (AC-8) */
|
|
94
|
+
errorMetadata: z.record(z.string(), z.unknown()).optional(),
|
|
95
|
+
/** Whether this error type is retryable (AC-9) */
|
|
96
|
+
isRetryable: z.boolean().optional(),
|
|
91
97
|
});
|
|
92
98
|
/**
|
|
93
99
|
* Condensed QA verdict summary for structured log output (#434).
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RunOrchestrator — CLI-free execution engine for sequant workflows.
|
|
3
|
+
*
|
|
4
|
+
* Owns the full lifecycle: config → issue discovery → dispatch → results.
|
|
5
|
+
* Importable and usable without Commander.js or CLI context.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { ExecutionConfig, IssueResult, RunOptions, ProgressCallback } from "./types.js";
|
|
10
|
+
import type { WorktreeInfo } from "./worktree-manager.js";
|
|
11
|
+
import { LogWriter } from "./log-writer.js";
|
|
12
|
+
import { StateManager } from "./state-manager.js";
|
|
13
|
+
import { ShutdownManager } from "../shutdown.js";
|
|
14
|
+
import type { SequantSettings } from "../settings.js";
|
|
15
|
+
/**
|
|
16
|
+
* Injectable services for RunOrchestrator.
|
|
17
|
+
* All optional — orchestrator degrades gracefully when services are absent.
|
|
18
|
+
*/
|
|
19
|
+
export interface OrchestratorServices {
|
|
20
|
+
logWriter?: LogWriter | null;
|
|
21
|
+
stateManager?: StateManager | null;
|
|
22
|
+
shutdownManager?: ShutdownManager;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* CLI-free configuration for RunOrchestrator.
|
|
26
|
+
* No Commander.js types leak into this interface.
|
|
27
|
+
*/
|
|
28
|
+
export interface OrchestratorConfig {
|
|
29
|
+
/** Execution settings (phases, timeouts, mode flags) */
|
|
30
|
+
config: ExecutionConfig;
|
|
31
|
+
/** Merged run options (post-resolution, no raw CLI types) */
|
|
32
|
+
options: RunOptions;
|
|
33
|
+
/** Issue metadata keyed by issue number */
|
|
34
|
+
issueInfoMap: Map<number, {
|
|
35
|
+
title: string;
|
|
36
|
+
labels: string[];
|
|
37
|
+
}>;
|
|
38
|
+
/** Worktree paths keyed by issue number */
|
|
39
|
+
worktreeMap: Map<number, WorktreeInfo>;
|
|
40
|
+
/** Injectable services */
|
|
41
|
+
services: OrchestratorServices;
|
|
42
|
+
/** Package manager name (e.g. "npm", "pnpm") */
|
|
43
|
+
packageManager?: string;
|
|
44
|
+
/** Base branch for rebase/PR targets */
|
|
45
|
+
baseBranch?: string;
|
|
46
|
+
/** Per-phase progress callback (parallel mode) */
|
|
47
|
+
onProgress?: ProgressCallback;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* High-level init config for full lifecycle execution.
|
|
51
|
+
* Used by RunOrchestrator.run() — the entry point for programmatic callers.
|
|
52
|
+
*/
|
|
53
|
+
export interface RunInit {
|
|
54
|
+
/** Raw CLI options (pre-merge) */
|
|
55
|
+
options: RunOptions;
|
|
56
|
+
/** Resolved settings */
|
|
57
|
+
settings: SequantSettings;
|
|
58
|
+
/** Manifest metadata */
|
|
59
|
+
manifest: {
|
|
60
|
+
stack: string;
|
|
61
|
+
packageManager: string;
|
|
62
|
+
};
|
|
63
|
+
/** Explicit base branch override */
|
|
64
|
+
baseBranch?: string;
|
|
65
|
+
/** Per-phase progress callback */
|
|
66
|
+
onProgress?: ProgressCallback;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Structured result of a full orchestrator run.
|
|
70
|
+
*/
|
|
71
|
+
export interface RunResult {
|
|
72
|
+
/** Per-issue results */
|
|
73
|
+
results: IssueResult[];
|
|
74
|
+
/** Log file path (if logging enabled) */
|
|
75
|
+
logPath: string | null;
|
|
76
|
+
/** Non-zero if any issue failed */
|
|
77
|
+
exitCode: number;
|
|
78
|
+
/** Worktree map (for summary display) */
|
|
79
|
+
worktreeMap: Map<number, WorktreeInfo>;
|
|
80
|
+
/** Issue info map (for summary display) */
|
|
81
|
+
issueInfoMap: Map<number, {
|
|
82
|
+
title: string;
|
|
83
|
+
labels: string[];
|
|
84
|
+
}>;
|
|
85
|
+
/** Resolved execution config */
|
|
86
|
+
config: ExecutionConfig;
|
|
87
|
+
/** Resolved merged options */
|
|
88
|
+
mergedOptions: RunOptions;
|
|
89
|
+
/** Log writer (for reflection access) */
|
|
90
|
+
logWriter: LogWriter | null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* CLI-free workflow execution engine.
|
|
94
|
+
*
|
|
95
|
+
* Two usage modes:
|
|
96
|
+
* 1. Full lifecycle: `RunOrchestrator.run(init, issueNumbers)` — handles
|
|
97
|
+
* services, worktrees, state guard, execution, and metrics.
|
|
98
|
+
* 2. Low-level: `new RunOrchestrator(config).execute(issueNumbers)` — caller
|
|
99
|
+
* manages setup/teardown.
|
|
100
|
+
*/
|
|
101
|
+
export declare class RunOrchestrator {
|
|
102
|
+
private readonly cfg;
|
|
103
|
+
constructor(config: OrchestratorConfig);
|
|
104
|
+
/**
|
|
105
|
+
* Full lifecycle execution — the primary entry point for programmatic use.
|
|
106
|
+
*
|
|
107
|
+
* Handles: config resolution → services setup → state guard →
|
|
108
|
+
* issue discovery → worktree creation → execution → metrics → cleanup.
|
|
109
|
+
*/
|
|
110
|
+
static run(init: RunInit, issueArgs: string[], batches?: number[][] | null): Promise<RunResult>;
|
|
111
|
+
/**
|
|
112
|
+
* Execute workflow for the given issue numbers.
|
|
113
|
+
* Returns one IssueResult per issue.
|
|
114
|
+
*/
|
|
115
|
+
execute(issueNumbers: number[]): Promise<IssueResult[]>;
|
|
116
|
+
private validate;
|
|
117
|
+
private buildBatchContext;
|
|
118
|
+
private executeSequential;
|
|
119
|
+
private executeParallel;
|
|
120
|
+
private executeOneIssue;
|
|
121
|
+
private static recordMetrics;
|
|
122
|
+
}
|
|
123
|
+
/** Log a non-fatal warning: one-line summary always, detail in verbose. */
|
|
124
|
+
export declare function logNonFatalWarning(message: string, error: unknown, verbose: boolean): void;
|