sequant 1.20.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -4
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +36 -15
- package/dist/bin/cli.js +25 -2
- package/dist/src/commands/doctor.js +42 -9
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.js +52 -0
- package/dist/src/commands/logs.d.ts +1 -0
- package/dist/src/commands/logs.js +18 -2
- package/dist/src/commands/run.d.ts +7 -0
- package/dist/src/commands/run.js +235 -68
- package/dist/src/commands/serve.d.ts +13 -0
- package/dist/src/commands/serve.js +131 -0
- package/dist/src/commands/stats.d.ts +1 -0
- package/dist/src/commands/stats.js +185 -26
- package/dist/src/commands/status.d.ts +2 -0
- package/dist/src/commands/status.js +99 -50
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +4 -1
- package/dist/src/lib/ac-parser.d.ts +2 -0
- package/dist/src/lib/ac-parser.js +12 -2
- package/dist/src/lib/assess-comment-parser.d.ts +137 -0
- package/dist/src/lib/assess-comment-parser.js +344 -0
- package/dist/src/lib/ci/config.d.ts +22 -0
- package/dist/src/lib/ci/config.js +134 -0
- package/dist/src/lib/ci/index.d.ts +12 -0
- package/dist/src/lib/ci/index.js +10 -0
- package/dist/src/lib/ci/inputs.d.ts +29 -0
- package/dist/src/lib/ci/inputs.js +103 -0
- package/dist/src/lib/ci/labels.d.ts +34 -0
- package/dist/src/lib/ci/labels.js +101 -0
- package/dist/src/lib/ci/outputs.d.ts +25 -0
- package/dist/src/lib/ci/outputs.js +84 -0
- package/dist/src/lib/ci/triggers.d.ts +9 -0
- package/dist/src/lib/ci/triggers.js +86 -0
- package/dist/src/lib/ci/types.d.ts +131 -0
- package/dist/src/lib/ci/types.js +47 -0
- package/dist/src/lib/mcp-config.d.ts +54 -0
- package/dist/src/lib/mcp-config.js +172 -0
- package/dist/src/lib/merge-check/index.js +6 -12
- package/dist/src/lib/merge-check/types.d.ts +20 -7
- package/dist/src/lib/merge-check/types.js +11 -0
- package/dist/src/lib/phase-signal.d.ts +3 -3
- package/dist/src/lib/phase-signal.js +5 -3
- package/dist/src/lib/settings.d.ts +52 -0
- package/dist/src/lib/settings.js +41 -0
- package/dist/src/lib/shutdown.d.ts +16 -5
- package/dist/src/lib/shutdown.js +32 -12
- package/dist/src/lib/solve-comment-parser.d.ts +9 -102
- package/dist/src/lib/solve-comment-parser.js +13 -248
- package/dist/src/lib/stacks.d.ts +8 -0
- package/dist/src/lib/stacks.js +34 -0
- package/dist/src/lib/system.js +3 -7
- package/dist/src/lib/test-tautology-detector.d.ts +10 -0
- package/dist/src/lib/test-tautology-detector.js +43 -4
- package/dist/src/lib/upstream/assessment.js +9 -59
- package/dist/src/lib/upstream/issues.js +12 -75
- package/dist/src/lib/version-check.d.ts +2 -2
- package/dist/src/lib/version-check.js +6 -3
- package/dist/src/lib/version.d.ts +4 -0
- package/dist/src/lib/version.js +25 -0
- package/dist/src/lib/workflow/batch-executor.d.ts +26 -86
- package/dist/src/lib/workflow/batch-executor.js +269 -55
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +56 -0
- package/dist/src/lib/workflow/drivers/agent-driver.js +8 -0
- package/dist/src/lib/workflow/drivers/aider.d.ts +18 -0
- package/dist/src/lib/workflow/drivers/aider.js +160 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +17 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +165 -0
- package/dist/src/lib/workflow/drivers/index.d.ts +20 -0
- package/dist/src/lib/workflow/drivers/index.js +27 -0
- package/dist/src/lib/workflow/error-classifier.d.ts +16 -0
- package/dist/src/lib/workflow/error-classifier.js +90 -0
- package/dist/src/lib/workflow/log-writer.d.ts +6 -3
- package/dist/src/lib/workflow/log-writer.js +57 -27
- package/dist/src/lib/workflow/metrics-schema.d.ts +9 -9
- package/dist/src/lib/workflow/phase-detection.d.ts +23 -0
- package/dist/src/lib/workflow/phase-detection.js +45 -29
- package/dist/src/lib/workflow/phase-executor.d.ts +42 -3
- package/dist/src/lib/workflow/phase-executor.js +375 -229
- package/dist/src/lib/workflow/phase-mapper.d.ts +1 -1
- package/dist/src/lib/workflow/phase-mapper.js +7 -7
- package/dist/src/lib/workflow/platforms/github.d.ts +157 -0
- package/dist/src/lib/workflow/platforms/github.js +466 -0
- package/dist/src/lib/workflow/platforms/index.d.ts +17 -0
- package/dist/src/lib/workflow/platforms/index.js +25 -0
- package/dist/src/lib/workflow/platforms/platform-provider.d.ts +67 -0
- package/dist/src/lib/workflow/platforms/platform-provider.js +8 -0
- package/dist/src/lib/workflow/pr-status.d.ts +2 -4
- package/dist/src/lib/workflow/pr-status.js +3 -16
- package/dist/src/lib/workflow/qa-cache.d.ts +58 -0
- package/dist/src/lib/workflow/qa-cache.js +88 -0
- package/dist/src/lib/workflow/reconcile.d.ts +69 -0
- package/dist/src/lib/workflow/reconcile.js +290 -0
- package/dist/src/lib/workflow/ring-buffer.d.ts +17 -0
- package/dist/src/lib/workflow/ring-buffer.js +37 -0
- package/dist/src/lib/workflow/run-log-schema.d.ts +115 -24
- package/dist/src/lib/workflow/run-log-schema.js +47 -12
- package/dist/src/lib/workflow/run-reflect.js +1 -1
- package/dist/src/lib/workflow/state-cleanup.js +21 -0
- package/dist/src/lib/workflow/state-manager.d.ts +34 -3
- package/dist/src/lib/workflow/state-manager.js +278 -126
- package/dist/src/lib/workflow/state-schema.d.ts +34 -30
- package/dist/src/lib/workflow/state-schema.js +35 -25
- package/dist/src/lib/workflow/state-utils.d.ts +3 -1
- package/dist/src/lib/workflow/state-utils.js +1 -0
- package/dist/src/lib/workflow/types.d.ts +224 -6
- package/dist/src/lib/workflow/types.js +20 -1
- package/dist/src/lib/workflow/worktree-discovery.d.ts +1 -1
- package/dist/src/lib/workflow/worktree-discovery.js +6 -14
- package/dist/src/lib/workflow/worktree-manager.js +33 -51
- package/dist/src/mcp/index.d.ts +4 -0
- package/dist/src/mcp/index.js +4 -0
- package/dist/src/mcp/resources.d.ts +7 -0
- package/dist/src/mcp/resources.js +111 -0
- package/dist/src/mcp/run-registry.d.ts +34 -0
- package/dist/src/mcp/run-registry.js +42 -0
- package/dist/src/mcp/server.d.ts +12 -0
- package/dist/src/mcp/server.js +50 -0
- package/dist/src/mcp/tools/logs.d.ts +7 -0
- package/dist/src/mcp/tools/logs.js +149 -0
- package/dist/src/mcp/tools/run.d.ts +121 -0
- package/dist/src/mcp/tools/run.js +591 -0
- package/dist/src/mcp/tools/status.d.ts +7 -0
- package/dist/src/mcp/tools/status.js +127 -0
- package/package.json +26 -7
- package/templates/hooks/post-tool.sh +19 -8
- package/templates/hooks/pre-tool.sh +36 -49
- package/templates/mcp.json +6 -0
- package/templates/skills/assess/SKILL.md +354 -352
- package/templates/skills/exec/SKILL.md +64 -1
- package/templates/skills/fullsolve/SKILL.md +35 -4
- package/templates/skills/qa/SKILL.md +486 -9
- package/templates/skills/qa/scripts/quality-checks.sh +1 -1
- package/templates/skills/setup/SKILL.md +386 -0
- package/templates/skills/solve/SKILL.md +38 -664
- package/templates/skills/spec/SKILL.md +90 -31
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AiderDriver — AgentDriver implementation wrapping the Aider CLI.
|
|
3
|
+
*
|
|
4
|
+
* Shells out to `aider --yes --no-auto-commits --message "<prompt>"`
|
|
5
|
+
* for fully non-interactive phase execution. Sequant manages git,
|
|
6
|
+
* not Aider.
|
|
7
|
+
*/
|
|
8
|
+
import { spawn } from "child_process";
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
import { RingBuffer } from "../ring-buffer.js";
|
|
11
|
+
export class AiderDriver {
|
|
12
|
+
name = "aider";
|
|
13
|
+
settings;
|
|
14
|
+
constructor(settings) {
|
|
15
|
+
this.settings = settings;
|
|
16
|
+
}
|
|
17
|
+
async executePhase(prompt, config) {
|
|
18
|
+
const args = this.buildArgs(prompt, config.files);
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
let capturedOutput = "";
|
|
21
|
+
let capturedStderr = "";
|
|
22
|
+
const stderrBuffer = new RingBuffer(50);
|
|
23
|
+
const stdoutBuffer = new RingBuffer(50);
|
|
24
|
+
const proc = spawn("aider", args, {
|
|
25
|
+
cwd: config.cwd,
|
|
26
|
+
env: { ...process.env, ...config.env },
|
|
27
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
28
|
+
});
|
|
29
|
+
// Set up timeout
|
|
30
|
+
const timeoutId = setTimeout(() => {
|
|
31
|
+
proc.kill("SIGTERM");
|
|
32
|
+
}, config.phaseTimeout * 1000);
|
|
33
|
+
// Wire external abort signal
|
|
34
|
+
if (config.abortSignal) {
|
|
35
|
+
const onAbort = () => {
|
|
36
|
+
proc.kill("SIGTERM");
|
|
37
|
+
};
|
|
38
|
+
config.abortSignal.addEventListener("abort", onAbort);
|
|
39
|
+
proc.on("close", () => {
|
|
40
|
+
config.abortSignal?.removeEventListener("abort", onAbort);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
proc.stdout.on("data", (data) => {
|
|
44
|
+
const text = data.toString();
|
|
45
|
+
capturedOutput += text;
|
|
46
|
+
const lines = text.split("\n").filter((l) => l.length > 0);
|
|
47
|
+
for (const line of lines) {
|
|
48
|
+
stdoutBuffer.push(line);
|
|
49
|
+
}
|
|
50
|
+
if (config.verbose) {
|
|
51
|
+
config.onOutput?.(text);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
proc.stderr.on("data", (data) => {
|
|
55
|
+
const text = data.toString();
|
|
56
|
+
capturedStderr += text;
|
|
57
|
+
const lines = text.split("\n").filter((l) => l.length > 0);
|
|
58
|
+
for (const line of lines) {
|
|
59
|
+
stderrBuffer.push(line);
|
|
60
|
+
}
|
|
61
|
+
config.onStderr?.(text);
|
|
62
|
+
});
|
|
63
|
+
proc.on("error", (err) => {
|
|
64
|
+
clearTimeout(timeoutId);
|
|
65
|
+
if (err.code === "ENOENT") {
|
|
66
|
+
resolve({
|
|
67
|
+
success: false,
|
|
68
|
+
output: capturedOutput,
|
|
69
|
+
error: "Aider CLI not found. Install it with: pip install aider-chat",
|
|
70
|
+
stderrTail: stderrBuffer.getLines(),
|
|
71
|
+
stdoutTail: stdoutBuffer.getLines(),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
resolve({
|
|
76
|
+
success: false,
|
|
77
|
+
output: capturedOutput,
|
|
78
|
+
error: `Failed to start aider: ${err.message}`,
|
|
79
|
+
stderrTail: stderrBuffer.getLines(),
|
|
80
|
+
stdoutTail: stdoutBuffer.getLines(),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
proc.on("close", (code, signal) => {
|
|
85
|
+
clearTimeout(timeoutId);
|
|
86
|
+
if (signal) {
|
|
87
|
+
const isTimeout = signal === "SIGTERM";
|
|
88
|
+
resolve({
|
|
89
|
+
success: false,
|
|
90
|
+
output: capturedOutput,
|
|
91
|
+
error: isTimeout
|
|
92
|
+
? `Timeout after ${config.phaseTimeout}s`
|
|
93
|
+
: `Process killed by signal: ${signal}`,
|
|
94
|
+
stderrTail: stderrBuffer.getLines(),
|
|
95
|
+
stdoutTail: stdoutBuffer.getLines(),
|
|
96
|
+
exitCode: code ?? undefined,
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (code === 0) {
|
|
101
|
+
resolve({
|
|
102
|
+
success: true,
|
|
103
|
+
output: capturedOutput,
|
|
104
|
+
stderrTail: stderrBuffer.getLines(),
|
|
105
|
+
stdoutTail: stdoutBuffer.getLines(),
|
|
106
|
+
exitCode: 0,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const stderrSuffix = capturedStderr
|
|
111
|
+
? `\nStderr: ${capturedStderr.slice(0, 500)}`
|
|
112
|
+
: "";
|
|
113
|
+
resolve({
|
|
114
|
+
success: false,
|
|
115
|
+
output: capturedOutput,
|
|
116
|
+
error: `Aider exited with code ${code}${stderrSuffix}`,
|
|
117
|
+
stderrTail: stderrBuffer.getLines(),
|
|
118
|
+
stdoutTail: stdoutBuffer.getLines(),
|
|
119
|
+
exitCode: code ?? undefined,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async isAvailable() {
|
|
126
|
+
try {
|
|
127
|
+
execSync("which aider", { stdio: "pipe" });
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** Build the CLI argument list for aider. */
|
|
135
|
+
buildArgs(prompt, files) {
|
|
136
|
+
const args = [
|
|
137
|
+
"--yes",
|
|
138
|
+
"--no-auto-commits",
|
|
139
|
+
"--no-pretty",
|
|
140
|
+
"--message",
|
|
141
|
+
prompt,
|
|
142
|
+
];
|
|
143
|
+
if (this.settings?.model) {
|
|
144
|
+
args.push("--model", this.settings.model);
|
|
145
|
+
}
|
|
146
|
+
if (this.settings?.editFormat) {
|
|
147
|
+
args.push("--edit-format", this.settings.editFormat);
|
|
148
|
+
}
|
|
149
|
+
if (this.settings?.extraArgs) {
|
|
150
|
+
args.push(...this.settings.extraArgs);
|
|
151
|
+
}
|
|
152
|
+
// Pass relevant files for context (e.g., changed files from git diff)
|
|
153
|
+
if (files?.length) {
|
|
154
|
+
for (const file of files) {
|
|
155
|
+
args.push("--file", file);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return args;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeCodeDriver — AgentDriver implementation wrapping the Claude Agent SDK.
|
|
3
|
+
*
|
|
4
|
+
* Owns the `@anthropic-ai/claude-agent-sdk` import. No other file in the
|
|
5
|
+
* orchestration layer should import the SDK directly.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentDriver, AgentExecutionConfig, AgentPhaseResult } from "./agent-driver.js";
|
|
8
|
+
export declare class ClaudeCodeDriver implements AgentDriver {
|
|
9
|
+
name: string;
|
|
10
|
+
/**
|
|
11
|
+
* Track session ID across calls so callers can implement resume.
|
|
12
|
+
* Set after each executePhase() call.
|
|
13
|
+
*/
|
|
14
|
+
private lastSessionId?;
|
|
15
|
+
executePhase(prompt: string, config: AgentExecutionConfig): Promise<AgentPhaseResult>;
|
|
16
|
+
isAvailable(): Promise<boolean>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeCodeDriver — AgentDriver implementation wrapping the Claude Agent SDK.
|
|
3
|
+
*
|
|
4
|
+
* Owns the `@anthropic-ai/claude-agent-sdk` import. No other file in the
|
|
5
|
+
* orchestration layer should import the SDK directly.
|
|
6
|
+
*/
|
|
7
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
8
|
+
import { getMcpServersConfig } from "../../system.js";
|
|
9
|
+
import { RingBuffer } from "../ring-buffer.js";
|
|
10
|
+
export class ClaudeCodeDriver {
|
|
11
|
+
name = "claude-code";
|
|
12
|
+
/**
|
|
13
|
+
* Track session ID across calls so callers can implement resume.
|
|
14
|
+
* Set after each executePhase() call.
|
|
15
|
+
*/
|
|
16
|
+
lastSessionId;
|
|
17
|
+
async executePhase(prompt, config) {
|
|
18
|
+
const abortController = new AbortController();
|
|
19
|
+
// Wire external abort signal
|
|
20
|
+
if (config.abortSignal) {
|
|
21
|
+
config.abortSignal.addEventListener("abort", () => abortController.abort());
|
|
22
|
+
}
|
|
23
|
+
// Set up timeout
|
|
24
|
+
const timeoutId = setTimeout(() => {
|
|
25
|
+
abortController.abort();
|
|
26
|
+
}, config.phaseTimeout * 1000);
|
|
27
|
+
let resultSessionId;
|
|
28
|
+
let resultMessage;
|
|
29
|
+
let capturedOutput = "";
|
|
30
|
+
let capturedStderr = "";
|
|
31
|
+
const stderrBuffer = new RingBuffer(50);
|
|
32
|
+
const stdoutBuffer = new RingBuffer(50);
|
|
33
|
+
try {
|
|
34
|
+
// Get MCP servers config if enabled
|
|
35
|
+
const mcpServers = config.mcp ? getMcpServersConfig() : undefined;
|
|
36
|
+
const queryInstance = query({
|
|
37
|
+
prompt,
|
|
38
|
+
options: {
|
|
39
|
+
abortController,
|
|
40
|
+
cwd: config.cwd,
|
|
41
|
+
settingSources: ["project"],
|
|
42
|
+
systemPrompt: { type: "preset", preset: "claude_code" },
|
|
43
|
+
tools: { type: "preset", preset: "claude_code" },
|
|
44
|
+
permissionMode: "bypassPermissions",
|
|
45
|
+
allowDangerouslySkipPermissions: true,
|
|
46
|
+
// Resume from previous session if provided
|
|
47
|
+
...(config.sessionId ? { resume: config.sessionId } : {}),
|
|
48
|
+
env: config.env,
|
|
49
|
+
...(mcpServers ? { mcpServers } : {}),
|
|
50
|
+
stderr: (data) => {
|
|
51
|
+
capturedStderr += data;
|
|
52
|
+
// Split on newlines and push each line to the ring buffer
|
|
53
|
+
const lines = data.split("\n").filter((l) => l.length > 0);
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
stderrBuffer.push(line);
|
|
56
|
+
}
|
|
57
|
+
config.onStderr?.(data);
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
// Stream and process messages
|
|
62
|
+
for await (const message of queryInstance) {
|
|
63
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
64
|
+
resultSessionId = message.session_id;
|
|
65
|
+
}
|
|
66
|
+
if (message.type === "assistant") {
|
|
67
|
+
const content = message.message.content;
|
|
68
|
+
const textContent = content
|
|
69
|
+
.filter((c) => c.type === "text" && c.text)
|
|
70
|
+
.map((c) => c.text)
|
|
71
|
+
.join("");
|
|
72
|
+
if (textContent) {
|
|
73
|
+
capturedOutput += textContent;
|
|
74
|
+
const lines = textContent.split("\n").filter((l) => l.length > 0);
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
stdoutBuffer.push(line);
|
|
77
|
+
}
|
|
78
|
+
config.onOutput?.(textContent);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (message.type === "result") {
|
|
82
|
+
resultMessage = message;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
clearTimeout(timeoutId);
|
|
86
|
+
this.lastSessionId = resultSessionId;
|
|
87
|
+
if (resultMessage) {
|
|
88
|
+
if (resultMessage.subtype === "success") {
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
output: capturedOutput,
|
|
92
|
+
sessionId: resultSessionId,
|
|
93
|
+
stderrTail: stderrBuffer.getLines(),
|
|
94
|
+
stdoutTail: stdoutBuffer.getLines(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// Handle error subtypes
|
|
98
|
+
let error;
|
|
99
|
+
const errorSubtype = resultMessage.subtype;
|
|
100
|
+
if (errorSubtype === "error_max_turns") {
|
|
101
|
+
error = "Max turns reached";
|
|
102
|
+
}
|
|
103
|
+
else if (errorSubtype === "error_during_execution") {
|
|
104
|
+
error = resultMessage.errors?.join(", ") || "Error during execution";
|
|
105
|
+
}
|
|
106
|
+
else if (errorSubtype === "error_max_budget_usd") {
|
|
107
|
+
error = "Budget limit exceeded";
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
error = `Error: ${errorSubtype}`;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
output: capturedOutput,
|
|
115
|
+
sessionId: resultSessionId,
|
|
116
|
+
error,
|
|
117
|
+
stderrTail: stderrBuffer.getLines(),
|
|
118
|
+
stdoutTail: stdoutBuffer.getLines(),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
output: capturedOutput,
|
|
124
|
+
sessionId: resultSessionId,
|
|
125
|
+
error: "No result received from Claude",
|
|
126
|
+
stderrTail: stderrBuffer.getLines(),
|
|
127
|
+
stdoutTail: stdoutBuffer.getLines(),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
clearTimeout(timeoutId);
|
|
132
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
133
|
+
if (error.includes("abort") || error.includes("AbortError")) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
output: capturedOutput,
|
|
137
|
+
error: `Timeout after ${config.phaseTimeout}s`,
|
|
138
|
+
stderrTail: stderrBuffer.getLines(),
|
|
139
|
+
stdoutTail: stdoutBuffer.getLines(),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const stderrSuffix = capturedStderr
|
|
143
|
+
? `\nStderr: ${capturedStderr.slice(0, 500)}`
|
|
144
|
+
: "";
|
|
145
|
+
return {
|
|
146
|
+
success: false,
|
|
147
|
+
output: capturedOutput,
|
|
148
|
+
sessionId: resultSessionId,
|
|
149
|
+
error: error + stderrSuffix,
|
|
150
|
+
stderrTail: stderrBuffer.getLines(),
|
|
151
|
+
stdoutTail: stdoutBuffer.getLines(),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async isAvailable() {
|
|
156
|
+
try {
|
|
157
|
+
// If we can import the SDK, it's available
|
|
158
|
+
await import("@anthropic-ai/claude-agent-sdk");
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent driver registry.
|
|
3
|
+
*
|
|
4
|
+
* Simple map-based registry — not a plugin system.
|
|
5
|
+
* New drivers are registered by adding entries to DRIVERS.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentDriver } from "./agent-driver.js";
|
|
8
|
+
import type { AiderSettings } from "../../settings.js";
|
|
9
|
+
export type { AgentDriver, AgentExecutionConfig, AgentPhaseResult, } from "./agent-driver.js";
|
|
10
|
+
export interface DriverOptions {
|
|
11
|
+
aiderSettings?: AiderSettings;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get an agent driver by name.
|
|
15
|
+
*
|
|
16
|
+
* @param name - Driver name (default: "claude-code")
|
|
17
|
+
* @param options - Optional driver-specific settings
|
|
18
|
+
* @throws Error if driver name is unknown
|
|
19
|
+
*/
|
|
20
|
+
export declare function getDriver(name?: string, options?: DriverOptions): AgentDriver;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent driver registry.
|
|
3
|
+
*
|
|
4
|
+
* Simple map-based registry — not a plugin system.
|
|
5
|
+
* New drivers are registered by adding entries to DRIVERS.
|
|
6
|
+
*/
|
|
7
|
+
import { ClaudeCodeDriver } from "./claude-code.js";
|
|
8
|
+
import { AiderDriver } from "./aider.js";
|
|
9
|
+
const DRIVERS = {
|
|
10
|
+
"claude-code": () => new ClaudeCodeDriver(),
|
|
11
|
+
aider: (opts) => new AiderDriver(opts?.aiderSettings),
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Get an agent driver by name.
|
|
15
|
+
*
|
|
16
|
+
* @param name - Driver name (default: "claude-code")
|
|
17
|
+
* @param options - Optional driver-specific settings
|
|
18
|
+
* @throws Error if driver name is unknown
|
|
19
|
+
*/
|
|
20
|
+
export function getDriver(name = "claude-code", options) {
|
|
21
|
+
const factory = DRIVERS[name];
|
|
22
|
+
if (!factory) {
|
|
23
|
+
const available = Object.keys(DRIVERS).join(", ");
|
|
24
|
+
throw new Error(`Unknown agent driver "${name}". Available drivers: ${available}`);
|
|
25
|
+
}
|
|
26
|
+
return factory(options);
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classifier — categorizes phase failures from stderr content.
|
|
3
|
+
*
|
|
4
|
+
* Pattern-matches stderr lines against known error signatures to produce
|
|
5
|
+
* a structured category for analytics and debugging.
|
|
6
|
+
*/
|
|
7
|
+
/** All recognized error categories (AC-7: defined as constants). */
|
|
8
|
+
export declare const ERROR_CATEGORIES: readonly ["context_overflow", "api_error", "hook_failure", "build_error", "timeout", "unknown"];
|
|
9
|
+
export type ErrorCategory = (typeof ERROR_CATEGORIES)[number];
|
|
10
|
+
/**
|
|
11
|
+
* Classify stderr lines into an error category.
|
|
12
|
+
*
|
|
13
|
+
* Scans lines in order; the first classifier whose pattern matches any line wins.
|
|
14
|
+
* Returns "unknown" if no patterns match.
|
|
15
|
+
*/
|
|
16
|
+
export declare function classifyError(stderrLines: string[]): ErrorCategory;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classifier — categorizes phase failures from stderr content.
|
|
3
|
+
*
|
|
4
|
+
* Pattern-matches stderr lines against known error signatures to produce
|
|
5
|
+
* a structured category for analytics and debugging.
|
|
6
|
+
*/
|
|
7
|
+
/** All recognized error categories (AC-7: defined as constants). */
|
|
8
|
+
export const ERROR_CATEGORIES = [
|
|
9
|
+
"context_overflow",
|
|
10
|
+
"api_error",
|
|
11
|
+
"hook_failure",
|
|
12
|
+
"build_error",
|
|
13
|
+
"timeout",
|
|
14
|
+
"unknown",
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Ordered list of classifiers. First match wins (highest priority first).
|
|
18
|
+
*/
|
|
19
|
+
const CLASSIFIERS = [
|
|
20
|
+
{
|
|
21
|
+
category: "context_overflow",
|
|
22
|
+
patterns: [
|
|
23
|
+
/context window/i,
|
|
24
|
+
/token limit/i,
|
|
25
|
+
/context length/i,
|
|
26
|
+
/max.?context/i,
|
|
27
|
+
/exceeded.*context/i,
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
category: "timeout",
|
|
32
|
+
patterns: [/timeout/i, /timed?\s*out/i, /SIGTERM/, /deadline exceeded/i],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
category: "api_error",
|
|
36
|
+
patterns: [
|
|
37
|
+
/rate.?limit/i,
|
|
38
|
+
/\b429\b/,
|
|
39
|
+
/api.*error/i,
|
|
40
|
+
/auth.*fail/i,
|
|
41
|
+
/unauthorized/i,
|
|
42
|
+
/\b503\b/,
|
|
43
|
+
/\b502\b/,
|
|
44
|
+
/overloaded/i,
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
category: "hook_failure",
|
|
49
|
+
patterns: [
|
|
50
|
+
/hook.*fail/i,
|
|
51
|
+
/pre-?commit/i,
|
|
52
|
+
/HOOK_BLOCKED/i,
|
|
53
|
+
/blocked by hook/i,
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
category: "build_error",
|
|
58
|
+
patterns: [
|
|
59
|
+
/typescript.*error/i,
|
|
60
|
+
/TS\d{4,5}:/,
|
|
61
|
+
/syntax\s*error/i,
|
|
62
|
+
/cannot find module/i,
|
|
63
|
+
/compilation.*fail/i,
|
|
64
|
+
/build.*fail/i,
|
|
65
|
+
/eslint/i,
|
|
66
|
+
/npm ERR!/,
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
/**
|
|
71
|
+
* Classify stderr lines into an error category.
|
|
72
|
+
*
|
|
73
|
+
* Scans lines in order; the first classifier whose pattern matches any line wins.
|
|
74
|
+
* Returns "unknown" if no patterns match.
|
|
75
|
+
*/
|
|
76
|
+
export function classifyError(stderrLines) {
|
|
77
|
+
if (!stderrLines || stderrLines.length === 0) {
|
|
78
|
+
return "unknown";
|
|
79
|
+
}
|
|
80
|
+
for (const { category, patterns } of CLASSIFIERS) {
|
|
81
|
+
for (const line of stderrLines) {
|
|
82
|
+
for (const pattern of patterns) {
|
|
83
|
+
if (pattern.test(line)) {
|
|
84
|
+
return category;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return "unknown";
|
|
90
|
+
}
|
|
@@ -32,6 +32,9 @@ export interface LogWriterOptions {
|
|
|
32
32
|
*/
|
|
33
33
|
export declare class LogWriter {
|
|
34
34
|
private runLog;
|
|
35
|
+
/** Active issues being tracked concurrently, keyed by issue number */
|
|
36
|
+
private activeIssues;
|
|
37
|
+
/** @deprecated Single-issue slot for backwards compatibility — use activeIssues */
|
|
35
38
|
private currentIssue;
|
|
36
39
|
private logPath;
|
|
37
40
|
private writeToUserLogs;
|
|
@@ -62,11 +65,11 @@ export declare class LogWriter {
|
|
|
62
65
|
/**
|
|
63
66
|
* Set PR info on the current issue (call before completeIssue)
|
|
64
67
|
*/
|
|
65
|
-
setPRInfo(prNumber: number, prUrl: string): void;
|
|
68
|
+
setPRInfo(prNumber: number, prUrl: string, issueNumber?: number): void;
|
|
66
69
|
/**
|
|
67
70
|
* Complete the current issue and add it to the run log
|
|
68
71
|
*/
|
|
69
|
-
completeIssue(): void;
|
|
72
|
+
completeIssue(issueNumber?: number): void;
|
|
70
73
|
/**
|
|
71
74
|
* Finalize the run log and write to disk
|
|
72
75
|
*
|
|
@@ -96,4 +99,4 @@ export declare class LogWriter {
|
|
|
96
99
|
*
|
|
97
100
|
* Utility function for creating phase logs when you have start/end times.
|
|
98
101
|
*/
|
|
99
|
-
export declare function createPhaseLogFromTiming(phase: Phase, issueNumber: number, startTime: Date, endTime: Date, status: PhaseLog["status"], options?: Partial<Pick<PhaseLog, "error" | "iterations" | "filesModified" | "testsRun" | "testsPassed" | "verdict" | "commitHash" | "fileDiffStats" | "cacheMetrics">>): PhaseLog;
|
|
102
|
+
export declare function createPhaseLogFromTiming(phase: Phase, issueNumber: number, startTime: Date, endTime: Date, status: PhaseLog["status"], options?: Partial<Pick<PhaseLog, "error" | "iterations" | "filesModified" | "testsRun" | "testsPassed" | "verdict" | "summary" | "commitHash" | "fileDiffStats" | "cacheMetrics" | "errorContext">>): PhaseLog;
|
|
@@ -23,6 +23,9 @@ import { rotateIfNeeded, DEFAULT_ROTATION_SETTINGS, } from "./log-rotation.js";
|
|
|
23
23
|
*/
|
|
24
24
|
export class LogWriter {
|
|
25
25
|
runLog = null;
|
|
26
|
+
/** Active issues being tracked concurrently, keyed by issue number */
|
|
27
|
+
activeIssues = new Map();
|
|
28
|
+
/** @deprecated Single-issue slot for backwards compatibility — use activeIssues */
|
|
26
29
|
currentIssue = null;
|
|
27
30
|
logPath;
|
|
28
31
|
writeToUserLogs;
|
|
@@ -64,7 +67,7 @@ export class LogWriter {
|
|
|
64
67
|
if (!this.runLog) {
|
|
65
68
|
throw new Error("LogWriter not initialized. Call initialize() first.");
|
|
66
69
|
}
|
|
67
|
-
|
|
70
|
+
const issueData = {
|
|
68
71
|
issueNumber,
|
|
69
72
|
title,
|
|
70
73
|
labels,
|
|
@@ -72,6 +75,9 @@ export class LogWriter {
|
|
|
72
75
|
status: "success",
|
|
73
76
|
totalDurationSeconds: 0,
|
|
74
77
|
};
|
|
78
|
+
this.activeIssues.set(issueNumber, issueData);
|
|
79
|
+
// Keep currentIssue in sync for callers that don't pass issueNumber
|
|
80
|
+
this.currentIssue = issueData;
|
|
75
81
|
if (this.verbose) {
|
|
76
82
|
console.log(`📝 Started logging issue #${issueNumber}`);
|
|
77
83
|
}
|
|
@@ -82,17 +88,18 @@ export class LogWriter {
|
|
|
82
88
|
* @param phaseLog - Complete phase log entry
|
|
83
89
|
*/
|
|
84
90
|
logPhase(phaseLog) {
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
// Route to the correct issue by issueNumber, falling back to currentIssue
|
|
92
|
+
const issue = this.activeIssues.get(phaseLog.issueNumber) ?? this.currentIssue;
|
|
93
|
+
if (!issue) {
|
|
94
|
+
throw new Error(`No active issue #${phaseLog.issueNumber}. Call startIssue() first.`);
|
|
87
95
|
}
|
|
88
|
-
|
|
96
|
+
issue.phases = [...(issue.phases ?? []), phaseLog];
|
|
89
97
|
// Update issue status based on phase result
|
|
90
98
|
if (phaseLog.status === "failure") {
|
|
91
|
-
|
|
99
|
+
issue.status = "failure";
|
|
92
100
|
}
|
|
93
|
-
else if (phaseLog.status === "timeout" &&
|
|
94
|
-
|
|
95
|
-
this.currentIssue.status = "partial";
|
|
101
|
+
else if (phaseLog.status === "timeout" && issue.status !== "failure") {
|
|
102
|
+
issue.status = "partial";
|
|
96
103
|
}
|
|
97
104
|
if (this.verbose) {
|
|
98
105
|
console.log(`📝 Logged phase: ${phaseLog.phase} (${phaseLog.status}) - ${phaseLog.durationSeconds.toFixed(1)}s`);
|
|
@@ -101,38 +108,57 @@ export class LogWriter {
|
|
|
101
108
|
/**
|
|
102
109
|
* Set PR info on the current issue (call before completeIssue)
|
|
103
110
|
*/
|
|
104
|
-
setPRInfo(prNumber, prUrl) {
|
|
105
|
-
|
|
111
|
+
setPRInfo(prNumber, prUrl, issueNumber) {
|
|
112
|
+
const issue = issueNumber
|
|
113
|
+
? (this.activeIssues.get(issueNumber) ?? this.currentIssue)
|
|
114
|
+
: this.currentIssue;
|
|
115
|
+
if (!issue) {
|
|
106
116
|
return;
|
|
107
117
|
}
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
issue.prNumber = prNumber;
|
|
119
|
+
issue.prUrl = prUrl;
|
|
110
120
|
}
|
|
111
121
|
/**
|
|
112
122
|
* Complete the current issue and add it to the run log
|
|
113
123
|
*/
|
|
114
|
-
completeIssue() {
|
|
115
|
-
if (!this.runLog
|
|
116
|
-
throw new Error("No
|
|
124
|
+
completeIssue(issueNumber) {
|
|
125
|
+
if (!this.runLog) {
|
|
126
|
+
throw new Error("No run log. Call initialize() first.");
|
|
127
|
+
}
|
|
128
|
+
// Resolve the issue to complete
|
|
129
|
+
const issue = issueNumber
|
|
130
|
+
? this.activeIssues.get(issueNumber)
|
|
131
|
+
: this.currentIssue;
|
|
132
|
+
if (!issue) {
|
|
133
|
+
throw new Error(issueNumber
|
|
134
|
+
? `No active issue #${issueNumber} to complete.`
|
|
135
|
+
: "No current issue to complete.");
|
|
117
136
|
}
|
|
118
137
|
// Calculate total duration from phases
|
|
119
|
-
const totalDurationSeconds =
|
|
138
|
+
const totalDurationSeconds = issue.phases?.reduce((sum, p) => sum + p.durationSeconds, 0) ?? 0;
|
|
120
139
|
const issueLog = {
|
|
121
|
-
issueNumber:
|
|
122
|
-
title:
|
|
123
|
-
labels:
|
|
124
|
-
status:
|
|
125
|
-
phases:
|
|
140
|
+
issueNumber: issue.issueNumber,
|
|
141
|
+
title: issue.title,
|
|
142
|
+
labels: issue.labels,
|
|
143
|
+
status: issue.status,
|
|
144
|
+
phases: issue.phases,
|
|
126
145
|
totalDurationSeconds,
|
|
127
|
-
...(
|
|
128
|
-
prNumber:
|
|
146
|
+
...(issue.prNumber != null && {
|
|
147
|
+
prNumber: issue.prNumber,
|
|
129
148
|
}),
|
|
130
|
-
...(
|
|
131
|
-
prUrl:
|
|
149
|
+
...(issue.prUrl != null && {
|
|
150
|
+
prUrl: issue.prUrl,
|
|
132
151
|
}),
|
|
133
152
|
};
|
|
134
153
|
this.runLog.issues.push(issueLog);
|
|
135
|
-
|
|
154
|
+
// Clean up from activeIssues map
|
|
155
|
+
if (issue.issueNumber != null) {
|
|
156
|
+
this.activeIssues.delete(issue.issueNumber);
|
|
157
|
+
}
|
|
158
|
+
// Clear currentIssue if it was the one completed
|
|
159
|
+
if (this.currentIssue === issue) {
|
|
160
|
+
this.currentIssue = null;
|
|
161
|
+
}
|
|
136
162
|
if (this.verbose) {
|
|
137
163
|
console.log(`📝 Completed issue #${issueLog.issueNumber} (${issueLog.status})`);
|
|
138
164
|
}
|
|
@@ -150,7 +176,11 @@ export class LogWriter {
|
|
|
150
176
|
if (!this.runLog) {
|
|
151
177
|
throw new Error("LogWriter not initialized.");
|
|
152
178
|
}
|
|
153
|
-
// Complete any pending
|
|
179
|
+
// Complete any pending issues (Map-based concurrent tracking)
|
|
180
|
+
for (const issueNum of [...this.activeIssues.keys()]) {
|
|
181
|
+
this.completeIssue(issueNum);
|
|
182
|
+
}
|
|
183
|
+
// Fallback: complete legacy currentIssue if not already handled
|
|
154
184
|
if (this.currentIssue) {
|
|
155
185
|
this.completeIssue();
|
|
156
186
|
}
|