@developerz.ai/aitm 0.0.2 → 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/agent-config/agent-config-detector.js +0 -3
- package/dist/cli/args.js +0 -10
- package/dist/cli/cli.js +0 -8
- package/dist/cli/commands.js +4 -46
- package/dist/compaction/compactor.js +0 -24
- package/dist/config/config-loader.js +3 -29
- package/dist/config/config-writer.js +0 -5
- package/dist/config/schema.d.ts +2 -0
- package/dist/config/schema.js +1 -12
- package/dist/credentials/credentials.js +0 -14
- package/dist/credentials/defaults.js +0 -15
- package/dist/fs/atomic-write.js +0 -7
- package/dist/github/errors.js +0 -2
- package/dist/github/github-client.d.ts +7 -0
- package/dist/github/github-client.js +85 -21
- package/dist/github/schema.js +0 -2
- package/dist/index.js +0 -2
- package/dist/logger/logger.js +0 -6
- package/dist/loop/run-loop-adapter.js +4 -46
- package/dist/loop/take-over-flow.d.ts +14 -0
- package/dist/loop/take-over-flow.js +22 -43
- package/dist/loop/work-loop.js +0 -36
- package/dist/mcp/mcp-client.js +0 -13
- package/dist/mcp/schema.js +0 -15
- package/dist/openrouter/client.js +0 -4
- package/dist/openrouter/model-limits.js +0 -4
- package/dist/openrouter/server-tools.js +0 -16
- package/dist/orchestrator/orchestrator.js +0 -26
- package/dist/orchestrator/subagent-tools.js +0 -22
- package/dist/orchestrator/system-prompts.js +0 -3
- package/dist/plan/plan-graph.js +0 -9
- package/dist/plan/schema.js +0 -8
- package/dist/state/pr-context-store.d.ts +20 -0
- package/dist/state/pr-context-store.js +60 -0
- package/dist/state/schema.js +0 -5
- package/dist/state/state-store.js +0 -8
- package/dist/subagents/factory.js +0 -9
- package/dist/subagents/planner.js +0 -9
- package/dist/subagents/reviewer.js +0 -25
- package/dist/subagents/worker.d.ts +3 -1
- package/dist/subagents/worker.js +10 -28
- package/dist/testing/temp-repo.js +0 -4
- package/dist/tools/datetime.js +0 -9
- package/dist/tools/fetch-html.js +0 -24
- package/dist/tools/github-thread-tool.js +0 -15
- package/dist/tools/web-fetch.js +0 -32
- package/dist/workspace/worktree-pool.js +0 -21
- package/package.json +2 -2
- package/dist/agent-config/agent-config-detector.js.map +0 -1
- package/dist/cli/args.js.map +0 -1
- package/dist/cli/cli.js.map +0 -1
- package/dist/cli/commands.js.map +0 -1
- package/dist/compaction/compactor.js.map +0 -1
- package/dist/config/config-loader.js.map +0 -1
- package/dist/config/config-writer.js.map +0 -1
- package/dist/config/schema.js.map +0 -1
- package/dist/credentials/credentials.js.map +0 -1
- package/dist/credentials/defaults.js.map +0 -1
- package/dist/fs/atomic-write.js.map +0 -1
- package/dist/github/errors.js.map +0 -1
- package/dist/github/github-client.js.map +0 -1
- package/dist/github/schema.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger/logger.js.map +0 -1
- package/dist/loop/run-loop-adapter.js.map +0 -1
- package/dist/loop/take-over-flow.js.map +0 -1
- package/dist/loop/work-loop.js.map +0 -1
- package/dist/mcp/mcp-client.js.map +0 -1
- package/dist/mcp/schema.js.map +0 -1
- package/dist/openrouter/client.js.map +0 -1
- package/dist/openrouter/model-limits.js.map +0 -1
- package/dist/openrouter/server-tools.js.map +0 -1
- package/dist/orchestrator/orchestrator.js.map +0 -1
- package/dist/orchestrator/subagent-tools.js.map +0 -1
- package/dist/orchestrator/system-prompts.js.map +0 -1
- package/dist/plan/plan-graph.js.map +0 -1
- package/dist/plan/schema.js.map +0 -1
- package/dist/state/schema.js.map +0 -1
- package/dist/state/state-store.js.map +0 -1
- package/dist/subagents/factory.js.map +0 -1
- package/dist/subagents/planner.js.map +0 -1
- package/dist/subagents/reviewer.js.map +0 -1
- package/dist/subagents/worker.js.map +0 -1
- package/dist/testing/temp-repo.js.map +0 -1
- package/dist/tools/datetime.js.map +0 -1
- package/dist/tools/fetch-html.js.map +0 -1
- package/dist/tools/github-thread-tool.js.map +0 -1
- package/dist/tools/web-fetch.js.map +0 -1
- package/dist/workspace/worktree-pool.js.map +0 -1
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// docs/agent-config-detection.md, docs/coding-style.md
|
|
2
|
-
// Style signal only — never selects a provider. Prefer CLAUDE.md over AGENTS.md when both exist.
|
|
3
1
|
import { readFile } from 'node:fs/promises';
|
|
4
2
|
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
5
3
|
export class AgentConfigDetector {
|
|
@@ -53,4 +51,3 @@ function isNotFound(err) {
|
|
|
53
51
|
'code' in err &&
|
|
54
52
|
err.code === 'ENOENT');
|
|
55
53
|
}
|
|
56
|
-
//# sourceMappingURL=agent-config-detector.js.map
|
package/dist/cli/args.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// docs/commands/start.md §Signature, docs/commands/merge-pr.md §Signature, docs/commands/config.md
|
|
2
|
-
// Tiny dependency-free argv parser. Pure function — easy to unit-test.
|
|
3
1
|
const HELP = { kind: 'help' };
|
|
4
2
|
export function parseArgs(argv) {
|
|
5
3
|
const [command, ...rest] = argv;
|
|
@@ -65,8 +63,6 @@ function parseStart(args) {
|
|
|
65
63
|
i += consumed(inlineValue !== null);
|
|
66
64
|
}
|
|
67
65
|
else if (flag === '--no-automerge') {
|
|
68
|
-
// Boolean flag rejects any inline value: `--no-automerge=true` is a usage error,
|
|
69
|
-
// not silently treated as the boolean.
|
|
70
66
|
if (inlineValue !== null)
|
|
71
67
|
return HELP;
|
|
72
68
|
autoMerge = false;
|
|
@@ -158,7 +154,6 @@ function parseConfig(args) {
|
|
|
158
154
|
scope = 'project';
|
|
159
155
|
}
|
|
160
156
|
else if (arg.startsWith('--')) {
|
|
161
|
-
// `--project=anything` is a usage error: --project is a boolean flag.
|
|
162
157
|
return HELP;
|
|
163
158
|
}
|
|
164
159
|
else {
|
|
@@ -211,8 +206,6 @@ function parsePositiveInt(s) {
|
|
|
211
206
|
const n = parseNonNegativeInt(s);
|
|
212
207
|
return n !== null && n > 0 ? n : null;
|
|
213
208
|
}
|
|
214
|
-
// Split `--key=value` into flag + inline value. For `--key` alone, inlineValue is null
|
|
215
|
-
// and the caller must read args[i+1] for the value (two-token form).
|
|
216
209
|
function splitFlag(raw) {
|
|
217
210
|
if (!raw.startsWith('--')) {
|
|
218
211
|
return { flag: raw, inlineValue: null, consumed: () => 1 };
|
|
@@ -227,12 +220,9 @@ function splitFlag(raw) {
|
|
|
227
220
|
consumed: (inline) => (inline ? 1 : 2),
|
|
228
221
|
};
|
|
229
222
|
}
|
|
230
|
-
// Resolve the value for a flag: prefer the inline form (--key=value); fall back to the
|
|
231
|
-
// next argv token (--key value). Returns null when neither is present.
|
|
232
223
|
function takeValue(args, i, inlineValue) {
|
|
233
224
|
if (inlineValue !== null)
|
|
234
225
|
return inlineValue;
|
|
235
226
|
const next = args[i + 1];
|
|
236
227
|
return next ?? null;
|
|
237
228
|
}
|
|
238
|
-
//# sourceMappingURL=args.js.map
|
package/dist/cli/cli.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// docs/commands/start.md, docs/commands/merge-pr.md, docs/commands/config.md
|
|
3
|
-
// Single entry. Parses argv, dispatches, exits with the right code.
|
|
4
2
|
import { realpathSync } from 'node:fs';
|
|
5
3
|
import { pathToFileURL } from 'node:url';
|
|
6
4
|
import { parseArgs } from "./args.js";
|
|
@@ -86,8 +84,6 @@ Exit codes:
|
|
|
86
84
|
2 cancelled
|
|
87
85
|
|
|
88
86
|
Docs: docs/commands/start.md, docs/commands/merge-pr.md, docs/commands/config.md`;
|
|
89
|
-
// Entry-point: when invoked as a script (via the `aitm` bin), parse process.argv
|
|
90
|
-
// and propagate the exit code. When imported (e.g. from tests), this is skipped.
|
|
91
87
|
if (isEntrypoint(import.meta.url, process.argv[1])) {
|
|
92
88
|
main(process.argv.slice(2)).then((code) => {
|
|
93
89
|
process.exit(code);
|
|
@@ -96,9 +92,6 @@ if (isEntrypoint(import.meta.url, process.argv[1])) {
|
|
|
96
92
|
process.exit(1);
|
|
97
93
|
});
|
|
98
94
|
}
|
|
99
|
-
// Exported for unit-test coverage of the symlink case (global installs put a symlink at
|
|
100
|
-
// e.g. ~/.bun/bin/aitm pointing at dist/cli/cli.js — argv[1] and import.meta.url differ
|
|
101
|
-
// until argv[1] is resolved via realpath).
|
|
102
95
|
export function isEntrypoint(metaUrl, argv1) {
|
|
103
96
|
if (argv1 === undefined)
|
|
104
97
|
return false;
|
|
@@ -110,4 +103,3 @@ export function isEntrypoint(metaUrl, argv1) {
|
|
|
110
103
|
return false;
|
|
111
104
|
}
|
|
112
105
|
}
|
|
113
|
-
//# sourceMappingURL=cli.js.map
|
package/dist/cli/commands.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
// docs/commands/start.md, docs/commands/merge-pr.md, docs/commands/config.md
|
|
2
|
-
// Dispatcher only. Each command does precondition checks → wires deps → kicks WorkLoop / writes config.
|
|
3
|
-
//
|
|
4
|
-
// The heavy WorkLoop+Orchestrator wiring is exposed via the `runLoop` / `runMergeFlow`
|
|
5
|
-
// injection seams so this module stays pure dispatch and is unit-testable without
|
|
6
|
-
// spinning up real subagents. Default seam implementations live below; integration
|
|
7
|
-
// tests (PR 12) cover the end-to-end stack.
|
|
8
1
|
import { homedir } from 'node:os';
|
|
9
2
|
import { join, resolve as resolvePath } from 'node:path';
|
|
10
3
|
import { AgentConfigDetector } from "../agent-config/agent-config-detector.js";
|
|
@@ -67,10 +60,6 @@ export async function runStart(args, ctx = {}) {
|
|
|
67
60
|
const github = new GitHubClient(cwd);
|
|
68
61
|
const stateDir = resolvePath(cwd, '.ai-task-master');
|
|
69
62
|
const state = new StateStore(stateDir);
|
|
70
|
-
// Resume detection: if a previous run left a valid state.json, skip re-init so
|
|
71
|
-
// runId and prGroups are preserved. Only fall back on expected "missing/invalid
|
|
72
|
-
// prior state" cases (ENOENT, JSON parse failure, schema mismatch); surface every
|
|
73
|
-
// other error (permissions, IO) as a hard failure rather than silently re-initing.
|
|
74
63
|
let resuming = false;
|
|
75
64
|
let existingState = null;
|
|
76
65
|
try {
|
|
@@ -81,7 +70,6 @@ export async function runStart(args, ctx = {}) {
|
|
|
81
70
|
if (!isMissingOrInvalidState(err, stateDir)) {
|
|
82
71
|
return { code: 1, message: `Could not read run state: ${errMsg(err)}` };
|
|
83
72
|
}
|
|
84
|
-
// No valid state.json — proceed with fresh init.
|
|
85
73
|
}
|
|
86
74
|
if (!resuming) {
|
|
87
75
|
const initial = buildInitialRunState({ resolved, agentConfig });
|
|
@@ -94,13 +82,6 @@ export async function runStart(args, ctx = {}) {
|
|
|
94
82
|
return { code: 1, message: errMsg(err) };
|
|
95
83
|
}
|
|
96
84
|
}
|
|
97
|
-
// Planning phase (issue #17): a one-shot step that runs the Planner once, before the loop,
|
|
98
|
-
// so `prGroups` is populated and the loop has something to iterate. Gated on whether a plan
|
|
99
|
-
// is already persisted — not merely on `resuming` — because a prior run whose planning
|
|
100
|
-
// blocked leaves a resumable state.json with empty `prGroups`; that case must re-plan rather
|
|
101
|
-
// than hand the loop an empty plan. Only runs when a `runPlanner` seam is injected; otherwise
|
|
102
|
-
// planning is handled inside the WorkLoop adapter (merged default), keeping production
|
|
103
|
-
// behaviour unchanged and this module pure dispatch.
|
|
104
85
|
const hasPersistedPlan = (existingState?.prGroups.length ?? 0) > 0;
|
|
105
86
|
if (ctx.runPlanner && !hasPersistedPlan) {
|
|
106
87
|
let plan;
|
|
@@ -144,9 +125,6 @@ export async function runStart(args, ctx = {}) {
|
|
|
144
125
|
catch (err) {
|
|
145
126
|
return { code: 1, message: errMsg(err) };
|
|
146
127
|
}
|
|
147
|
-
// Persist the first awaiting-pr number into state so `aitm merge-pr` (with no --pr)
|
|
148
|
-
// can pick it up. WorkLoop tracks per-group PR numbers but does not nominate one as
|
|
149
|
-
// "current"; that's a CLI-level concern resolved here.
|
|
150
128
|
if (result.kind === 'awaiting-pr' && result.prs.length > 0) {
|
|
151
129
|
const firstPr = result.prs[0];
|
|
152
130
|
if (firstPr !== undefined) {
|
|
@@ -182,11 +160,6 @@ export async function runMergePr(args, ctx = {}) {
|
|
|
182
160
|
const stateDir = resolvePath(cwd, '.ai-task-master');
|
|
183
161
|
const state = new StateStore(stateDir);
|
|
184
162
|
const github = ctx.github ?? new GitHubClient(cwd);
|
|
185
|
-
// Take-over flow: `aitm merge-pr` (no args, no prior state) should work against any PR
|
|
186
|
-
// the user built by hand — e.g. via Claude Code or `gh pr create`. We mirror the
|
|
187
|
-
// claude-task-master `merge_pr` pattern: try to read existing state, and if absent,
|
|
188
|
-
// synthesize a minimal one from --pr (or the current branch's PR) and persist it so
|
|
189
|
-
// subsequent calls resume.
|
|
190
163
|
let runState;
|
|
191
164
|
try {
|
|
192
165
|
runState = await state.read();
|
|
@@ -284,7 +257,6 @@ export async function runConfig(args, ctx = {}) {
|
|
|
284
257
|
}
|
|
285
258
|
case 'config-list': {
|
|
286
259
|
const file = await writer.list(args.scope);
|
|
287
|
-
// Never dump the API key in cleartext — `config list` output lands in terminals/logs.
|
|
288
260
|
const safe = file.openrouterApiKey
|
|
289
261
|
? { ...file, openrouterApiKey: maskSecret(file.openrouterApiKey) }
|
|
290
262
|
: file;
|
|
@@ -302,7 +274,6 @@ export async function runConfig(args, ctx = {}) {
|
|
|
302
274
|
return { code: 1, message: errMsg(err) };
|
|
303
275
|
}
|
|
304
276
|
}
|
|
305
|
-
// ---- helpers ---------------------------------------------------------------
|
|
306
277
|
function toCliOverrides(args) {
|
|
307
278
|
const out = {};
|
|
308
279
|
if (args.maxPrs !== undefined)
|
|
@@ -367,10 +338,6 @@ function buildInitialRunState(input) {
|
|
|
367
338
|
function errMsg(err) {
|
|
368
339
|
return err instanceof Error ? err.message : String(err);
|
|
369
340
|
}
|
|
370
|
-
// Recognises "no valid prior state" — the only conditions where we fall through to a
|
|
371
|
-
// fresh init. ENOENT means the file is absent. StateStore.read() prefixes any JSON
|
|
372
|
-
// parse or Zod schema error with the state-file path, so we match that prefix to
|
|
373
|
-
// distinguish corrupt state from genuine IO/permission errors.
|
|
374
341
|
function isMissingOrInvalidState(err, stateDir) {
|
|
375
342
|
if (typeof err === 'object' &&
|
|
376
343
|
err !== null &&
|
|
@@ -385,8 +352,6 @@ function isMissingOrInvalidState(err, stateDir) {
|
|
|
385
352
|
}
|
|
386
353
|
return false;
|
|
387
354
|
}
|
|
388
|
-
// Mask a secret for display: keep the non-secret `sk-or-` prefix + last 4 chars so the user can
|
|
389
|
-
// confirm WHICH key is set without exposing it. Short values are fully hidden.
|
|
390
355
|
function maskSecret(value) {
|
|
391
356
|
return value.length <= 12 ? '***' : `${value.slice(0, 6)}…${value.slice(-4)}`;
|
|
392
357
|
}
|
|
@@ -398,24 +363,18 @@ function formatConfigValue(value) {
|
|
|
398
363
|
return JSON.stringify(value, null, 2);
|
|
399
364
|
}
|
|
400
365
|
const defaultAuthStatus = (cwd) => new GitHubClient(cwd).authStatus();
|
|
401
|
-
// Default loop seam — production wiring of Planner → PlanGraph → WorktreePool → WorkLoop with
|
|
402
|
-
// the Orchestrator/Worker/Reviewer subagents and MCP tools. Lives in run-loop-adapter.ts so this
|
|
403
|
-
// module stays pure dispatch; the adapter exposes its own seams for unit + integration tests.
|
|
404
366
|
async function defaultRunLoop(input) {
|
|
405
367
|
return runLoopAdapter(input);
|
|
406
368
|
}
|
|
407
|
-
// Real merge-pr adapter. Drives runTakeOverFlow against the cwd worktree: wait CI →
|
|
408
|
-
// Reviewer per unresolved thread → push → loop → merge. See src/loop/take-over-flow.ts
|
|
409
|
-
// for the iteration shape (mirrors claude-task-master `merge_pr`).
|
|
410
369
|
async function defaultRunMergeFlow(input) {
|
|
411
370
|
const { runTakeOverFlow } = await import("../loop/take-over-flow.js");
|
|
412
371
|
const { execa } = await import('execa');
|
|
413
372
|
const { githubThreadTool } = await import("../tools/github-thread-tool.js");
|
|
373
|
+
const { PrContextStore } = await import("../state/pr-context-store.js");
|
|
414
374
|
const worktreePath = input.cwd;
|
|
415
375
|
const baseBranch = await input.github.defaultBranch();
|
|
416
376
|
const styleContents = input.agentConfig.contents;
|
|
417
|
-
|
|
418
|
-
// full read/write/edit/search/bash set; the Reviewer adds the `github` thread tool.
|
|
377
|
+
const prContext = new PrContextStore(resolvePath(input.cwd, '.ai-task-master'));
|
|
419
378
|
const workerTools = localEditTools(worktreePath);
|
|
420
379
|
const github = githubThreadTool({ github: input.github });
|
|
421
380
|
const result = await runTakeOverFlow({
|
|
@@ -423,6 +382,7 @@ async function defaultRunMergeFlow(input) {
|
|
|
423
382
|
worktreePath,
|
|
424
383
|
baseBranch,
|
|
425
384
|
github: input.github,
|
|
385
|
+
prContext,
|
|
426
386
|
mergeMethod: input.runState.options.mergeMethod,
|
|
427
387
|
push: async (cwd) => {
|
|
428
388
|
const r = await execa('git', ['push'], { cwd });
|
|
@@ -436,6 +396,7 @@ async function defaultRunMergeFlow(input) {
|
|
|
436
396
|
workerModel: input.credentials.modelFor('worker'),
|
|
437
397
|
workerTools,
|
|
438
398
|
styleContents,
|
|
399
|
+
...(input.resolved.formatCommand ? { formatCommand: input.resolved.formatCommand } : {}),
|
|
439
400
|
},
|
|
440
401
|
});
|
|
441
402
|
if (result.kind === 'merged') {
|
|
@@ -450,8 +411,6 @@ async function defaultRunMergeFlow(input) {
|
|
|
450
411
|
outcomes: [{ groupId: `takeover-${input.pr}`, status: 'blocked', reason: result.reason }],
|
|
451
412
|
};
|
|
452
413
|
}
|
|
453
|
-
// Build a minimal RunState that lets `merge-pr` take over a PR opened outside aitm. PR
|
|
454
|
-
// number comes from --pr or, failing that, from `gh pr view` against the current branch.
|
|
455
414
|
async function synthesizeTakeoverState(input) {
|
|
456
415
|
const { args, github, resolved } = input;
|
|
457
416
|
let pr = args.pr ?? null;
|
|
@@ -518,4 +477,3 @@ function isFileNotFound(err) {
|
|
|
518
477
|
'code' in err &&
|
|
519
478
|
err.code === 'ENOENT');
|
|
520
479
|
}
|
|
521
|
-
//# sourceMappingURL=commands.js.map
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
// Drives context compaction for long-running agent loops. Keeps the orchestrator and
|
|
2
|
-
// each subagent coherent on huge PRs by summarizing chat history when usage crosses
|
|
3
|
-
// a fraction of the model's context window.
|
|
4
|
-
//
|
|
5
|
-
// Strategy:
|
|
6
|
-
// 1. Pull contextLength for the active model from ModelLimitsRegistry.
|
|
7
|
-
// 2. Estimate live token usage from the agent's step.usage.inputTokens running total.
|
|
8
|
-
// 3. When usage / contextLength >= threshold (default 0.7), invoke a `fast`-tier
|
|
9
|
-
// summarization step that rewrites the early conversation into a compact note;
|
|
10
|
-
// the next step resumes with the summary + the most recent N steps verbatim.
|
|
11
|
-
//
|
|
12
|
-
// SDK references:
|
|
13
|
-
// docs/vendor/ai-sdk/chunk-09.md §"Subagents" §"Controlling What the Model Sees"
|
|
14
|
-
// (toModelOutput is the per-tool version of the same idea)
|
|
15
|
-
// docs/vendor/ai-sdk/chunk-09.md §"Loop Control" §"Prepare Step"
|
|
16
|
-
// (use prepareStep to swap in compacted messages between steps)
|
|
17
1
|
import { generateText } from 'ai';
|
|
18
2
|
const DEFAULT_THRESHOLD = 0.7;
|
|
19
3
|
const DEFAULT_KEEP_LAST_STEPS = 6;
|
|
@@ -33,8 +17,6 @@ export class Compactor {
|
|
|
33
17
|
}
|
|
34
18
|
async shouldCompact(modelId, liveInputTokens) {
|
|
35
19
|
const { contextLength } = await this.init.limits.forModel(modelId);
|
|
36
|
-
// A non-finite or non-positive window would make ratio NaN/Infinity and force a
|
|
37
|
-
// wrong decision. Treat it as "we don't know enough to compact" — skip.
|
|
38
20
|
if (!Number.isFinite(contextLength) || contextLength <= 0) {
|
|
39
21
|
return { kind: 'skip' };
|
|
40
22
|
}
|
|
@@ -48,7 +30,6 @@ export class Compactor {
|
|
|
48
30
|
}
|
|
49
31
|
return { kind: 'skip' };
|
|
50
32
|
}
|
|
51
|
-
// Produce a compact summary suitable for replacing the older conversation prefix.
|
|
52
33
|
async compact(olderMessages) {
|
|
53
34
|
const { text } = await generateText({
|
|
54
35
|
model: this.init.summarizer,
|
|
@@ -57,10 +38,6 @@ export class Compactor {
|
|
|
57
38
|
return text;
|
|
58
39
|
}
|
|
59
40
|
}
|
|
60
|
-
// Cycle-safe JSON.stringify. SDK message objects can transitively reference each other
|
|
61
|
-
// (tool result -> tool call -> step -> message), and a single circular ref would throw a
|
|
62
|
-
// raw TypeError out of compact() and crash the agent loop mid-step. Replace any cycle
|
|
63
|
-
// with the literal "[CYCLE]" so the summarizer still gets a usable transcript.
|
|
64
41
|
function safeStringify(value) {
|
|
65
42
|
const seen = new WeakSet();
|
|
66
43
|
return JSON.stringify(value, (_key, v) => {
|
|
@@ -72,4 +49,3 @@ function safeStringify(value) {
|
|
|
72
49
|
return v;
|
|
73
50
|
});
|
|
74
51
|
}
|
|
75
|
-
//# sourceMappingURL=compactor.js.map
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// docs/config.md §"Resolution order", docs/auth.md §"LLM provider"
|
|
2
|
-
// Only module allowed to read ~/.aitm.json and .ai-task-master/config.json.
|
|
3
|
-
// Merge order: defaults < global < project < env < CLI flags. Frozen snapshot written by writeSnapshot().
|
|
4
1
|
import { readFile } from 'node:fs/promises';
|
|
5
2
|
import { join } from 'node:path';
|
|
6
3
|
import { ZodError, z } from 'zod';
|
|
@@ -12,10 +9,6 @@ const GLOBAL_FILE = '.aitm.json';
|
|
|
12
9
|
const PROJECT_DIR = '.ai-task-master';
|
|
13
10
|
const PROJECT_FILE = 'config.json';
|
|
14
11
|
const SNAPSHOT_FILE = 'config.snapshot.json';
|
|
15
|
-
// Claude Code's standard MCP config files. Discovering these lets users plug aitm into
|
|
16
|
-
// the same MCP servers their Claude Code session already uses, without re-declaring them.
|
|
17
|
-
// Refs: https://code.claude.com/docs/en/mcp ("Project scope" = .mcp.json in project root;
|
|
18
|
-
// "User scope" = ~/.claude.json with an mcpServers key).
|
|
19
12
|
const CLAUDE_PROJECT_MCP_FILE = '.mcp.json';
|
|
20
13
|
const CLAUDE_USER_FILE = '.claude.json';
|
|
21
14
|
const KNOWN_KEYS = new Set([
|
|
@@ -26,6 +19,7 @@ const KNOWN_KEYS = new Set([
|
|
|
26
19
|
'autoMerge',
|
|
27
20
|
'mergeMethod',
|
|
28
21
|
'stylePath',
|
|
22
|
+
'formatCommand',
|
|
29
23
|
'logLevel',
|
|
30
24
|
'concurrency',
|
|
31
25
|
'mcpServers',
|
|
@@ -36,6 +30,7 @@ const DEFAULTS = {
|
|
|
36
30
|
autoMerge: true,
|
|
37
31
|
mergeMethod: 'squash',
|
|
38
32
|
stylePath: null,
|
|
33
|
+
formatCommand: null,
|
|
39
34
|
logLevel: 'info',
|
|
40
35
|
concurrency: 1,
|
|
41
36
|
};
|
|
@@ -75,7 +70,7 @@ export class ConfigLoader {
|
|
|
75
70
|
autoMerge: pick(cliOverrides.autoMerge, project?.autoMerge, global?.autoMerge, DEFAULTS.autoMerge),
|
|
76
71
|
mergeMethod: pick(cliOverrides.mergeMethod, project?.mergeMethod, global?.mergeMethod, DEFAULTS.mergeMethod),
|
|
77
72
|
stylePath: pickNullable(cliOverrides.stylePath, project?.stylePath, global?.stylePath, DEFAULTS.stylePath),
|
|
78
|
-
|
|
73
|
+
formatCommand: pickNullable(undefined, project?.formatCommand, global?.formatCommand, DEFAULTS.formatCommand),
|
|
79
74
|
logLevel: pick(undefined, project?.logLevel, global?.logLevel, DEFAULTS.logLevel),
|
|
80
75
|
concurrency: pick(cliOverrides.concurrency, project?.concurrency, global?.concurrency, DEFAULTS.concurrency),
|
|
81
76
|
mcpServers,
|
|
@@ -88,19 +83,12 @@ export class ConfigLoader {
|
|
|
88
83
|
async readProject() {
|
|
89
84
|
return this.readConfigFile(join(this.cwd, PROJECT_DIR, PROJECT_FILE));
|
|
90
85
|
}
|
|
91
|
-
// Read Claude Code's project-scoped MCP file (./.mcp.json). Schema is permissive:
|
|
92
|
-
// we only extract `mcpServers`, ignore any other keys Claude Code may add.
|
|
93
86
|
async readClaudeProjectMcp() {
|
|
94
87
|
return this.readMcpEnvelope(join(this.cwd, CLAUDE_PROJECT_MCP_FILE));
|
|
95
88
|
}
|
|
96
|
-
// Read Claude Code's user-scoped config (~/.claude.json) and extract the `mcpServers`
|
|
97
|
-
// block, if any. ~/.claude.json holds many unrelated keys (auth tokens, history); we
|
|
98
|
-
// intentionally read it but only consume `mcpServers`.
|
|
99
89
|
async readClaudeUserMcp() {
|
|
100
90
|
return this.readMcpEnvelope(join(this.homeDir, CLAUDE_USER_FILE));
|
|
101
91
|
}
|
|
102
|
-
// Frozen run snapshot. API key value is replaced by its source label so the
|
|
103
|
-
// file is safe to inspect; only the resolution source is recorded.
|
|
104
92
|
async writeSnapshot(resolved, stateDir) {
|
|
105
93
|
const redacted = {
|
|
106
94
|
...resolved,
|
|
@@ -109,9 +97,6 @@ export class ConfigLoader {
|
|
|
109
97
|
const path = join(stateDir, SNAPSHOT_FILE);
|
|
110
98
|
await atomicWrite(path, `${JSON.stringify(redacted, null, 2)}\n`);
|
|
111
99
|
}
|
|
112
|
-
// Reads any JSON file whose only field we care about is `mcpServers` (Claude Code's
|
|
113
|
-
// .mcp.json or the much larger ~/.claude.json). Missing file → null. Malformed JSON
|
|
114
|
-
// is a hard error — we don't want to silently ignore a corrupted user file.
|
|
115
100
|
async readMcpEnvelope(path) {
|
|
116
101
|
let raw;
|
|
117
102
|
try {
|
|
@@ -137,12 +122,6 @@ export class ConfigLoader {
|
|
|
137
122
|
return envelope.data.mcpServers ?? null;
|
|
138
123
|
}
|
|
139
124
|
resolveMcpServers(sources) {
|
|
140
|
-
// Precedence, lowest → highest:
|
|
141
|
-
// 1. ~/.claude.json (user-scoped Claude Code config)
|
|
142
|
-
// 2. ~/.aitm.json (user-scoped aitm config)
|
|
143
|
-
// 3. ./.mcp.json (project-scoped Claude Code config, checked into git)
|
|
144
|
-
// 4. ./.ai-task-master/config.json (project-scoped aitm config — final word)
|
|
145
|
-
// Same name in two places: higher precedence wins; the lower is shadowed with a warn.
|
|
146
125
|
const layers = [
|
|
147
126
|
['claude-user', sources.claudeUser],
|
|
148
127
|
['aitm-global', sources.aitmGlobal],
|
|
@@ -231,8 +210,6 @@ export class ConfigLoader {
|
|
|
231
210
|
if (src.fast)
|
|
232
211
|
merged.fast = src.fast;
|
|
233
212
|
}
|
|
234
|
-
// --model pins the `generic` tier — the fallback every other capability
|
|
235
|
-
// inherits when not explicitly set. See docs/config.md §"Per-role models".
|
|
236
213
|
if (cliOverrides.model)
|
|
237
214
|
merged.generic = cliOverrides.model;
|
|
238
215
|
return merged;
|
|
@@ -265,11 +242,8 @@ function isNotFound(err) {
|
|
|
265
242
|
function formatZodError(err) {
|
|
266
243
|
return err.issues.map((i) => `${i.path.join('.') || '<root>'}: ${i.message}`).join('; ');
|
|
267
244
|
}
|
|
268
|
-
// Permissive envelope for Claude Code config files: we only extract `mcpServers` and
|
|
269
|
-
// ignore every other key (~/.claude.json especially has many auth/history fields).
|
|
270
245
|
const McpEnvelopeSchema = z
|
|
271
246
|
.object({
|
|
272
247
|
mcpServers: McpServersSchema.optional(),
|
|
273
248
|
})
|
|
274
249
|
.passthrough();
|
|
275
|
-
//# sourceMappingURL=config-loader.js.map
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// docs/commands/config.md, docs/config.md
|
|
2
|
-
// Mutates ~/.aitm.json (or, with --project, ./.ai-task-master/config.json) for `aitm config set/unset`.
|
|
3
|
-
// Atomic write: temp file + rename. Refuses to write unknown top-level keys.
|
|
4
1
|
import { mkdir, readFile } from 'node:fs/promises';
|
|
5
2
|
import { dirname, join } from 'node:path';
|
|
6
3
|
import { ZodError } from 'zod';
|
|
@@ -110,7 +107,6 @@ function parseValue(v) {
|
|
|
110
107
|
return JSON.parse(v);
|
|
111
108
|
}
|
|
112
109
|
catch {
|
|
113
|
-
// Bare strings ("squash", "sk-or-...") aren't valid JSON; treat them as literal strings.
|
|
114
110
|
return v;
|
|
115
111
|
}
|
|
116
112
|
}
|
|
@@ -175,4 +171,3 @@ function isNotFound(err) {
|
|
|
175
171
|
function formatZodError(err) {
|
|
176
172
|
return err.issues.map((i) => `${i.path.join('.') || '<root>'}: ${i.message}`).join('; ');
|
|
177
173
|
}
|
|
178
|
-
//# sourceMappingURL=config-writer.js.map
|
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
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
// docs/config.md §Schema, docs/auth.md §LLM provider
|
|
2
|
-
// Models are configured by *capability tier*, not by subagent role. The mapping
|
|
3
|
-
// role → tier lives in src/credentials/credentials.ts.
|
|
4
|
-
// generic — fallback for anything not otherwise specified
|
|
5
|
-
// smart — best reasoning (Planner, Reviewer)
|
|
6
|
-
// coding — code generation / edits (Worker)
|
|
7
|
-
// fast — cheap routing / summarization (Orchestrator, toModelOutput compaction)
|
|
8
1
|
import { z } from 'zod';
|
|
9
2
|
import { McpServersSchema } from "../mcp/schema.js";
|
|
10
3
|
export const CapabilityModelsSchema = z
|
|
@@ -26,13 +19,9 @@ export const ConfigFileSchema = z
|
|
|
26
19
|
autoMerge: z.boolean().optional(),
|
|
27
20
|
mergeMethod: MergeMethodSchema.optional(),
|
|
28
21
|
stylePath: z.string().nullable().optional(),
|
|
22
|
+
formatCommand: z.string().optional(),
|
|
29
23
|
logLevel: LogLevelSchema.optional(),
|
|
30
|
-
// How many PR groups may have a Worker running at the same time. Default 1 = sequential.
|
|
31
|
-
// See src/loop/work-loop.ts and src/workspace/worktree-pool.ts.
|
|
32
24
|
concurrency: z.number().int().positive().optional(),
|
|
33
|
-
// External MCP servers to mount into subagent tool surfaces (client only — aitm is never
|
|
34
|
-
// exposed as an MCP server). See docs/mcp.md and src/mcp/schema.ts.
|
|
35
25
|
mcpServers: McpServersSchema.optional(),
|
|
36
26
|
})
|
|
37
27
|
.passthrough();
|
|
38
|
-
//# sourceMappingURL=schema.js.map
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
// docs/auth.md, docs/runtime.md, docs/config.md
|
|
2
|
-
// Maps subagent role → capability tier → OpenRouter model handle.
|
|
3
|
-
// Never reads config files or env directly — that's ConfigLoader's job.
|
|
4
|
-
// SDK reference: docs/vendor/ai-sdk/chunk-09.md §"Subagents", chunk-04.md §"ToolLoopAgent".
|
|
5
1
|
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
|
6
2
|
import { DEFAULT_MODELS } from "./defaults.js";
|
|
7
3
|
export const ROLE_CAPABILITY = {
|
|
@@ -12,14 +8,10 @@ export const ROLE_CAPABILITY = {
|
|
|
12
8
|
};
|
|
13
9
|
export class Credentials {
|
|
14
10
|
resolved;
|
|
15
|
-
// Lazy: provider creation also asserts the API key is present, so callers that
|
|
16
|
-
// only inspect role/capability mapping (tests, dry-run) don't need a real key.
|
|
17
11
|
providerInstance;
|
|
18
12
|
constructor(resolved) {
|
|
19
13
|
this.resolved = resolved;
|
|
20
14
|
}
|
|
21
|
-
// Build a handle per role using ROLE_CAPABILITY. Capability fallback chain:
|
|
22
|
-
// models[capability] → models.generic → built-in default.
|
|
23
15
|
handles() {
|
|
24
16
|
return {
|
|
25
17
|
planner: this.modelFor('planner'),
|
|
@@ -35,13 +27,8 @@ export class Credentials {
|
|
|
35
27
|
const modelId = this.resolved.models[capability] ||
|
|
36
28
|
this.resolved.models.generic ||
|
|
37
29
|
DEFAULT_MODELS[capability];
|
|
38
|
-
// Subagents lean on structured output (Output.object). OpenRouter may route a model to a
|
|
39
|
-
// provider — notably Amazon Bedrock — that rejects the AI SDK's structured-output request
|
|
40
|
-
// (`output_config.format`), failing the Planner/Worker/Reviewer at random. Skip Bedrock so
|
|
41
|
-
// those calls land on a provider that accepts the parameter.
|
|
42
30
|
return this.provider().chat(modelId, { provider: { ignore: ['amazon-bedrock'] } });
|
|
43
31
|
}
|
|
44
|
-
// Lets CLI fail fast before any LLM call (docs/commands/start.md §Preconditions step 2).
|
|
45
32
|
static assertApiKeyPresent(resolved) {
|
|
46
33
|
if (!resolved.openrouterApiKey || resolved.openrouterApiKey.trim() === '') {
|
|
47
34
|
throw new Error('OPENROUTER_API_KEY is missing. Set OPENROUTER_API_KEY in the environment, or run `aitm config set openrouterApiKey <key>` (get one at https://openrouter.ai/keys).');
|
|
@@ -55,4 +42,3 @@ export class Credentials {
|
|
|
55
42
|
return this.providerInstance;
|
|
56
43
|
}
|
|
57
44
|
}
|
|
58
|
-
//# sourceMappingURL=credentials.js.map
|
|
@@ -1,21 +1,6 @@
|
|
|
1
|
-
// Canonical capability defaults — all OpenRouter routes (docs/auth.md §"LLM provider").
|
|
2
|
-
// User-set models.{generic,smart,coding,fast} always wins; these only fill gaps.
|
|
3
|
-
//
|
|
4
|
-
// We don't fork defaults by AgentConfigFlavor (CLAUDE.md vs AGENTS.md). Flavor is a
|
|
5
|
-
// *style* signal, not a *vendor* signal — every model goes through OpenRouter, so a
|
|
6
|
-
// project's convention file does not constrain which model serves a request. The
|
|
7
|
-
// flavor only affects which markdown is fed to subagent system prompts.
|
|
8
|
-
//
|
|
9
|
-
// docs/agent-config-detection.md, docs/config.md, docs/runtime.md
|
|
10
|
-
// Tier mapping rationale (Claude family via OpenRouter — the most flexible coding stack today):
|
|
11
|
-
// haiku → fast : routing, orchestration, summarization (toModelOutput compaction)
|
|
12
|
-
// sonnet → generic : default fallback for any unspecified tier
|
|
13
|
-
// opus → smart : Planner, Reviewer (architectural reasoning, critique)
|
|
14
|
-
// opus → coding : Worker (best-in-class code generation)
|
|
15
1
|
export const DEFAULT_MODELS = {
|
|
16
2
|
fast: 'anthropic/claude-haiku-4.5',
|
|
17
3
|
generic: 'anthropic/claude-sonnet-4.6',
|
|
18
4
|
smart: 'anthropic/claude-opus-4.7',
|
|
19
5
|
coding: 'anthropic/claude-opus-4.7',
|
|
20
6
|
};
|
|
21
|
-
//# sourceMappingURL=defaults.js.map
|
package/dist/fs/atomic-write.js
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
// Shared atomic-file primitive. Temp file + fsync + rename so readers never see
|
|
2
|
-
// a half-written file. Used by ConfigLoader.writeSnapshot, ConfigWriter, StateStore.
|
|
3
|
-
//
|
|
4
|
-
// Temp name carries a random suffix so concurrent writes to the same path don't
|
|
5
|
-
// clobber each other's in-flight temp file. Mode 0o600 keeps secret-bearing
|
|
6
|
-
// config files owner-readable only on POSIX.
|
|
7
1
|
import { randomUUID } from 'node:crypto';
|
|
8
2
|
import { open, rename, rm } from 'node:fs/promises';
|
|
9
3
|
export async function atomicWrite(path, contents) {
|
|
@@ -24,4 +18,3 @@ export async function atomicWrite(path, contents) {
|
|
|
24
18
|
throw err;
|
|
25
19
|
}
|
|
26
20
|
}
|
|
27
|
-
//# sourceMappingURL=atomic-write.js.map
|
package/dist/github/errors.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// docs/github-integration.md §"Result typing" — never raw stderr; always domain errors.
|
|
2
1
|
export class PrNotFound extends Error {
|
|
3
2
|
name = 'PrNotFound';
|
|
4
3
|
}
|
|
@@ -17,4 +16,3 @@ export class GhAuthRequired extends Error {
|
|
|
17
16
|
export class MergeConflict extends Error {
|
|
18
17
|
name = 'MergeConflict';
|
|
19
18
|
}
|
|
20
|
-
//# sourceMappingURL=errors.js.map
|
|
@@ -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;
|