@developerz.ai/aitm 0.0.3 → 0.0.5

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.
@@ -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,9 +1,8 @@
1
1
  import type { GlobInput, GlobOutput, GrepInput, GrepOutput, ReadFileInput, ReadFileOutput } from '@developerz.ai/ai-claude-compat';
2
- import { type DeepPartial, Output, type Tool, type ToolLoopAgent } from 'ai';
2
+ import { type Tool, type ToolLoopAgent } from 'ai';
3
3
  import { type Plan } from '../plan/schema.ts';
4
4
  import type { SubagentInit } from './factory.ts';
5
- type PlannerOutput = Output.Output<Plan, DeepPartial<Plan>, never>;
6
- export type PlannerAgent = ToolLoopAgent<never, PlannerTools, PlannerOutput>;
5
+ export type PlannerAgent = ToolLoopAgent<never, PlannerTools>;
7
6
  export type PlannerTools = {
8
7
  readFile: Tool<ReadFileInput, ReadFileOutput>;
9
8
  grep: Tool<GrepInput, GrepOutput>;
@@ -28,4 +27,3 @@ export type PlannerResult = {
28
27
  export declare const PLANNER_SYSTEM_PREFIX: string;
29
28
  export declare function createPlannerAgent(init: SubagentInit<PlannerTools>): PlannerAgent;
30
29
  export declare function runPlanner(agent: PlannerAgent, input: PlannerInput): Promise<PlannerResult>;
31
- export {};
@@ -1,5 +1,5 @@
1
- import { createSubagent } from '@developerz.ai/ai-claude-compat';
2
- import { Output } from 'ai';
1
+ import { createSubagent, submittedOutput } from '@developerz.ai/ai-claude-compat';
2
+ import { tool } from 'ai';
3
3
  import { PlanSchema } from "../plan/schema.js";
4
4
  export const PLANNER_SYSTEM_PREFIX = [
5
5
  '',
@@ -15,14 +15,18 @@ export const PLANNER_SYSTEM_PREFIX = [
15
15
  '- Prefer parallelizable siblings over a single linear chain.',
16
16
  '- Do not invent files. Do not propose work outside the repo.',
17
17
  '',
18
- 'Return JSON that matches the Plan schema exactly.',
18
+ 'When the plan is ready, call the `submit` tool exactly once with the Plan (matching the Plan schema).',
19
19
  ].join('\n');
20
20
  export function createPlannerAgent(init) {
21
21
  return createSubagent({
22
22
  model: init.model,
23
23
  tools: init.tools,
24
24
  systemPrompt: init.systemPrompt,
25
- output: plannerOutput(),
25
+ submit: tool({
26
+ description: 'Submit the finished plan as an ordered list of PR groups (the Plan schema).',
27
+ inputSchema: PlanSchema,
28
+ execute: async (plan) => plan,
29
+ }),
26
30
  ...(init.maxSteps !== undefined ? { maxSteps: init.maxSteps } : {}),
27
31
  }, 20);
28
32
  }
@@ -32,8 +36,8 @@ export async function runPlanner(agent, input) {
32
36
  }
33
37
  try {
34
38
  const result = await agent.generate({ prompt: buildUserPrompt(input) });
35
- const raw = result.experimental_output;
36
- if (!raw.groups || raw.groups.length === 0) {
39
+ const raw = submittedOutput(result, PlanSchema);
40
+ if (!raw || raw.groups.length === 0) {
37
41
  return { kind: 'blocked', reason: 'planner returned an empty group list' };
38
42
  }
39
43
  return { kind: 'ok', plan: capGroups(raw, input.maxPrs) };
@@ -42,16 +46,13 @@ export async function runPlanner(agent, input) {
42
46
  return { kind: 'error', error: err instanceof Error ? err.message : String(err) };
43
47
  }
44
48
  }
45
- function plannerOutput() {
46
- return Output.object({ schema: PlanSchema, name: 'Plan' });
47
- }
48
49
  function buildUserPrompt(input) {
49
50
  const lines = [`Goal: ${input.goal}`];
50
51
  if (input.criteria?.trim()) {
51
52
  lines.push(`Acceptance criteria: ${input.criteria}`);
52
53
  }
53
54
  lines.push(`maxPrs: ${input.maxPrs}`);
54
- lines.push('Survey the repo with the read-only tools, then emit the Plan JSON.');
55
+ lines.push('Survey the repo with the read-only tools, then call submit with the Plan.');
55
56
  return lines.join('\n');
56
57
  }
57
58
  function capGroups(plan, maxPrs) {
@@ -1,4 +1,4 @@
1
- import { type DeepPartial, Output, type Tool, type ToolLoopAgent } from 'ai';
1
+ import { type Tool, type ToolLoopAgent } from 'ai';
2
2
  import { z } from 'zod';
3
3
  import type { ReviewThread } from '../github/schema.ts';
4
4
  import type { SubagentInit } from './factory.ts';
@@ -24,8 +24,7 @@ export declare const ThreadResolutionOutputSchema: z.ZodObject<{
24
24
  reason: z.ZodOptional<z.ZodString>;
25
25
  }, z.core.$strip>;
26
26
  export type ThreadResolutionOutput = z.infer<typeof ThreadResolutionOutputSchema>;
27
- type ReviewerAgentOutput = Output.Output<ThreadResolutionOutput, DeepPartial<ThreadResolutionOutput>, never>;
28
- export type ReviewerAgent = ToolLoopAgent<never, ReviewerTools, ReviewerAgentOutput>;
27
+ export type ReviewerAgent = ToolLoopAgent<never, ReviewerTools>;
29
28
  export type ReviewerInput = {
30
29
  pr: number;
31
30
  threads: ReviewThread[];
@@ -57,4 +56,3 @@ export type ReviewerResult = {
57
56
  export declare const REVIEWER_SYSTEM_PREFIX: string;
58
57
  export declare function createReviewerAgent(init: SubagentInit<ReviewerTools>): ReviewerAgent;
59
58
  export declare function runReviewer(agent: ReviewerAgent, input: ReviewerInput): Promise<ReviewerResult>;
60
- export {};
@@ -1,5 +1,5 @@
1
- import { createSubagent } from '@developerz.ai/ai-claude-compat';
2
- import { Output } from 'ai';
1
+ import { createSubagent, submittedOutput, } from '@developerz.ai/ai-claude-compat';
2
+ import { tool } from 'ai';
3
3
  import { z } from 'zod';
4
4
  export const ThreadResolutionOutputSchema = z.object({
5
5
  kind: z.enum(['fixed', 'replied', 'wontfix']),
@@ -9,24 +9,24 @@ export const ThreadResolutionOutputSchema = z.object({
9
9
  export const REVIEWER_SYSTEM_PREFIX = [
10
10
  '',
11
11
  'You are the Reviewer subagent. You receive ONE unresolved PR review thread at a time and',
12
- 'decide between three outcomes, emitting a ThreadResolutionOutput JSON that names the choice.',
12
+ 'decide between three outcomes, then call the `submit` tool with a ThreadResolutionOutput naming the choice.',
13
13
  '',
14
14
  '- "fixed": the reviewer is right and a code change is needed. Use your tools (grep/glob/',
15
15
  ' readFile to locate, editFile/multiEdit to change, writeFile to rewrite, bash for the rest)',
16
16
  ' to make the fix inside the worktree. DO NOT run `git commit` yourself — the runner commits',
17
17
  ' every staged change after you finish. Reply on the thread via the github tool explaining',
18
- ' the fix and resolve the thread, then emit { kind: "fixed", commitMessage } where',
18
+ ' the fix and resolve the thread, then submit { kind: "fixed", commitMessage } where',
19
19
  ' commitMessage is the subject line the runner will pass to `git commit`.',
20
20
  '- "replied": the comment is a question or clarification request and no code change is needed.',
21
- ' Answer it via github.replyToThread. Do not edit code. Emit { kind: "replied" }.',
21
+ ' Answer it via github.replyToThread. Do not edit code. Submit { kind: "replied" }.',
22
22
  '- "wontfix": the suggestion is stale, out of scope, or you disagree. Reply with the reason',
23
- ' via github.replyToThread, resolve the thread via github.resolveThread, and emit',
23
+ ' via github.replyToThread, resolve the thread via github.resolveThread, and submit',
24
24
  ' { kind: "wontfix", reason }.',
25
25
  '',
26
26
  'Rules:',
27
27
  '- Stay inside the worktree. No work outside the repo.',
28
28
  '- Resolve the thread for "fixed" and "wontfix" outcomes; "replied" leaves it open.',
29
- '- Return JSON that matches the ThreadResolutionOutput schema exactly.',
29
+ '- When done, call `submit` once with a value matching the ThreadResolutionOutput schema.',
30
30
  ].join('\n');
31
31
  const reviewerInitRegistry = new WeakMap();
32
32
  export function createReviewerAgent(init) {
@@ -34,9 +34,10 @@ export function createReviewerAgent(init) {
34
34
  model: init.model,
35
35
  tools: init.tools,
36
36
  systemPrompt: init.systemPrompt,
37
- output: Output.object({
38
- schema: ThreadResolutionOutputSchema,
39
- name: 'ThreadResolution',
37
+ submit: tool({
38
+ description: 'Submit the resolution for this review thread (the ThreadResolutionOutput schema).',
39
+ inputSchema: ThreadResolutionOutputSchema,
40
+ execute: async (resolution) => resolution,
40
41
  }),
41
42
  ...(init.maxSteps !== undefined ? { maxSteps: init.maxSteps } : {}),
42
43
  }, 20);
@@ -67,7 +68,10 @@ export async function runReviewer(agent, input) {
67
68
  }
68
69
  async function resolveOneThread(agent, init, input, thread) {
69
70
  const result = await agent.generate({ prompt: buildThreadPrompt(input, thread) });
70
- const out = result.experimental_output;
71
+ const out = submittedOutput(result, ThreadResolutionOutputSchema);
72
+ if (!out) {
73
+ return { threadId: thread.id, kind: 'wontfix', reason: 'reviewer did not submit a resolution' };
74
+ }
71
75
  switch (out.kind) {
72
76
  case 'fixed': {
73
77
  const message = out.commitMessage?.trim() || `fix: address review thread ${thread.id}`;
@@ -92,7 +96,7 @@ function buildThreadPrompt(input, thread) {
92
96
  for (const c of thread.comments) {
93
97
  lines.push(` @${c.author}: ${c.body}`);
94
98
  }
95
- lines.push('', 'Decide the outcome, take the action, then emit the ThreadResolutionOutput JSON.');
99
+ lines.push('', 'Decide the outcome, take the action, then call submit with the ThreadResolutionOutput.');
96
100
  return lines.join('\n');
97
101
  }
98
102
  async function commitFix(bash, worktreePath, message) {
@@ -1,5 +1,5 @@
1
- import type { BashInput, BashOutput, EditFileInput, EditFileOutput, GlobInput, GlobOutput, GrepInput, GrepOutput, MultiEditInput, MultiEditOutput, ReadFileInput, ReadFileOutput, WriteFileInput, WriteFileOutput } from '@developerz.ai/ai-claude-compat';
2
- import { type DeepPartial, Output, type Tool, type ToolLoopAgent } from 'ai';
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
+ import { type Tool, type ToolLoopAgent } from 'ai';
3
3
  import { z } from 'zod';
4
4
  import type { PrGroup } from '../state/schema.ts';
5
5
  import type { SubagentInit } from './factory.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;
@@ -35,14 +36,14 @@ export declare const FileManifestSchema: z.ZodObject<{
35
36
  draftCommitMessage: z.ZodString;
36
37
  }, z.core.$strip>;
37
38
  export type FileManifest = z.infer<typeof FileManifestSchema>;
38
- type WorkerOutput = Output.Output<FileManifest, DeepPartial<FileManifest>, never>;
39
- export type WorkerAgent = ToolLoopAgent<never, WorkerTools, WorkerOutput>;
39
+ export type WorkerAgent = ToolLoopAgent<never, WorkerTools>;
40
40
  export type WorkerInput = {
41
41
  group: PrGroup;
42
42
  worktreePath: string;
43
43
  baseBranch: string;
44
44
  styleContents: string;
45
45
  rollingContext: string;
46
+ formatCommand?: string;
46
47
  };
47
48
  export type FileChange = {
48
49
  path: string;
@@ -68,4 +69,3 @@ export type WorkerResult = {
68
69
  export declare const WORKER_SYSTEM_PREFIX: string;
69
70
  export declare function createWorkerAgent(init: SubagentInit<WorkerTools>): WorkerAgent;
70
71
  export declare function runWorker(agent: WorkerAgent, input: WorkerInput): Promise<WorkerResult>;
71
- export {};
@@ -1,5 +1,5 @@
1
- import { composeSystemPrompt, createSubagent } from '@developerz.ai/ai-claude-compat';
2
- import { generateText, Output, stepCountIs, } from 'ai';
1
+ import { composeSystemPrompt, createSubagent, submittedOutput, } from '@developerz.ai/ai-claude-compat';
2
+ import { generateText, stepCountIs, tool } from 'ai';
3
3
  import { z } from 'zod';
4
4
  export const FileManifestEntrySchema = z.object({
5
5
  path: z.string().min(1),
@@ -16,8 +16,8 @@ export const WORKER_SYSTEM_PREFIX = [
16
16
  'land in a single pull request on a dedicated branch. Work in two phases.',
17
17
  '',
18
18
  'Phase 1 — manifest. Use your read-only tools (readFile with optional offset/limit, grep,',
19
- 'glob) to ground yourself in the existing code, then emit a FileManifest JSON listing every',
20
- 'file to create/modify/delete plus a one-line draft commit message. Do not edit yet.',
19
+ 'glob) to ground yourself in the existing code, then call the `submit` tool with a FileManifest',
20
+ 'listing every file to create/modify/delete plus a one-line draft commit message. Do not edit yet.',
21
21
  '',
22
22
  'Phase 2 — edits. Each manifest entry is handed to a dedicated editor subagent in',
23
23
  'parallel by the runtime; you do not execute Phase 2 yourself.',
@@ -26,7 +26,7 @@ export const WORKER_SYSTEM_PREFIX = [
26
26
  '- Stay inside the worktree provided. No work outside the repo.',
27
27
  '- One responsibility per file. If a file has multiple unrelated edits, split it.',
28
28
  '- draftCommitMessage is a hint to the Orchestrator; keep the subject under 72 chars.',
29
- '- Return the FileManifest JSON exactly matching the schema.',
29
+ '- When the manifest is complete, call `submit` once with the FileManifest (matching the schema).',
30
30
  ].join('\n');
31
31
  const EDITOR_SYSTEM_PREFIX = [
32
32
  '',
@@ -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",
@@ -47,7 +50,11 @@ export function createWorkerAgent(init) {
47
50
  model: init.model,
48
51
  tools: init.tools,
49
52
  systemPrompt: init.systemPrompt,
50
- output: Output.object({ schema: FileManifestSchema, name: 'FileManifest' }),
53
+ submit: tool({
54
+ description: 'Submit the file manifest (the FileManifest schema) for this PR group.',
55
+ inputSchema: FileManifestSchema,
56
+ execute: async (manifest) => manifest,
57
+ }),
51
58
  ...(init.maxSteps !== undefined ? { maxSteps: init.maxSteps } : {}),
52
59
  }, 30);
53
60
  workerInitRegistry.set(agent, init);
@@ -65,7 +72,10 @@ export async function runWorker(agent, input) {
65
72
  try {
66
73
  const manifest = await planManifest(agent, input);
67
74
  if (manifest.files.length === 0) {
68
- return { kind: 'blocked', reason: 'worker produced an empty file manifest' };
75
+ return {
76
+ kind: 'blocked',
77
+ 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).',
78
+ };
69
79
  }
70
80
  const changes = await Promise.all(manifest.files.map((file) => runEditor(init, file, input)));
71
81
  await commitOnBranch(init.tools.bash, input, branch, manifest.draftCommitMessage);
@@ -85,7 +95,7 @@ export async function runWorker(agent, input) {
85
95
  }
86
96
  async function planManifest(agent, input) {
87
97
  const result = await agent.generate({ prompt: buildManifestPrompt(input) });
88
- return result.experimental_output;
98
+ return submittedOutput(result, FileManifestSchema) ?? { files: [], draftCommitMessage: '' };
89
99
  }
90
100
  function buildManifestPrompt(input) {
91
101
  const lines = [
@@ -100,7 +110,7 @@ function buildManifestPrompt(input) {
100
110
  if (input.rollingContext.trim()) {
101
111
  lines.push('', 'Rolling context from prior PRs:', input.rollingContext);
102
112
  }
103
- lines.push('', 'Survey the repo, then emit the FileManifest JSON.');
113
+ lines.push('', 'Survey the repo, then call submit with the FileManifest.');
104
114
  return lines.join('\n');
105
115
  }
106
116
  async function runEditor(init, file, input) {
@@ -133,6 +143,9 @@ async function commitOnBranch(bash, input, branch, message) {
133
143
  }
134
144
  const wt = shQuote(input.worktreePath);
135
145
  await runBash(exec, `git -C ${wt} checkout -B ${shQuote(branch)}`);
146
+ if (input.formatCommand) {
147
+ await runBash(exec, `cd ${wt} && ${input.formatCommand}`);
148
+ }
136
149
  await runBash(exec, `git -C ${wt} add -A`);
137
150
  await runBash(exec, `git -C ${wt} commit -m ${shQuote(message)}`);
138
151
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@developerz.ai/aitm",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
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.3",
45
+ "@developerz.ai/ai-claude-compat": "0.0.5",
46
46
  "@openrouter/ai-sdk-provider": "^2.9.0",
47
47
  "ai": "^6.0.182",
48
48
  "execa": "^9.6.1",