@developerz.ai/aitm 0.0.3 → 0.0.4
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/cli/commands.js +4 -0
- package/dist/config/config-loader.js +3 -0
- package/dist/config/schema.d.ts +2 -0
- package/dist/config/schema.js +1 -0
- package/dist/github/github-client.d.ts +7 -0
- package/dist/github/github-client.js +85 -0
- package/dist/loop/run-loop-adapter.js +4 -1
- package/dist/loop/take-over-flow.d.ts +14 -0
- package/dist/loop/take-over-flow.js +22 -6
- package/dist/state/pr-context-store.d.ts +20 -0
- package/dist/state/pr-context-store.js +60 -0
- package/dist/subagents/worker.d.ts +3 -1
- package/dist/subagents/worker.js +10 -1
- package/package.json +2 -2
package/dist/cli/commands.js
CHANGED
|
@@ -370,9 +370,11 @@ async function defaultRunMergeFlow(input) {
|
|
|
370
370
|
const { runTakeOverFlow } = await import("../loop/take-over-flow.js");
|
|
371
371
|
const { execa } = await import('execa');
|
|
372
372
|
const { githubThreadTool } = await import("../tools/github-thread-tool.js");
|
|
373
|
+
const { PrContextStore } = await import("../state/pr-context-store.js");
|
|
373
374
|
const worktreePath = input.cwd;
|
|
374
375
|
const baseBranch = await input.github.defaultBranch();
|
|
375
376
|
const styleContents = input.agentConfig.contents;
|
|
377
|
+
const prContext = new PrContextStore(resolvePath(input.cwd, '.ai-task-master'));
|
|
376
378
|
const workerTools = localEditTools(worktreePath);
|
|
377
379
|
const github = githubThreadTool({ github: input.github });
|
|
378
380
|
const result = await runTakeOverFlow({
|
|
@@ -380,6 +382,7 @@ async function defaultRunMergeFlow(input) {
|
|
|
380
382
|
worktreePath,
|
|
381
383
|
baseBranch,
|
|
382
384
|
github: input.github,
|
|
385
|
+
prContext,
|
|
383
386
|
mergeMethod: input.runState.options.mergeMethod,
|
|
384
387
|
push: async (cwd) => {
|
|
385
388
|
const r = await execa('git', ['push'], { cwd });
|
|
@@ -393,6 +396,7 @@ async function defaultRunMergeFlow(input) {
|
|
|
393
396
|
workerModel: input.credentials.modelFor('worker'),
|
|
394
397
|
workerTools,
|
|
395
398
|
styleContents,
|
|
399
|
+
...(input.resolved.formatCommand ? { formatCommand: input.resolved.formatCommand } : {}),
|
|
396
400
|
},
|
|
397
401
|
});
|
|
398
402
|
if (result.kind === 'merged') {
|
|
@@ -19,6 +19,7 @@ const KNOWN_KEYS = new Set([
|
|
|
19
19
|
'autoMerge',
|
|
20
20
|
'mergeMethod',
|
|
21
21
|
'stylePath',
|
|
22
|
+
'formatCommand',
|
|
22
23
|
'logLevel',
|
|
23
24
|
'concurrency',
|
|
24
25
|
'mcpServers',
|
|
@@ -29,6 +30,7 @@ const DEFAULTS = {
|
|
|
29
30
|
autoMerge: true,
|
|
30
31
|
mergeMethod: 'squash',
|
|
31
32
|
stylePath: null,
|
|
33
|
+
formatCommand: null,
|
|
32
34
|
logLevel: 'info',
|
|
33
35
|
concurrency: 1,
|
|
34
36
|
};
|
|
@@ -68,6 +70,7 @@ export class ConfigLoader {
|
|
|
68
70
|
autoMerge: pick(cliOverrides.autoMerge, project?.autoMerge, global?.autoMerge, DEFAULTS.autoMerge),
|
|
69
71
|
mergeMethod: pick(cliOverrides.mergeMethod, project?.mergeMethod, global?.mergeMethod, DEFAULTS.mergeMethod),
|
|
70
72
|
stylePath: pickNullable(cliOverrides.stylePath, project?.stylePath, global?.stylePath, DEFAULTS.stylePath),
|
|
73
|
+
formatCommand: pickNullable(undefined, project?.formatCommand, global?.formatCommand, DEFAULTS.formatCommand),
|
|
71
74
|
logLevel: pick(undefined, project?.logLevel, global?.logLevel, DEFAULTS.logLevel),
|
|
72
75
|
concurrency: pick(cliOverrides.concurrency, project?.concurrency, global?.concurrency, DEFAULTS.concurrency),
|
|
73
76
|
mcpServers,
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export declare const ConfigFileSchema: z.ZodObject<{
|
|
|
35
35
|
rebase: "rebase";
|
|
36
36
|
}>>;
|
|
37
37
|
stylePath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
38
|
+
formatCommand: z.ZodOptional<z.ZodString>;
|
|
38
39
|
logLevel: z.ZodOptional<z.ZodEnum<{
|
|
39
40
|
debug: "debug";
|
|
40
41
|
info: "info";
|
|
@@ -77,6 +78,7 @@ export type ResolvedConfig = {
|
|
|
77
78
|
autoMerge: boolean;
|
|
78
79
|
mergeMethod: 'squash' | 'merge' | 'rebase';
|
|
79
80
|
stylePath: string | null;
|
|
81
|
+
formatCommand: string | null;
|
|
80
82
|
logLevel: 'debug' | 'info' | 'warn' | 'error';
|
|
81
83
|
concurrency: number;
|
|
82
84
|
mcpServers: import('../mcp/schema.ts').McpServers;
|
package/dist/config/schema.js
CHANGED
|
@@ -19,6 +19,7 @@ export const ConfigFileSchema = z
|
|
|
19
19
|
autoMerge: z.boolean().optional(),
|
|
20
20
|
mergeMethod: MergeMethodSchema.optional(),
|
|
21
21
|
stylePath: z.string().nullable().optional(),
|
|
22
|
+
formatCommand: z.string().optional(),
|
|
22
23
|
logLevel: LogLevelSchema.optional(),
|
|
23
24
|
concurrency: z.number().int().positive().optional(),
|
|
24
25
|
mcpServers: McpServersSchema.optional(),
|
|
@@ -33,6 +33,13 @@ export declare class GitHubClient {
|
|
|
33
33
|
getPrForBranch(branch: string): Promise<PullRequest | null>;
|
|
34
34
|
createPr(input: CreatePrInput): Promise<PullRequest>;
|
|
35
35
|
waitForChecks(pr: number): Promise<CheckStatus>;
|
|
36
|
+
getFailedCiLogs(pr: number): Promise<Array<{
|
|
37
|
+
check: string;
|
|
38
|
+
logs: string;
|
|
39
|
+
}>>;
|
|
40
|
+
private failedRunIds;
|
|
41
|
+
private failedJobs;
|
|
42
|
+
private jobLogs;
|
|
36
43
|
listUnresolvedThreads(pr: number): Promise<ReviewThread[]>;
|
|
37
44
|
private paginateReviewThreads;
|
|
38
45
|
private paginateThreadComments;
|
|
@@ -121,6 +121,67 @@ export class GitHubClient {
|
|
|
121
121
|
delay = Math.min(delay * 2, CHECKS_MAX_DELAY_MS);
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
+
async getFailedCiLogs(pr) {
|
|
125
|
+
const head = await this.runCmd('gh', ['pr', 'view', String(pr), '--json', 'headRefName,headRefOid'], { cwd: this.cwd });
|
|
126
|
+
if (head.exitCode !== 0)
|
|
127
|
+
return [];
|
|
128
|
+
const parsedHead = safeJson(head.stdout);
|
|
129
|
+
const branch = isRecord(parsedHead) ? parsedHead.headRefName : undefined;
|
|
130
|
+
const sha = isRecord(parsedHead) ? parsedHead.headRefOid : undefined;
|
|
131
|
+
if (typeof branch !== 'string')
|
|
132
|
+
return [];
|
|
133
|
+
const runIds = await this.failedRunIds(branch, typeof sha === 'string' ? sha : undefined);
|
|
134
|
+
if (runIds.length === 0)
|
|
135
|
+
return [];
|
|
136
|
+
const { owner, name } = await this.repoMeta();
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const runId of runIds) {
|
|
139
|
+
for (const job of await this.failedJobs(owner, name, runId)) {
|
|
140
|
+
const logs = await this.jobLogs(owner, name, job.id);
|
|
141
|
+
if (logs.trim())
|
|
142
|
+
out.push({ check: job.name, logs });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
async failedRunIds(branch, sha) {
|
|
148
|
+
const r = await this.runCmd('gh', [
|
|
149
|
+
'run',
|
|
150
|
+
'list',
|
|
151
|
+
'--branch',
|
|
152
|
+
branch,
|
|
153
|
+
'--json',
|
|
154
|
+
'databaseId,headSha,conclusion',
|
|
155
|
+
'--limit',
|
|
156
|
+
'30',
|
|
157
|
+
], { cwd: this.cwd });
|
|
158
|
+
if (r.exitCode !== 0)
|
|
159
|
+
return [];
|
|
160
|
+
const parsed = safeJson(r.stdout);
|
|
161
|
+
const rows = WorkflowRunsSchema.safeParse(parsed);
|
|
162
|
+
if (!rows.success)
|
|
163
|
+
return [];
|
|
164
|
+
const failed = rows.data.filter((run) => FAILED_CONCLUSIONS.has(run.conclusion ?? ''));
|
|
165
|
+
const forSha = sha ? failed.filter((run) => run.headSha === sha) : [];
|
|
166
|
+
return (forSha.length > 0 ? forSha : failed).map((run) => run.databaseId);
|
|
167
|
+
}
|
|
168
|
+
async failedJobs(owner, name, runId) {
|
|
169
|
+
const r = await this.runCmd('gh', ['api', `repos/${owner}/${name}/actions/runs/${runId}/jobs`], { cwd: this.cwd });
|
|
170
|
+
if (r.exitCode !== 0)
|
|
171
|
+
return [];
|
|
172
|
+
const parsed = JobsResponseSchema.safeParse(safeJson(r.stdout));
|
|
173
|
+
if (!parsed.success)
|
|
174
|
+
return [];
|
|
175
|
+
return parsed.data.jobs
|
|
176
|
+
.filter((job) => FAILED_CONCLUSIONS.has(job.conclusion ?? ''))
|
|
177
|
+
.map((job) => ({ id: job.id, name: job.name }));
|
|
178
|
+
}
|
|
179
|
+
async jobLogs(owner, name, jobId) {
|
|
180
|
+
const r = await this.runCmd('gh', ['api', `repos/${owner}/${name}/actions/jobs/${jobId}/logs`], {
|
|
181
|
+
cwd: this.cwd,
|
|
182
|
+
});
|
|
183
|
+
return r.exitCode === 0 ? r.stdout : '';
|
|
184
|
+
}
|
|
124
185
|
async listUnresolvedThreads(pr) {
|
|
125
186
|
const { owner, name } = await this.repoMeta();
|
|
126
187
|
const threads = await this.paginateReviewThreads(owner, name, pr);
|
|
@@ -249,6 +310,30 @@ export class GitHubClient {
|
|
|
249
310
|
return { ok: r.exitCode === 0, scopes };
|
|
250
311
|
}
|
|
251
312
|
}
|
|
313
|
+
const FAILED_CONCLUSIONS = new Set(['failure', 'timed_out', 'startup_failure', 'action_required']);
|
|
314
|
+
const WorkflowRunsSchema = z.array(z.object({
|
|
315
|
+
databaseId: z.number(),
|
|
316
|
+
headSha: z.string().optional(),
|
|
317
|
+
conclusion: z.string().nullable().optional(),
|
|
318
|
+
}));
|
|
319
|
+
const JobsResponseSchema = z.object({
|
|
320
|
+
jobs: z.array(z.object({
|
|
321
|
+
id: z.number(),
|
|
322
|
+
name: z.string(),
|
|
323
|
+
conclusion: z.string().nullable().optional(),
|
|
324
|
+
})),
|
|
325
|
+
});
|
|
326
|
+
function safeJson(s) {
|
|
327
|
+
try {
|
|
328
|
+
return JSON.parse(s);
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function isRecord(v) {
|
|
335
|
+
return typeof v === 'object' && v !== null;
|
|
336
|
+
}
|
|
252
337
|
function isPrNotFoundStderr(stderr) {
|
|
253
338
|
return /no pull requests? found|could not resolve to a pullrequest|no open pull requests/i.test(stderr);
|
|
254
339
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve as resolvePath } from 'node:path';
|
|
2
|
-
import { bashTool, composeSystemPrompt, editFileTool, globTool, grepTool, multiEditTool, readFileTool, writeFileTool, } from '@developerz.ai/ai-claude-compat';
|
|
2
|
+
import { bashTool, composeSystemPrompt, editFileTool, globTool, grepTool, multiBashTool, multiEditTool, readFileTool, writeFileTool, } from '@developerz.ai/ai-claude-compat';
|
|
3
3
|
import { tool } from 'ai';
|
|
4
4
|
import { execa } from 'execa';
|
|
5
5
|
import { z } from 'zod';
|
|
@@ -20,6 +20,7 @@ export function localEditTools(cwd) {
|
|
|
20
20
|
grep: grepTool({ cwd }),
|
|
21
21
|
glob: globTool({ cwd }),
|
|
22
22
|
bash: bashTool({ cwd }),
|
|
23
|
+
multiBash: multiBashTool({ cwd }),
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
export function localReadTools(cwd) {
|
|
@@ -156,6 +157,7 @@ function defaultMakeOrchestrator(ctx) {
|
|
|
156
157
|
baseBranch,
|
|
157
158
|
styleContents: style,
|
|
158
159
|
rollingContext,
|
|
160
|
+
...(input.resolved.formatCommand ? { formatCommand: input.resolved.formatCommand } : {}),
|
|
159
161
|
});
|
|
160
162
|
},
|
|
161
163
|
finalizeCommit: (group, delivery, worktreePath) => orch.finalizeCommit(group, delivery, worktreePath),
|
|
@@ -191,6 +193,7 @@ function resolveWorkerTools(set, cwd) {
|
|
|
191
193
|
grep: set.grep ?? local.grep,
|
|
192
194
|
glob: set.glob ?? local.glob,
|
|
193
195
|
bash: set.bash ?? local.bash,
|
|
196
|
+
multiBash: set.multiBash ?? local.multiBash,
|
|
194
197
|
};
|
|
195
198
|
}
|
|
196
199
|
function resolveReviewerTools(set, cwd, github) {
|
|
@@ -11,6 +11,18 @@ export type TakeOverGithub = {
|
|
|
11
11
|
mergePr(pr: number, method: MergeMethod): Promise<void>;
|
|
12
12
|
replyToThread(threadId: string, body: string): Promise<void>;
|
|
13
13
|
resolveThread(threadId: string): Promise<void>;
|
|
14
|
+
getFailedCiLogs?(pr: number): Promise<Array<{
|
|
15
|
+
check: string;
|
|
16
|
+
logs: string;
|
|
17
|
+
}>>;
|
|
18
|
+
};
|
|
19
|
+
export type PrContextPort = {
|
|
20
|
+
clear(pr: number): Promise<void>;
|
|
21
|
+
saveCiFailures(pr: number, failures: ReadonlyArray<{
|
|
22
|
+
check: string;
|
|
23
|
+
logs: string;
|
|
24
|
+
}>): Promise<string | null>;
|
|
25
|
+
saveComments(pr: number, threads: readonly ReviewThread[]): Promise<string | null>;
|
|
14
26
|
};
|
|
15
27
|
export type TakeOverSubagents = {
|
|
16
28
|
reviewerModel: LanguageModel;
|
|
@@ -18,6 +30,7 @@ export type TakeOverSubagents = {
|
|
|
18
30
|
workerModel: LanguageModel;
|
|
19
31
|
workerTools: WorkerTools;
|
|
20
32
|
styleContents: string;
|
|
33
|
+
formatCommand?: string;
|
|
21
34
|
runReviewerOverride?: (input: {
|
|
22
35
|
pr: number;
|
|
23
36
|
threads: ReviewThread[];
|
|
@@ -38,6 +51,7 @@ export type TakeOverFlowInput = {
|
|
|
38
51
|
baseBranch: string;
|
|
39
52
|
github: TakeOverGithub;
|
|
40
53
|
subagents: TakeOverSubagents;
|
|
54
|
+
prContext?: PrContextPort;
|
|
41
55
|
mergeMethod: MergeMethod;
|
|
42
56
|
maxIterations?: number;
|
|
43
57
|
cooldownMs?: number;
|
|
@@ -19,9 +19,24 @@ export async function runTakeOverFlow(input) {
|
|
|
19
19
|
if (ciStatus === 'success' && threads.length === 0) {
|
|
20
20
|
break;
|
|
21
21
|
}
|
|
22
|
+
let ciLogsDir = null;
|
|
23
|
+
if (input.prContext) {
|
|
24
|
+
await input.prContext.clear(input.pr);
|
|
25
|
+
if ((ciStatus === 'failure' || ciStatus === 'cancelled') && input.github.getFailedCiLogs) {
|
|
26
|
+
const failures = await input.github.getFailedCiLogs(input.pr);
|
|
27
|
+
ciLogsDir = await input.prContext.saveCiFailures(input.pr, failures);
|
|
28
|
+
log?.info('take-over: downloaded ci logs', {
|
|
29
|
+
pr: input.pr,
|
|
30
|
+
checks: failures.length,
|
|
31
|
+
dir: ciLogsDir,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (threads.length > 0)
|
|
35
|
+
await input.prContext.saveComments(input.pr, threads);
|
|
36
|
+
}
|
|
22
37
|
let pushedSomething = false;
|
|
23
38
|
if (ciStatus === 'failure' || ciStatus === 'cancelled') {
|
|
24
|
-
const fixed = await runWorkerCiFix(input);
|
|
39
|
+
const fixed = await runWorkerCiFix(input, ciLogsDir);
|
|
25
40
|
if (fixed.kind === 'blocked') {
|
|
26
41
|
return { kind: 'blocked', reason: fixed.reason, iterations: iteration };
|
|
27
42
|
}
|
|
@@ -104,14 +119,14 @@ async function runReviewerThreads(input, threads) {
|
|
|
104
119
|
styleContents: input.subagents.styleContents,
|
|
105
120
|
});
|
|
106
121
|
}
|
|
107
|
-
async function runWorkerCiFix(input) {
|
|
122
|
+
async function runWorkerCiFix(input, ciLogsDir) {
|
|
123
|
+
const readTask = ciLogsDir
|
|
124
|
+
? `Read the downloaded CI failure logs in ${ciLogsDir} (one file per failed check, full untruncated logs) with your shell/read tools, then fix every failure those logs report.`
|
|
125
|
+
: `Read the CI logs (via gh) and fix every failing check on PR #${input.pr}.`;
|
|
108
126
|
const group = {
|
|
109
127
|
id: `takeover-ci-${input.pr}`,
|
|
110
128
|
title: `Fix CI failures on PR #${input.pr}`,
|
|
111
|
-
tasks: [
|
|
112
|
-
`Read the CI logs (via gh) and fix every failing check on PR #${input.pr}.`,
|
|
113
|
-
'Run the project test/lint commands locally to verify, then stage fixes.',
|
|
114
|
-
],
|
|
129
|
+
tasks: [readTask, 'Run the project test/lint commands locally to verify, then stage fixes.'],
|
|
115
130
|
dependsOn: [],
|
|
116
131
|
branch: null,
|
|
117
132
|
pr: input.pr,
|
|
@@ -137,6 +152,7 @@ async function runWorkerCiFix(input) {
|
|
|
137
152
|
baseBranch: input.baseBranch,
|
|
138
153
|
styleContents: input.subagents.styleContents,
|
|
139
154
|
rollingContext: '',
|
|
155
|
+
...(input.subagents.formatCommand ? { formatCommand: input.subagents.formatCommand } : {}),
|
|
140
156
|
});
|
|
141
157
|
}
|
|
142
158
|
function defaultSleep(ms) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReviewThread } from '../github/schema.ts';
|
|
2
|
+
export type CiFailure = {
|
|
3
|
+
check: string;
|
|
4
|
+
logs: string;
|
|
5
|
+
};
|
|
6
|
+
export type PrContextSummary = {
|
|
7
|
+
prDir: string;
|
|
8
|
+
ciDir: string | null;
|
|
9
|
+
commentsDir: string | null;
|
|
10
|
+
ciCount: number;
|
|
11
|
+
commentCount: number;
|
|
12
|
+
};
|
|
13
|
+
export declare class PrContextStore {
|
|
14
|
+
private readonly stateDir;
|
|
15
|
+
constructor(stateDir: string);
|
|
16
|
+
prDir(pr: number): string;
|
|
17
|
+
clear(pr: number): Promise<void>;
|
|
18
|
+
saveCiFailures(pr: number, failures: readonly CiFailure[]): Promise<string | null>;
|
|
19
|
+
saveComments(pr: number, threads: readonly ReviewThread[]): Promise<string | null>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export class PrContextStore {
|
|
4
|
+
stateDir;
|
|
5
|
+
constructor(stateDir) {
|
|
6
|
+
this.stateDir = stateDir;
|
|
7
|
+
}
|
|
8
|
+
prDir(pr) {
|
|
9
|
+
return join(this.stateDir, 'debugging', 'pr', String(pr));
|
|
10
|
+
}
|
|
11
|
+
async clear(pr) {
|
|
12
|
+
await rm(this.prDir(pr), { recursive: true, force: true });
|
|
13
|
+
}
|
|
14
|
+
async saveCiFailures(pr, failures) {
|
|
15
|
+
if (failures.length === 0)
|
|
16
|
+
return null;
|
|
17
|
+
const ciDir = join(this.prDir(pr), 'ci');
|
|
18
|
+
await mkdir(ciDir, { recursive: true });
|
|
19
|
+
const used = new Map();
|
|
20
|
+
for (const { check, logs } of failures) {
|
|
21
|
+
const base = sanitize(check);
|
|
22
|
+
const n = used.get(base) ?? 0;
|
|
23
|
+
used.set(base, n + 1);
|
|
24
|
+
const file = n === 0 ? `failed_${base}.txt` : `failed_${base}_${n}.txt`;
|
|
25
|
+
const header = `CI check failed: ${check}\nPR: #${pr}\n${'='.repeat(60)}\n\n`;
|
|
26
|
+
await writeFile(join(ciDir, file), header + logs);
|
|
27
|
+
}
|
|
28
|
+
await writeFile(join(ciDir, 'summary.txt'), [
|
|
29
|
+
`PR #${pr} — ${failures.length} failed check(s):`,
|
|
30
|
+
...failures.map((f) => ` - ${f.check}`),
|
|
31
|
+
].join('\n'));
|
|
32
|
+
return ciDir;
|
|
33
|
+
}
|
|
34
|
+
async saveComments(pr, threads) {
|
|
35
|
+
if (threads.length === 0)
|
|
36
|
+
return null;
|
|
37
|
+
const commentsDir = join(this.prDir(pr), 'comments');
|
|
38
|
+
await mkdir(commentsDir, { recursive: true });
|
|
39
|
+
let i = 0;
|
|
40
|
+
for (const thread of threads) {
|
|
41
|
+
i += 1;
|
|
42
|
+
const path = thread.path ?? 'general';
|
|
43
|
+
const body = thread.comments
|
|
44
|
+
.map((c) => `@${c.author}:\n${c.body}`)
|
|
45
|
+
.join(`\n${'-'.repeat(40)}\n`);
|
|
46
|
+
const header = `Review thread on ${path} (thread ${thread.id})\nPR: #${pr}\n${'='.repeat(60)}\n\n`;
|
|
47
|
+
await writeFile(join(commentsDir, `${String(i).padStart(3, '0')}_${sanitize(path)}.txt`), header + body);
|
|
48
|
+
}
|
|
49
|
+
const paths = [...new Set(threads.map((t) => t.path ?? 'general'))].sort();
|
|
50
|
+
await writeFile(join(commentsDir, 'summary.txt'), [
|
|
51
|
+
`PR #${pr} — ${threads.length} unresolved review thread(s).`,
|
|
52
|
+
'Files with comments:',
|
|
53
|
+
...paths.map((p) => ` - ${p}`),
|
|
54
|
+
].join('\n'));
|
|
55
|
+
return commentsDir;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function sanitize(s) {
|
|
59
|
+
return s.replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/^_+|_+$/g, '') || 'unnamed';
|
|
60
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BashInput, BashOutput, EditFileInput, EditFileOutput, GlobInput, GlobOutput, GrepInput, GrepOutput, MultiEditInput, MultiEditOutput, ReadFileInput, ReadFileOutput, WriteFileInput, WriteFileOutput } from '@developerz.ai/ai-claude-compat';
|
|
1
|
+
import type { BashInput, BashOutput, EditFileInput, EditFileOutput, GlobInput, GlobOutput, GrepInput, GrepOutput, MultiBashInput, MultiBashOutput, MultiEditInput, MultiEditOutput, ReadFileInput, ReadFileOutput, WriteFileInput, WriteFileOutput } from '@developerz.ai/ai-claude-compat';
|
|
2
2
|
import { type DeepPartial, Output, type Tool, type ToolLoopAgent } from 'ai';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import type { PrGroup } from '../state/schema.ts';
|
|
@@ -11,6 +11,7 @@ export type WorkerTools = {
|
|
|
11
11
|
grep: Tool<GrepInput, GrepOutput>;
|
|
12
12
|
glob: Tool<GlobInput, GlobOutput>;
|
|
13
13
|
bash: Tool<BashInput, BashOutput>;
|
|
14
|
+
multiBash: Tool<MultiBashInput, MultiBashOutput>;
|
|
14
15
|
};
|
|
15
16
|
export declare const FileManifestEntrySchema: z.ZodObject<{
|
|
16
17
|
path: z.ZodString;
|
|
@@ -43,6 +44,7 @@ export type WorkerInput = {
|
|
|
43
44
|
baseBranch: string;
|
|
44
45
|
styleContents: string;
|
|
45
46
|
rollingContext: string;
|
|
47
|
+
formatCommand?: string;
|
|
46
48
|
};
|
|
47
49
|
export type FileChange = {
|
|
48
50
|
path: string;
|
package/dist/subagents/worker.js
CHANGED
|
@@ -36,6 +36,9 @@ const EDITOR_SYSTEM_PREFIX = [
|
|
|
36
36
|
' replacement) or `multiEdit` (several replacements applied atomically). Use `writeFile` only',
|
|
37
37
|
' for a full rewrite.',
|
|
38
38
|
'- To DELETE a file, use `bash` with `rm -f <path>`.',
|
|
39
|
+
'- For a dependent sequence of shell steps (e.g. `mkdir … && generate && test`), prefer',
|
|
40
|
+
' `multiBash` with an ordered `commands` array — it stops at the first failure, so you',
|
|
41
|
+
' see exactly which step broke without chaining `&&` by hand.',
|
|
39
42
|
'You may issue multiple tool calls in parallel.',
|
|
40
43
|
'',
|
|
41
44
|
"IMPORTANT: your final assistant message is returned to the outer Worker as this file's",
|
|
@@ -65,7 +68,10 @@ export async function runWorker(agent, input) {
|
|
|
65
68
|
try {
|
|
66
69
|
const manifest = await planManifest(agent, input);
|
|
67
70
|
if (manifest.files.length === 0) {
|
|
68
|
-
return {
|
|
71
|
+
return {
|
|
72
|
+
kind: 'blocked',
|
|
73
|
+
reason: 'The Worker returned an empty file manifest — the configured coding model produced no files to change for this PR group. This usually means the model is not capable enough to plan the work; try a more capable coding model (set `models.coding` in .ai-task-master/config.json or pass a stronger --model).',
|
|
74
|
+
};
|
|
69
75
|
}
|
|
70
76
|
const changes = await Promise.all(manifest.files.map((file) => runEditor(init, file, input)));
|
|
71
77
|
await commitOnBranch(init.tools.bash, input, branch, manifest.draftCommitMessage);
|
|
@@ -133,6 +139,9 @@ async function commitOnBranch(bash, input, branch, message) {
|
|
|
133
139
|
}
|
|
134
140
|
const wt = shQuote(input.worktreePath);
|
|
135
141
|
await runBash(exec, `git -C ${wt} checkout -B ${shQuote(branch)}`);
|
|
142
|
+
if (input.formatCommand) {
|
|
143
|
+
await runBash(exec, `cd ${wt} && ${input.formatCommand}`);
|
|
144
|
+
}
|
|
136
145
|
await runBash(exec, `git -C ${wt} add -A`);
|
|
137
146
|
await runBash(exec, `git -C ${wt} commit -m ${shQuote(message)}`);
|
|
138
147
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@developerz.ai/aitm",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Autonomous task orchestrator. Goal in, merged PRs out.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@ai-sdk/mcp": "^1.0.42",
|
|
45
|
-
"@developerz.ai/ai-claude-compat": "0.0.
|
|
45
|
+
"@developerz.ai/ai-claude-compat": "0.0.4",
|
|
46
46
|
"@openrouter/ai-sdk-provider": "^2.9.0",
|
|
47
47
|
"ai": "^6.0.182",
|
|
48
48
|
"execa": "^9.6.1",
|