@ai-sdlc/orchestrator 0.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/dist/adapters.d.ts +60 -0
- package/dist/adapters.js +160 -0
- package/dist/admission.d.ts +49 -0
- package/dist/admission.js +90 -0
- package/dist/analysis/analyzer.d.ts +10 -0
- package/dist/analysis/analyzer.js +67 -0
- package/dist/analysis/complexity-scorer.d.ts +24 -0
- package/dist/analysis/complexity-scorer.js +39 -0
- package/dist/analysis/context-builder.d.ts +14 -0
- package/dist/analysis/context-builder.js +79 -0
- package/dist/analysis/convention-detector.d.ts +9 -0
- package/dist/analysis/convention-detector.js +177 -0
- package/dist/analysis/dependency-parser.d.ts +17 -0
- package/dist/analysis/dependency-parser.js +167 -0
- package/dist/analysis/file-walker.d.ts +18 -0
- package/dist/analysis/file-walker.js +158 -0
- package/dist/analysis/hotspot-analyzer.d.ts +28 -0
- package/dist/analysis/hotspot-analyzer.js +85 -0
- package/dist/analysis/index.d.ts +13 -0
- package/dist/analysis/index.js +12 -0
- package/dist/analysis/pattern-detector.d.ts +10 -0
- package/dist/analysis/pattern-detector.js +86 -0
- package/dist/analysis/types.d.ts +94 -0
- package/dist/analysis/types.js +5 -0
- package/dist/audit-archival.d.ts +48 -0
- package/dist/audit-archival.js +72 -0
- package/dist/audit-export.d.ts +45 -0
- package/dist/audit-export.js +100 -0
- package/dist/audit-extended.d.ts +28 -0
- package/dist/audit-extended.js +38 -0
- package/dist/audit-scheduler.d.ts +32 -0
- package/dist/audit-scheduler.js +52 -0
- package/dist/audit-sqlite-sink.d.ts +11 -0
- package/dist/audit-sqlite-sink.js +51 -0
- package/dist/autonomy-tracker.d.ts +76 -0
- package/dist/autonomy-tracker.js +277 -0
- package/dist/check-runs.d.ts +37 -0
- package/dist/check-runs.js +72 -0
- package/dist/cli/commands/agents.d.ts +6 -0
- package/dist/cli/commands/agents.js +33 -0
- package/dist/cli/commands/complexity.d.ts +6 -0
- package/dist/cli/commands/complexity.js +35 -0
- package/dist/cli/commands/cost.d.ts +6 -0
- package/dist/cli/commands/cost.js +53 -0
- package/dist/cli/commands/dashboard.d.ts +9 -0
- package/dist/cli/commands/dashboard.js +50 -0
- package/dist/cli/commands/health.d.ts +6 -0
- package/dist/cli/commands/health.js +33 -0
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.js +127 -0
- package/dist/cli/commands/routing.d.ts +6 -0
- package/dist/cli/commands/routing.js +46 -0
- package/dist/cli/commands/run.d.ts +6 -0
- package/dist/cli/commands/run.js +36 -0
- package/dist/cli/commands/start.d.ts +6 -0
- package/dist/cli/commands/start.js +26 -0
- package/dist/cli/commands/status.d.ts +6 -0
- package/dist/cli/commands/status.js +34 -0
- package/dist/cli/dashboard-renderer.d.ts +24 -0
- package/dist/cli/dashboard-renderer.js +129 -0
- package/dist/cli/formatters/index.d.ts +6 -0
- package/dist/cli/formatters/index.js +18 -0
- package/dist/cli/formatters/json.d.ts +5 -0
- package/dist/cli/formatters/json.js +7 -0
- package/dist/cli/formatters/minimal.d.ts +5 -0
- package/dist/cli/formatters/minimal.js +36 -0
- package/dist/cli/formatters/table.d.ts +5 -0
- package/dist/cli/formatters/table.js +184 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +35 -0
- package/dist/compliance-extended.d.ts +41 -0
- package/dist/compliance-extended.js +58 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.js +66 -0
- package/dist/context-enrichment.d.ts +33 -0
- package/dist/context-enrichment.js +125 -0
- package/dist/cost-governance.d.ts +22 -0
- package/dist/cost-governance.js +106 -0
- package/dist/cost-tracker.d.ts +75 -0
- package/dist/cost-tracker.js +177 -0
- package/dist/defaults.d.ts +140 -0
- package/dist/defaults.js +218 -0
- package/dist/deploy/flyio-target.d.ts +19 -0
- package/dist/deploy/flyio-target.js +114 -0
- package/dist/deploy/index.d.ts +15 -0
- package/dist/deploy/index.js +11 -0
- package/dist/deploy/kubernetes-target.d.ts +25 -0
- package/dist/deploy/kubernetes-target.js +124 -0
- package/dist/deploy/metrics-collector.d.ts +31 -0
- package/dist/deploy/metrics-collector.js +75 -0
- package/dist/deploy/rollout-controller.d.ts +42 -0
- package/dist/deploy/rollout-controller.js +192 -0
- package/dist/deploy/rollout-types.d.ts +86 -0
- package/dist/deploy/rollout-types.js +5 -0
- package/dist/deploy/types.d.ts +47 -0
- package/dist/deploy/types.js +5 -0
- package/dist/deploy/vercel-target.d.ts +16 -0
- package/dist/deploy/vercel-target.js +115 -0
- package/dist/discovery.d.ts +26 -0
- package/dist/discovery.js +46 -0
- package/dist/episodic-enhanced.d.ts +88 -0
- package/dist/episodic-enhanced.js +212 -0
- package/dist/execute.d.ts +81 -0
- package/dist/execute.js +703 -0
- package/dist/fix-ci.d.ts +61 -0
- package/dist/fix-ci.js +380 -0
- package/dist/handoff-executor.d.ts +61 -0
- package/dist/handoff-executor.js +119 -0
- package/dist/index.d.ts +70 -0
- package/dist/index.js +87 -0
- package/dist/instrumented.d.ts +27 -0
- package/dist/instrumented.js +34 -0
- package/dist/logger.d.ts +17 -0
- package/dist/logger.js +37 -0
- package/dist/multi-repo/impact-analyzer.d.ts +22 -0
- package/dist/multi-repo/impact-analyzer.js +95 -0
- package/dist/multi-repo/index.d.ts +5 -0
- package/dist/multi-repo/index.js +4 -0
- package/dist/multi-repo/monorepo-detector.d.ts +16 -0
- package/dist/multi-repo/monorepo-detector.js +246 -0
- package/dist/multi-repo/service-map-builder.d.ts +24 -0
- package/dist/multi-repo/service-map-builder.js +235 -0
- package/dist/multi-repo/types.d.ts +54 -0
- package/dist/multi-repo/types.js +5 -0
- package/dist/notifications/index.d.ts +4 -0
- package/dist/notifications/index.js +4 -0
- package/dist/notifications/notification-router.d.ts +62 -0
- package/dist/notifications/notification-router.js +206 -0
- package/dist/notifications/slack-messenger.d.ts +26 -0
- package/dist/notifications/slack-messenger.js +69 -0
- package/dist/notifications/teams-messenger.d.ts +21 -0
- package/dist/notifications/teams-messenger.js +85 -0
- package/dist/notifications.d.ts +13 -0
- package/dist/notifications.js +15 -0
- package/dist/orchestration.d.ts +20 -0
- package/dist/orchestration.js +53 -0
- package/dist/orchestrator.d.ts +160 -0
- package/dist/orchestrator.js +372 -0
- package/dist/otel-exporter.d.ts +37 -0
- package/dist/otel-exporter.js +128 -0
- package/dist/plugin.d.ts +45 -0
- package/dist/plugin.js +8 -0
- package/dist/policy-evaluators.d.ts +40 -0
- package/dist/policy-evaluators.js +57 -0
- package/dist/process-escalation.d.ts +38 -0
- package/dist/process-escalation.js +129 -0
- package/dist/progressive-gates.d.ts +54 -0
- package/dist/progressive-gates.js +135 -0
- package/dist/provenance.d.ts +29 -0
- package/dist/provenance.js +61 -0
- package/dist/reconcilers.d.ts +28 -0
- package/dist/reconcilers.js +37 -0
- package/dist/runners/claude-code.d.ts +17 -0
- package/dist/runners/claude-code.js +200 -0
- package/dist/runners/codex.d.ts +19 -0
- package/dist/runners/codex.js +153 -0
- package/dist/runners/copilot-stub.d.ts +9 -0
- package/dist/runners/copilot-stub.js +15 -0
- package/dist/runners/copilot.d.ts +15 -0
- package/dist/runners/copilot.js +119 -0
- package/dist/runners/cursor-stub.d.ts +9 -0
- package/dist/runners/cursor-stub.js +15 -0
- package/dist/runners/cursor.d.ts +20 -0
- package/dist/runners/cursor.js +140 -0
- package/dist/runners/devin-stub.d.ts +9 -0
- package/dist/runners/devin-stub.js +15 -0
- package/dist/runners/generic-llm.d.ts +51 -0
- package/dist/runners/generic-llm.js +125 -0
- package/dist/runners/index.d.ts +8 -0
- package/dist/runners/index.js +7 -0
- package/dist/runners/runner-registry.d.ts +50 -0
- package/dist/runners/runner-registry.js +156 -0
- package/dist/runners/types.d.ts +59 -0
- package/dist/runners/types.js +7 -0
- package/dist/security.d.ts +51 -0
- package/dist/security.js +70 -0
- package/dist/shared.d.ts +134 -0
- package/dist/shared.js +284 -0
- package/dist/state/index.d.ts +4 -0
- package/dist/state/index.js +3 -0
- package/dist/state/schema.d.ts +16 -0
- package/dist/state/schema.js +264 -0
- package/dist/state/store.d.ts +109 -0
- package/dist/state/store.js +644 -0
- package/dist/state/types.d.ts +190 -0
- package/dist/state/types.js +5 -0
- package/dist/structured-logger.d.ts +20 -0
- package/dist/structured-logger.js +64 -0
- package/dist/task-decomposer.d.ts +61 -0
- package/dist/task-decomposer.js +265 -0
- package/dist/telemetry-extended.d.ts +24 -0
- package/dist/telemetry-extended.js +31 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.js +7 -0
- package/dist/validate-agent-output.d.ts +30 -0
- package/dist/validate-agent-output.js +81 -0
- package/dist/validate-issue.d.ts +25 -0
- package/dist/validate-issue.js +102 -0
- package/dist/watch.d.ts +43 -0
- package/dist/watch.js +99 -0
- package/dist/webhook-manager.d.ts +38 -0
- package/dist/webhook-manager.js +66 -0
- package/package.json +59 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor CLI runner — invokes `cursor-agent` in --print mode with
|
|
3
|
+
* stream-json output and collects the result via git diff.
|
|
4
|
+
*/
|
|
5
|
+
import { spawn, execFile } from 'node:child_process';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
import { DEFAULT_RUNNER_TIMEOUT_MS, DEFAULT_CURSOR_MODEL, DEFAULT_COMMIT_MESSAGE_TEMPLATE, DEFAULT_COMMIT_CO_AUTHOR, } from '../defaults.js';
|
|
8
|
+
import { buildPrompt } from './claude-code.js';
|
|
9
|
+
export { buildPrompt };
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
async function gitExec(workDir, args) {
|
|
12
|
+
const { stdout } = await execFileAsync('git', args, { cwd: workDir });
|
|
13
|
+
return stdout.trim();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse NDJSON stream output from cursor-agent --output-format=stream-json.
|
|
17
|
+
* Extracts the final assistant message content for use as a summary.
|
|
18
|
+
*/
|
|
19
|
+
export function parseStreamJson(stdout) {
|
|
20
|
+
const lines = stdout.split('\n').filter(Boolean);
|
|
21
|
+
let lastAssistantContent = '';
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
try {
|
|
24
|
+
const obj = JSON.parse(line);
|
|
25
|
+
if (obj.role === 'assistant' && typeof obj.content === 'string') {
|
|
26
|
+
lastAssistantContent = obj.content;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Skip malformed lines
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return lastAssistantContent || stdout.slice(0, 2000);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse token usage from Cursor CLI stderr output.
|
|
37
|
+
*/
|
|
38
|
+
export function parseTokenUsage(stderr, model) {
|
|
39
|
+
const inputMatch = stderr.match(/input[\s_-]*tokens?[:\s]+(\d[\d,]*)/i);
|
|
40
|
+
const outputMatch = stderr.match(/output[\s_-]*tokens?[:\s]+(\d[\d,]*)/i);
|
|
41
|
+
if (inputMatch || outputMatch) {
|
|
42
|
+
return {
|
|
43
|
+
inputTokens: inputMatch ? parseInt(inputMatch[1].replace(/,/g, ''), 10) : 0,
|
|
44
|
+
outputTokens: outputMatch ? parseInt(outputMatch[1].replace(/,/g, ''), 10) : 0,
|
|
45
|
+
model,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const totalMatch = stderr.match(/total[\s_-]*tokens?[:\s]+(\d[\d,]*)/i);
|
|
49
|
+
if (totalMatch) {
|
|
50
|
+
const total = parseInt(totalMatch[1].replace(/,/g, ''), 10);
|
|
51
|
+
return {
|
|
52
|
+
inputTokens: Math.round(total * 0.7),
|
|
53
|
+
outputTokens: Math.round(total * 0.3),
|
|
54
|
+
model,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
export class CursorRunner {
|
|
60
|
+
async run(ctx) {
|
|
61
|
+
const prompt = buildPrompt(ctx);
|
|
62
|
+
const timeoutMs = ctx.timeoutMs ?? DEFAULT_RUNNER_TIMEOUT_MS;
|
|
63
|
+
const model = DEFAULT_CURSOR_MODEL ?? 'cursor-default';
|
|
64
|
+
try {
|
|
65
|
+
const args = ['--print', prompt, '--force', '--output-format=stream-json'];
|
|
66
|
+
if (DEFAULT_CURSOR_MODEL) {
|
|
67
|
+
args.push('-m', DEFAULT_CURSOR_MODEL);
|
|
68
|
+
}
|
|
69
|
+
const { stdout, stderr } = await new Promise((resolve, reject) => {
|
|
70
|
+
const child = spawn('cursor-agent', args, {
|
|
71
|
+
cwd: ctx.workDir,
|
|
72
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
73
|
+
env: { ...process.env },
|
|
74
|
+
timeout: timeoutMs,
|
|
75
|
+
});
|
|
76
|
+
const chunks = [];
|
|
77
|
+
const errChunks = [];
|
|
78
|
+
child.stdout.on('data', (data) => chunks.push(data));
|
|
79
|
+
child.stderr.on('data', (data) => errChunks.push(data));
|
|
80
|
+
child.on('close', (code) => {
|
|
81
|
+
const out = Buffer.concat(chunks).toString('utf-8');
|
|
82
|
+
const err = Buffer.concat(errChunks).toString('utf-8');
|
|
83
|
+
if (code === 0) {
|
|
84
|
+
resolve({ stdout: out, stderr: err });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
reject(new Error(`cursor-agent exited with code ${code}: ${err || out}`));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
child.on('error', reject);
|
|
91
|
+
});
|
|
92
|
+
const tokenUsage = parseTokenUsage(stderr, model);
|
|
93
|
+
const summary = parseStreamJson(stdout);
|
|
94
|
+
// Collect changed files
|
|
95
|
+
const diffOutput = await gitExec(ctx.workDir, ['diff', '--name-only']);
|
|
96
|
+
const untrackedOutput = await gitExec(ctx.workDir, [
|
|
97
|
+
'ls-files',
|
|
98
|
+
'--others',
|
|
99
|
+
'--exclude-standard',
|
|
100
|
+
]);
|
|
101
|
+
const filesChanged = [
|
|
102
|
+
...diffOutput.split('\n').filter(Boolean),
|
|
103
|
+
...untrackedOutput.split('\n').filter(Boolean),
|
|
104
|
+
];
|
|
105
|
+
if (filesChanged.length === 0) {
|
|
106
|
+
return {
|
|
107
|
+
success: false,
|
|
108
|
+
filesChanged: [],
|
|
109
|
+
summary: 'Agent made no changes',
|
|
110
|
+
error: 'No files were modified',
|
|
111
|
+
tokenUsage,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// Stage and commit
|
|
115
|
+
await gitExec(ctx.workDir, ['add', '-A']);
|
|
116
|
+
const tmpl = ctx.commitMessageTemplate ?? DEFAULT_COMMIT_MESSAGE_TEMPLATE;
|
|
117
|
+
const coAuthor = ctx.commitCoAuthor ?? DEFAULT_COMMIT_CO_AUTHOR;
|
|
118
|
+
const commitMsg = tmpl
|
|
119
|
+
.replace(/\{issueNumber\}/g, String(ctx.issueNumber))
|
|
120
|
+
.replace(/\{issueTitle\}/g, ctx.issueTitle);
|
|
121
|
+
await gitExec(ctx.workDir, ['commit', '-m', `${commitMsg}\n\nCo-Authored-By: ${coAuthor}`]);
|
|
122
|
+
return {
|
|
123
|
+
success: true,
|
|
124
|
+
filesChanged,
|
|
125
|
+
summary: summary.slice(0, 2000),
|
|
126
|
+
tokenUsage,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
filesChanged: [],
|
|
134
|
+
summary: 'Agent execution failed',
|
|
135
|
+
error: message,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stub runner for Devin — placeholder for future implementation.
|
|
3
|
+
*/
|
|
4
|
+
import type { AgentRunner, AgentContext, AgentResult } from './types.js';
|
|
5
|
+
export declare class DevinStubRunner implements AgentRunner {
|
|
6
|
+
readonly name = "devin";
|
|
7
|
+
run(_ctx: AgentContext): Promise<AgentResult>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=devin-stub.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stub runner for Devin — placeholder for future implementation.
|
|
3
|
+
*/
|
|
4
|
+
export class DevinStubRunner {
|
|
5
|
+
name = 'devin';
|
|
6
|
+
async run(_ctx) {
|
|
7
|
+
return {
|
|
8
|
+
success: false,
|
|
9
|
+
filesChanged: [],
|
|
10
|
+
summary: 'Devin runner not yet implemented',
|
|
11
|
+
error: 'STUB: Devin integration requires Devin API access',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=devin-stub.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic LLM runner — HTTP-based, OpenAI-compatible API runner.
|
|
3
|
+
*
|
|
4
|
+
* Design decision D4: Calls LLM APIs directly via HTTP (not CLI subprocess).
|
|
5
|
+
* Accepts apiUrl, apiKey, model config. Enables non-Claude agents.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentRunner, AgentContext, AgentResult } from './types.js';
|
|
8
|
+
export interface GenericLLMConfig {
|
|
9
|
+
/** API endpoint URL (e.g., https://api.openai.com/v1/chat/completions). */
|
|
10
|
+
apiUrl: string;
|
|
11
|
+
/** API key for authentication. */
|
|
12
|
+
apiKey: string;
|
|
13
|
+
/** Model identifier (e.g., gpt-4, claude-3-opus, etc.). */
|
|
14
|
+
model: string;
|
|
15
|
+
/** Maximum tokens to generate. Defaults to 4096. */
|
|
16
|
+
maxTokens?: number;
|
|
17
|
+
/** Request timeout in milliseconds. Defaults to 120000. */
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
/** System prompt prefix. */
|
|
20
|
+
systemPrompt?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ChatMessage {
|
|
23
|
+
role: 'system' | 'user' | 'assistant';
|
|
24
|
+
content: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ChatCompletionResponse {
|
|
27
|
+
id: string;
|
|
28
|
+
choices: Array<{
|
|
29
|
+
message: {
|
|
30
|
+
role: string;
|
|
31
|
+
content: string;
|
|
32
|
+
};
|
|
33
|
+
finish_reason: string;
|
|
34
|
+
}>;
|
|
35
|
+
usage?: {
|
|
36
|
+
prompt_tokens: number;
|
|
37
|
+
completion_tokens: number;
|
|
38
|
+
total_tokens: number;
|
|
39
|
+
};
|
|
40
|
+
model?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare class GenericLLMRunner implements AgentRunner {
|
|
43
|
+
private config;
|
|
44
|
+
constructor(config: GenericLLMConfig);
|
|
45
|
+
run(ctx: AgentContext): Promise<AgentResult>;
|
|
46
|
+
private buildMessages;
|
|
47
|
+
private callAPI;
|
|
48
|
+
private parseTokenUsage;
|
|
49
|
+
private extractFilesFromResponse;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=generic-llm.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic LLM runner — HTTP-based, OpenAI-compatible API runner.
|
|
3
|
+
*
|
|
4
|
+
* Design decision D4: Calls LLM APIs directly via HTTP (not CLI subprocess).
|
|
5
|
+
* Accepts apiUrl, apiKey, model config. Enables non-Claude agents.
|
|
6
|
+
*/
|
|
7
|
+
import { DEFAULT_LLM_TIMEOUT_MS, DEFAULT_LLM_MAX_TOKENS, DEFAULT_LLM_SYSTEM_PROMPT, } from '../defaults.js';
|
|
8
|
+
export class GenericLLMRunner {
|
|
9
|
+
config;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
}
|
|
13
|
+
async run(ctx) {
|
|
14
|
+
const messages = this.buildMessages(ctx);
|
|
15
|
+
try {
|
|
16
|
+
const response = await this.callAPI(messages);
|
|
17
|
+
if (!response.choices?.[0]?.message?.content) {
|
|
18
|
+
return {
|
|
19
|
+
success: false,
|
|
20
|
+
filesChanged: [],
|
|
21
|
+
summary: 'LLM returned empty response',
|
|
22
|
+
error: 'No content in response',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const content = response.choices[0].message.content;
|
|
26
|
+
const tokenUsage = this.parseTokenUsage(response);
|
|
27
|
+
return {
|
|
28
|
+
success: true,
|
|
29
|
+
filesChanged: this.extractFilesFromResponse(content),
|
|
30
|
+
summary: content.slice(0, 2000),
|
|
31
|
+
tokenUsage,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
filesChanged: [],
|
|
38
|
+
summary: 'LLM API call failed',
|
|
39
|
+
error: err instanceof Error ? err.message : String(err),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
buildMessages(ctx) {
|
|
44
|
+
const messages = [];
|
|
45
|
+
if (this.config.systemPrompt) {
|
|
46
|
+
messages.push({ role: 'system', content: this.config.systemPrompt });
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
messages.push({
|
|
50
|
+
role: 'system',
|
|
51
|
+
content: DEFAULT_LLM_SYSTEM_PROMPT,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const userContent = [
|
|
55
|
+
`Issue #${ctx.issueNumber}: ${ctx.issueTitle}`,
|
|
56
|
+
'',
|
|
57
|
+
ctx.issueBody,
|
|
58
|
+
'',
|
|
59
|
+
`Working directory: ${ctx.workDir}`,
|
|
60
|
+
`Branch: ${ctx.branch}`,
|
|
61
|
+
`Max files: ${ctx.constraints.maxFilesPerChange}`,
|
|
62
|
+
`Require tests: ${ctx.constraints.requireTests}`,
|
|
63
|
+
];
|
|
64
|
+
if (ctx.codebaseContext) {
|
|
65
|
+
userContent.push('', `Codebase: ${JSON.stringify(ctx.codebaseContext)}`);
|
|
66
|
+
}
|
|
67
|
+
messages.push({ role: 'user', content: userContent.join('\n') });
|
|
68
|
+
return messages;
|
|
69
|
+
}
|
|
70
|
+
async callAPI(messages) {
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs ?? DEFAULT_LLM_TIMEOUT_MS);
|
|
73
|
+
try {
|
|
74
|
+
const res = await fetch(this.config.apiUrl, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
model: this.config.model,
|
|
82
|
+
messages,
|
|
83
|
+
max_tokens: this.config.maxTokens ?? DEFAULT_LLM_MAX_TOKENS,
|
|
84
|
+
}),
|
|
85
|
+
signal: controller.signal,
|
|
86
|
+
});
|
|
87
|
+
if (!res.ok) {
|
|
88
|
+
const text = await res.text().catch(() => '');
|
|
89
|
+
throw new Error(`API error ${res.status}: ${text.slice(0, 200)}`);
|
|
90
|
+
}
|
|
91
|
+
return (await res.json());
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
clearTimeout(timeout);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
parseTokenUsage(response) {
|
|
98
|
+
if (!response.usage)
|
|
99
|
+
return undefined;
|
|
100
|
+
return {
|
|
101
|
+
inputTokens: response.usage.prompt_tokens,
|
|
102
|
+
outputTokens: response.usage.completion_tokens,
|
|
103
|
+
model: response.model ?? this.config.model,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
extractFilesFromResponse(content) {
|
|
107
|
+
// Extract file paths from common patterns in LLM responses
|
|
108
|
+
const patterns = [
|
|
109
|
+
/(?:modified|created|updated|changed|edited)\s+[`"]?([^\s`"]+\.[a-z]+)/gi,
|
|
110
|
+
/```[\w]*\s*\/\/\s*(\S+\.[a-z]+)/g,
|
|
111
|
+
];
|
|
112
|
+
const files = new Set();
|
|
113
|
+
for (const pattern of patterns) {
|
|
114
|
+
let match;
|
|
115
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
116
|
+
const file = match[1].replace(/[`'"]/g, '');
|
|
117
|
+
if (file.includes('/') || file.includes('.')) {
|
|
118
|
+
files.add(file);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return [...files];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=generic-llm.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type { AgentRunner, AgentContext, AgentResult, TokenUsage } from './types.js';
|
|
2
|
+
export { ClaudeCodeRunner, GitHubActionsRunner } from './claude-code.js';
|
|
3
|
+
export { GenericLLMRunner, type GenericLLMConfig, type ChatCompletionResponse, } from './generic-llm.js';
|
|
4
|
+
export { CopilotRunner } from './copilot.js';
|
|
5
|
+
export { CursorRunner } from './cursor.js';
|
|
6
|
+
export { CodexRunner } from './codex.js';
|
|
7
|
+
export { RunnerRegistry, createRunnerRegistry, type RegisteredRunner } from './runner-registry.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ClaudeCodeRunner, GitHubActionsRunner } from './claude-code.js';
|
|
2
|
+
export { GenericLLMRunner, } from './generic-llm.js';
|
|
3
|
+
export { CopilotRunner } from './copilot.js';
|
|
4
|
+
export { CursorRunner } from './cursor.js';
|
|
5
|
+
export { CodexRunner } from './codex.js';
|
|
6
|
+
export { RunnerRegistry, createRunnerRegistry } from './runner-registry.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runner registry — manages available agent runners with auto-discovery.
|
|
3
|
+
*
|
|
4
|
+
* Design decision D4: Registry auto-discovers available runners from environment.
|
|
5
|
+
*/
|
|
6
|
+
import type { AgentRunner } from './types.js';
|
|
7
|
+
export interface RegisteredRunner {
|
|
8
|
+
name: string;
|
|
9
|
+
runner: AgentRunner;
|
|
10
|
+
/** Whether this runner is available (has required config). */
|
|
11
|
+
available: boolean;
|
|
12
|
+
/** Source of the runner (built-in, env, manual). */
|
|
13
|
+
source: 'built-in' | 'env' | 'manual';
|
|
14
|
+
}
|
|
15
|
+
export declare class RunnerRegistry {
|
|
16
|
+
private runners;
|
|
17
|
+
/**
|
|
18
|
+
* Register a runner manually.
|
|
19
|
+
*/
|
|
20
|
+
register(name: string, runner: AgentRunner): void;
|
|
21
|
+
/**
|
|
22
|
+
* Get a runner by name.
|
|
23
|
+
*/
|
|
24
|
+
get(name: string): AgentRunner | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Get the default runner. Returns the first available runner.
|
|
27
|
+
*/
|
|
28
|
+
getDefault(): AgentRunner | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* List all registered runners.
|
|
31
|
+
*/
|
|
32
|
+
list(): RegisteredRunner[];
|
|
33
|
+
/**
|
|
34
|
+
* List only available runners.
|
|
35
|
+
*/
|
|
36
|
+
listAvailable(): RegisteredRunner[];
|
|
37
|
+
/**
|
|
38
|
+
* Check if a runner is registered and available.
|
|
39
|
+
*/
|
|
40
|
+
has(name: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Auto-discover runners from environment variables and register them.
|
|
43
|
+
*/
|
|
44
|
+
discoverFromEnv(env?: Record<string, string | undefined>): void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create a runner registry with auto-discovery.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createRunnerRegistry(env?: Record<string, string | undefined>): RunnerRegistry;
|
|
50
|
+
//# sourceMappingURL=runner-registry.d.ts.map
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runner registry — manages available agent runners with auto-discovery.
|
|
3
|
+
*
|
|
4
|
+
* Design decision D4: Registry auto-discovers available runners from environment.
|
|
5
|
+
*/
|
|
6
|
+
import { ClaudeCodeRunner } from './claude-code.js';
|
|
7
|
+
import { GenericLLMRunner } from './generic-llm.js';
|
|
8
|
+
import { CopilotRunner } from './copilot.js';
|
|
9
|
+
import { CursorRunner } from './cursor.js';
|
|
10
|
+
import { CodexRunner } from './codex.js';
|
|
11
|
+
import { DEFAULT_OPENAI_API_URL, DEFAULT_OPENAI_MODEL, DEFAULT_ANTHROPIC_API_URL, DEFAULT_ANTHROPIC_MODEL, DEFAULT_GENERIC_LLM_MODEL, } from '../defaults.js';
|
|
12
|
+
export class RunnerRegistry {
|
|
13
|
+
runners = new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Register a runner manually.
|
|
16
|
+
*/
|
|
17
|
+
register(name, runner) {
|
|
18
|
+
this.runners.set(name, {
|
|
19
|
+
name,
|
|
20
|
+
runner,
|
|
21
|
+
available: true,
|
|
22
|
+
source: 'manual',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get a runner by name.
|
|
27
|
+
*/
|
|
28
|
+
get(name) {
|
|
29
|
+
return this.runners.get(name)?.runner;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get the default runner. Returns the first available runner.
|
|
33
|
+
*/
|
|
34
|
+
getDefault() {
|
|
35
|
+
for (const entry of this.runners.values()) {
|
|
36
|
+
if (entry.available)
|
|
37
|
+
return entry.runner;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* List all registered runners.
|
|
43
|
+
*/
|
|
44
|
+
list() {
|
|
45
|
+
return [...this.runners.values()];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* List only available runners.
|
|
49
|
+
*/
|
|
50
|
+
listAvailable() {
|
|
51
|
+
return [...this.runners.values()].filter((r) => r.available);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if a runner is registered and available.
|
|
55
|
+
*/
|
|
56
|
+
has(name) {
|
|
57
|
+
const entry = this.runners.get(name);
|
|
58
|
+
return entry?.available ?? false;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Auto-discover runners from environment variables and register them.
|
|
62
|
+
*/
|
|
63
|
+
discoverFromEnv(env = process.env) {
|
|
64
|
+
// Claude Code is always available as CLI runner
|
|
65
|
+
if (!this.runners.has('claude-code')) {
|
|
66
|
+
this.runners.set('claude-code', {
|
|
67
|
+
name: 'claude-code',
|
|
68
|
+
runner: new ClaudeCodeRunner(),
|
|
69
|
+
available: true,
|
|
70
|
+
source: 'built-in',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// OpenAI-compatible runner from env
|
|
74
|
+
const openaiKey = env.OPENAI_API_KEY;
|
|
75
|
+
if (openaiKey && !this.runners.has('openai')) {
|
|
76
|
+
this.runners.set('openai', {
|
|
77
|
+
name: 'openai',
|
|
78
|
+
runner: new GenericLLMRunner({
|
|
79
|
+
apiUrl: env.OPENAI_API_URL ?? DEFAULT_OPENAI_API_URL,
|
|
80
|
+
apiKey: openaiKey,
|
|
81
|
+
model: env.OPENAI_MODEL ?? DEFAULT_OPENAI_MODEL,
|
|
82
|
+
}),
|
|
83
|
+
available: true,
|
|
84
|
+
source: 'env',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// Anthropic API runner from env
|
|
88
|
+
const anthropicKey = env.ANTHROPIC_API_KEY;
|
|
89
|
+
if (anthropicKey && !this.runners.has('anthropic')) {
|
|
90
|
+
this.runners.set('anthropic', {
|
|
91
|
+
name: 'anthropic',
|
|
92
|
+
runner: new GenericLLMRunner({
|
|
93
|
+
apiUrl: env.ANTHROPIC_API_URL ?? DEFAULT_ANTHROPIC_API_URL,
|
|
94
|
+
apiKey: anthropicKey,
|
|
95
|
+
model: env.ANTHROPIC_MODEL ?? DEFAULT_ANTHROPIC_MODEL,
|
|
96
|
+
}),
|
|
97
|
+
available: true,
|
|
98
|
+
source: 'env',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Generic LLM runner from env
|
|
102
|
+
const genericUrl = env.LLM_API_URL;
|
|
103
|
+
const genericKey = env.LLM_API_KEY;
|
|
104
|
+
if (genericUrl && genericKey && !this.runners.has('generic-llm')) {
|
|
105
|
+
this.runners.set('generic-llm', {
|
|
106
|
+
name: 'generic-llm',
|
|
107
|
+
runner: new GenericLLMRunner({
|
|
108
|
+
apiUrl: genericUrl,
|
|
109
|
+
apiKey: genericKey,
|
|
110
|
+
model: env.LLM_MODEL ?? DEFAULT_GENERIC_LLM_MODEL,
|
|
111
|
+
}),
|
|
112
|
+
available: true,
|
|
113
|
+
source: 'env',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// GitHub Copilot CLI runner — available when GH_TOKEN or GITHUB_TOKEN is set
|
|
117
|
+
const ghToken = env.GH_TOKEN ?? env.GITHUB_TOKEN;
|
|
118
|
+
if (ghToken && !this.runners.has('copilot')) {
|
|
119
|
+
this.runners.set('copilot', {
|
|
120
|
+
name: 'copilot',
|
|
121
|
+
runner: new CopilotRunner(),
|
|
122
|
+
available: true,
|
|
123
|
+
source: 'env',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Cursor CLI runner — available when CURSOR_API_KEY is set
|
|
127
|
+
const cursorKey = env.CURSOR_API_KEY;
|
|
128
|
+
if (cursorKey && !this.runners.has('cursor')) {
|
|
129
|
+
this.runners.set('cursor', {
|
|
130
|
+
name: 'cursor',
|
|
131
|
+
runner: new CursorRunner(),
|
|
132
|
+
available: true,
|
|
133
|
+
source: 'env',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Codex CLI runner — available when CODEX_API_KEY is set
|
|
137
|
+
const codexKey = env.CODEX_API_KEY;
|
|
138
|
+
if (codexKey && !this.runners.has('codex')) {
|
|
139
|
+
this.runners.set('codex', {
|
|
140
|
+
name: 'codex',
|
|
141
|
+
runner: new CodexRunner(),
|
|
142
|
+
available: true,
|
|
143
|
+
source: 'env',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Create a runner registry with auto-discovery.
|
|
150
|
+
*/
|
|
151
|
+
export function createRunnerRegistry(env) {
|
|
152
|
+
const registry = new RunnerRegistry();
|
|
153
|
+
registry.discoverFromEnv(env);
|
|
154
|
+
return registry;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=runner-registry.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentRunner abstraction — decouples agent invocation from execution environment.
|
|
3
|
+
* The ClaudeCodeRunner is the initial implementation; swap to
|
|
4
|
+
* Codespaces / devcontainers by implementing this interface.
|
|
5
|
+
*/
|
|
6
|
+
import type { AgentMemory } from '@ai-sdlc/reference';
|
|
7
|
+
import type { CodebaseContext } from '../analysis/types.js';
|
|
8
|
+
export interface AgentContext {
|
|
9
|
+
issueNumber: number;
|
|
10
|
+
issueTitle: string;
|
|
11
|
+
issueBody: string;
|
|
12
|
+
workDir: string;
|
|
13
|
+
branch: string;
|
|
14
|
+
constraints: {
|
|
15
|
+
maxFilesPerChange: number;
|
|
16
|
+
requireTests: boolean;
|
|
17
|
+
blockedPaths: string[];
|
|
18
|
+
};
|
|
19
|
+
/** CI failure logs, populated only during fix-CI retries. */
|
|
20
|
+
ciErrors?: string;
|
|
21
|
+
/** Agent memory for long-term/episodic recall. */
|
|
22
|
+
memory?: AgentMemory;
|
|
23
|
+
/** Override the default tool allowlist for the agent subprocess. */
|
|
24
|
+
allowedTools?: string[];
|
|
25
|
+
/** Timeout in milliseconds for the agent subprocess (default 300000). */
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
/** Override the default model for this agent invocation. */
|
|
28
|
+
model?: string;
|
|
29
|
+
/** Codebase context for intelligent agent prompting. */
|
|
30
|
+
codebaseContext?: CodebaseContext;
|
|
31
|
+
/** Enriched episodic context from prior runs. */
|
|
32
|
+
episodicContext?: string;
|
|
33
|
+
/** Lint command for agent prompt (e.g., `npm run lint`). */
|
|
34
|
+
lintCommand?: string;
|
|
35
|
+
/** Format command for agent prompt (e.g., `npm run format`). */
|
|
36
|
+
formatCommand?: string;
|
|
37
|
+
/** Commit message template with `{issueNumber}` and `{issueTitle}` placeholders. */
|
|
38
|
+
commitMessageTemplate?: string;
|
|
39
|
+
/** Co-author line for commits. */
|
|
40
|
+
commitCoAuthor?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface TokenUsage {
|
|
43
|
+
inputTokens: number;
|
|
44
|
+
outputTokens: number;
|
|
45
|
+
cacheReadTokens?: number;
|
|
46
|
+
model: string;
|
|
47
|
+
}
|
|
48
|
+
export interface AgentResult {
|
|
49
|
+
success: boolean;
|
|
50
|
+
filesChanged: string[];
|
|
51
|
+
summary: string;
|
|
52
|
+
error?: string;
|
|
53
|
+
/** Token usage parsed from the agent subprocess. */
|
|
54
|
+
tokenUsage?: TokenUsage;
|
|
55
|
+
}
|
|
56
|
+
export interface AgentRunner {
|
|
57
|
+
run(context: AgentContext): Promise<AgentResult>;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentRunner abstraction — decouples agent invocation from execution environment.
|
|
3
|
+
* The ClaudeCodeRunner is the initial implementation; swap to
|
|
4
|
+
* Codespaces / devcontainers by implementing this interface.
|
|
5
|
+
*/
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=types.js.map
|