New:Socket for Asana Is Now Available.Learn more
Get Started

@developerz.ai/aitm

Package Overview
Dependencies
Maintainers
2
Versions
57
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@developerz.ai/aitm - npm Package Compare versions

Comparing version
0.0.53
to
0.0.54
+21
dist/subagents/goal-assessor.d.ts
import { type ToolLoopAgent } from 'ai';
import { z } from 'zod';
import { type SubagentInit } from './factory.ts';
import type { PlannerTools } from './planner.ts';
export declare const GOAL_ASSESSOR_MAX_STEPS = 1000;
export declare const GoalAssessmentSchema: z.ZodObject<{
complete: z.ZodBoolean;
remaining: z.ZodDefault<z.ZodString>;
rationale: z.ZodDefault<z.ZodString>;
}, z.core.$strip>;
export type GoalAssessment = z.infer<typeof GoalAssessmentSchema>;
export type GoalAssessorAgent = ToolLoopAgent<never, PlannerTools>;
export type GoalAssessorInput = {
goal: string;
criteria?: string;
delivered: readonly string[];
contextBlock?: string;
};
export { GOAL_ASSESSOR_SYSTEM_PREFIX } from './prompts/role-guidance.ts';
export declare function createGoalAssessorAgent(init: SubagentInit<PlannerTools>): GoalAssessorAgent;
export declare function runGoalAssessor(agent: GoalAssessorAgent, input: GoalAssessorInput): Promise<GoalAssessment>;
import { createSubagent, runWithSchemaRetry } from '@developerz.ai/ai-claude-compat';
import { tool } from 'ai';
import { z } from 'zod';
import { AGENT_STEP_BACKSTOP, forwardInit } from "./factory.js";
export const GOAL_ASSESSOR_MAX_STEPS = AGENT_STEP_BACKSTOP;
export const GoalAssessmentSchema = z.object({
complete: z.boolean(),
remaining: z.string().default(''),
rationale: z.string().default(''),
});
export { GOAL_ASSESSOR_SYSTEM_PREFIX } from "./prompts/role-guidance.js";
const assessorInitRegistry = new WeakMap();
export function createGoalAssessorAgent(init) {
const agent = createSubagent({
model: init.model,
tools: init.tools,
systemPrompt: init.systemPrompt,
submit: tool({
description: 'Submit the verdict: is the goal delivered, and if not, what remains.',
inputSchema: GoalAssessmentSchema,
execute: async (assessment) => assessment,
}),
...forwardInit(init),
}, GOAL_ASSESSOR_MAX_STEPS);
assessorInitRegistry.set(agent, init);
return agent;
}
export async function runGoalAssessor(agent, input) {
const onUsage = assessorInitRegistry.get(agent)?.onUsage;
try {
const submitted = await runWithSchemaRetry(agent, GoalAssessmentSchema, buildAssessorPrompt(input), { ...(onUsage ? { onUsage } : {}) });
if (!submitted.ok) {
return { complete: true, remaining: '', rationale: 'assessor did not submit a verdict' };
}
const value = submitted.value;
if (!value.complete && value.remaining.trim() === '') {
return { ...value, complete: true, rationale: 'no remaining work named' };
}
return value;
}
catch {
return { complete: true, remaining: '', rationale: 'assessor failed' };
}
}
function buildAssessorPrompt(input) {
const lines = [`Goal: ${input.goal}`];
if (input.criteria?.trim())
lines.push(`Acceptance criteria: ${input.criteria}`);
lines.push('', 'Already delivered by this run:');
lines.push(...(input.delivered.length > 0
? input.delivered.map((d) => `- ${d}`)
: ['- (nothing yet — no group has landed)']));
lines.push('', 'Check the repo against the goal with the read-only tools, then call submit.');
const body = lines.join('\n');
return input.contextBlock ? `${input.contextBlock}\n\n${body}` : body;
}
+4
-0

@@ -6,2 +6,3 @@ import type { RunLoopInput } from '../composition/run-input.ts';

import type { OnUsage } from '../subagents/factory.ts';
import { type GoalAssessment } from '../subagents/goal-assessor.ts';
import { sanitizeBranchComponent } from '../workspace/branch-name.ts';

@@ -21,5 +22,7 @@ export type PlanGroupsOutcome = {

export declare function planToPrGroups(plan: Plan, branch?: string, takenBranches?: ReadonlySet<string>): PrGroup[];
export declare function namespaceWaveGroups(fresh: readonly PrGroup[], taken: readonly PrGroup[], wave: number): PrGroup[];
export declare function remoteBranchNames(cwd: string, signal?: AbortSignal): Promise<Set<string>>;
export declare function listTrackedFiles(cwd: string): Promise<string[]>;
export declare function parseRemoteHeads(stdout: string): string[];
export declare const SURVEY_MIN_TRACKED_FILES = 25;
export declare function surveyRepoForPlanner(params: {

@@ -34,1 +37,2 @@ input: RunLoopInput;

export declare function defaultPlanGroups(input: RunLoopInput, mcp: McpClientManager, fetchHtmlAvailable: boolean): Promise<PlanGroupsOutcome>;
export declare function defaultAssessGoal(input: RunLoopInput, mcp: McpClientManager, fetchHtmlAvailable: boolean, delivered: readonly string[]): Promise<GoalAssessment>;
import { harnessProgress, shortModelName } from "../observability/step-progress.js";
import { roleUsageSink } from "../observability/usage-tracker.js";
import { createGoalAssessorAgent, GOAL_ASSESSOR_SYSTEM_PREFIX, runGoalAssessor, } from "../subagents/goal-assessor.js";
import { createPlannerAgent, PLANNER_SYSTEM_PREFIX, runPlanner, } from "../subagents/planner.js";

@@ -47,2 +48,36 @@ import { createScoutRunner, SCOUT_SYSTEM_PREFIX, synthesizeSurveyBrief, } from "../subagents/planner-scouts.js";

}
export function namespaceWaveGroups(fresh, taken, wave) {
const takenIds = new Set(taken.map((g) => g.id));
const takenBranches = new Set(taken.map((g) => g.branch).filter((b) => typeof b === 'string'));
const idMap = new Map();
for (const group of fresh) {
let id = group.id;
if (takenIds.has(id)) {
id = `w${wave}-${group.id}`;
for (let n = 2; takenIds.has(id); n++)
id = `w${wave}-${group.id}-${n}`;
}
takenIds.add(id);
idMap.set(group.id, id);
}
return fresh.map((group) => {
const id = idMap.get(group.id) ?? group.id;
let branch = group.branch;
if (typeof branch === 'string' && takenBranches.has(branch)) {
const base = branch;
branch = `${base}-w${wave}`;
for (let n = 2; takenBranches.has(branch); n++)
branch = `${base}-w${wave}-${n}`;
}
if (typeof branch === 'string')
takenBranches.add(branch);
return {
...group,
id,
branch,
dependsOn: group.dependsOn.map((dep) => idMap.get(dep) ?? dep),
tasks: group.tasks.map((task, i) => ({ ...task, id: `${id}-${i + 1}` })),
};
});
}
export async function remoteBranchNames(cwd, signal) {

@@ -79,2 +114,3 @@ try {

}
export const SURVEY_MIN_TRACKED_FILES = 25;
export async function surveyRepoForPlanner(params) {

@@ -84,2 +120,7 @@ const { input, style, plannerModelId, plannerUsage, mcp, fetchHtmlAvailable } = params;

const repoMap = skeleton.totalFiles === 0 ? '' : renderRepoSkeleton(skeleton);
if (skeleton.totalFiles > 0 && skeleton.totalFiles < SURVEY_MIN_TRACKED_FILES) {
harnessProgress(`survey: skipped — ${skeleton.totalFiles} tracked file(s), the planner reads the repo directly`, { phase: 'planning' });
const mapOnly = synthesizeSurveyBrief([], repoMap);
return mapOnly === '' ? undefined : mapOnly;
}
const base = {

@@ -196,1 +237,26 @@ model: input.credentials.modelFor('planner'),

}
export async function defaultAssessGoal(input, mcp, fetchHtmlAvailable, delivered) {
const style = resolveStyleContents(input);
const modelId = input.credentials.modelIdFor('planner');
const usage = roleUsageSink(input.usage, 'planner', modelId);
harnessProgress('checking the goal against the repo', { phase: 'planning' });
const agent = createGoalAssessorAgent({
model: input.credentials.modelFor('planner'),
tools: applyHooks(resolvePlannerTools(mcp.toolsForRole('planner'), input.cwd, fetchHtmlAvailable, buildExploreFor(input, input.cwd, usage)), input, input.cwd),
systemPrompt: reminderAgentSystemPrompt({
style,
roleGuidance: GOAL_ASSESSOR_SYSTEM_PREFIX,
cwd: input.cwd,
modelId,
}),
timeout: { stepMs: input.resolved.llmStepTimeoutMs },
...(usage ? { onUsage: usage } : {}),
...(input.signal ? { signal: input.signal } : {}),
});
return runGoalAssessor(agent, {
goal: input.goal,
delivered,
contextBlock: harnessContextBlock(),
...(input.criteria !== undefined ? { criteria: input.criteria } : {}),
});
}

@@ -7,2 +7,3 @@ import { type BackgroundProcessTools } from '@developerz.ai/ai-claude-compat';

import { providerOptionsWithServerTools } from '../openrouter/server-tools.ts';
import type { GoalAssessment } from '../subagents/goal-assessor.ts';
import { harnessContextBlock, reminderAgentSystemPrompt } from '../subagents/role-prompt.ts';

@@ -33,2 +34,3 @@ import { discoverSpecialists } from '../subagents/specialist-registry.ts';

planGroups?: (input: RunLoopInput, mcp: McpClientManager, fetchHtmlAvailable: boolean) => Promise<PlanGroupsOutcome>;
assessGoal?: (input: RunLoopInput, mcp: McpClientManager, fetchHtmlAvailable: boolean, delivered: readonly string[]) => Promise<GoalAssessment>;
makeOrchestrator?: (ctx: OrchestratorBridgeCtx) => WorkLoopOrchestrator | Promise<WorkLoopOrchestrator>;

@@ -35,0 +37,0 @@ makeCheckout?: (input: RunLoopInput) => CheckoutHome;

+120
-78

@@ -31,3 +31,3 @@ import { existsSync } from 'node:fs';

import { Disposer, disposeQuietly } from "./disposer.js";
import { defaultPlanGroups } from "./planner-wiring.js";
import { defaultAssessGoal, defaultPlanGroups, namespaceWaveGroups, } from "./planner-wiring.js";
import { makeProgressTee } from "./progress-file.js";

@@ -75,43 +75,8 @@ import { hasInterruptedGroup, normalizeResumeStatus } from "./resume-normalize.js";

const fetchHtmlAvailable = await isFetchHtmlAvailable();
let groups;
let freshPlan = false;
if (current.prGroups.length > 0) {
if (hasInterruptedGroup(current.prGroups)) {
const next = await state.update((s) => ({
...s,
prGroups: normalizeResumeStatus(s.prGroups),
}));
groups = next.prGroups;
}
else {
groups = current.prGroups;
}
}
else {
const planFn = seams.planGroups ?? defaultPlanGroups;
const outcome = await planFn(input, mcp, fetchHtmlAvailable);
if (outcome.kind === 'blocked') {
return { kind: 'blocked', reason: outcome.reason, outcomes: [] };
}
if (outcome.kind === 'error') {
return { kind: 'blocked', reason: `planner error: ${outcome.error}`, outcomes: [] };
}
if (outcome.groups.length === 0) {
return { kind: 'blocked', reason: 'planner produced no PR groups', outcomes: [] };
}
groups = outcome.groups;
freshPlan = true;
}
const stepCounter = makeStepCounter(groups, current.options.prPerTask ?? false);
try {
PlanGraph.validate(groups);
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { kind: 'blocked', reason: msg, outcomes: [] };
}
if (freshPlan) {
await state.update((s) => ({ ...s, status: 'working', prGroups: groups }));
}
let liveGroups = groups;
const effectiveConcurrency = 1;
const checkout = seams.makeCheckout?.(input) ??
new InPlaceCheckout(input.cwd, { allowDirty: input.resolved.allowDirty ?? false });
const github = seams.makeGithub?.(input) ?? input.github;
const budgetCheck = makeBudgetCheck(input.usage, input.resolved.maxCostUsd, input.resolved.maxTotalTokens);
let liveGroups = [];
const graph = {

@@ -121,2 +86,3 @@ ready: () => PlanGraph.trusted(liveGroups).ready(),

};
let liveSessionCount = current.sessionCount;
const workLoopState = {

@@ -126,2 +92,3 @@ update: async (mutator) => {

liveGroups = next.prGroups;
liveSessionCount = next.sessionCount;
return next;

@@ -133,38 +100,107 @@ },

};
const effectiveConcurrency = 1;
const checkout = seams.makeCheckout?.(input) ??
new InPlaceCheckout(input.cwd, { allowDirty: input.resolved.allowDirty ?? false });
const github = seams.makeGithub?.(input) ?? input.github;
const orchestrator = await (seams.makeOrchestrator ?? defaultMakeOrchestrator)({
input,
mcp,
rollingContext,
fetchHtmlAvailable,
state,
stepCounter,
background,
});
const budgetCheck = makeBudgetCheck(input.usage, input.resolved.maxCostUsd, input.resolved.maxTotalTokens);
const loop = new WorkLoop({
orchestrator,
github,
state: workLoopState,
home: checkout,
graph,
prContext: new PrContextStore(resolvePath(input.cwd, '.ai-task-master')),
concurrency: effectiveConcurrency,
autoMerge: input.resolved.autoMerge,
selfReview: input.resolved.selfReview,
prPerTask: current.options.prPerTask ?? false,
maxSessions: input.resolved.maxSessions,
maxCiFixAttempts: input.resolved.maxCiFixAttempts,
mergeMethod: input.resolved.mergeMethod,
adminMerge: input.resolved.adminMerge ?? false,
initialSessionCount: current.sessionCount,
progress: makeProgressTee(state.appendProgress ? { append: state.appendProgress.bind(state) } : {}),
stepCounter,
...(input.signal ? { signal: input.signal } : {}),
...(budgetCheck ? { budget: budgetCheck } : {}),
});
return await loop.run();
const allOutcomes = [];
const assessGoal = seams.assessGoal ?? defaultAssessGoal;
let waveGoal = input.goal;
let landed = [];
const plannedGoals = new Set([input.goal]);
for (let wave = 1;; wave++) {
let groups;
let freshPlan = false;
if (wave === 1 && current.prGroups.length > 0) {
if (hasInterruptedGroup(current.prGroups)) {
const next = await state.update((s) => ({
...s,
prGroups: normalizeResumeStatus(s.prGroups),
}));
groups = next.prGroups;
}
else {
groups = current.prGroups;
}
}
else {
const planFn = seams.planGroups ?? defaultPlanGroups;
const outcome = await planFn({ ...input, goal: waveGoal }, mcp, fetchHtmlAvailable);
if (outcome.kind === 'blocked') {
if (wave > 1)
return { kind: 'success', outcomes: allOutcomes };
return { kind: 'blocked', reason: outcome.reason, outcomes: [] };
}
if (outcome.kind === 'error') {
if (wave > 1)
return { kind: 'success', outcomes: allOutcomes };
return { kind: 'blocked', reason: `planner error: ${outcome.error}`, outcomes: [] };
}
if (outcome.groups.length === 0) {
if (wave > 1)
return { kind: 'success', outcomes: allOutcomes };
return { kind: 'blocked', reason: 'planner produced no PR groups', outcomes: [] };
}
groups = [...landed, ...namespaceWaveGroups(outcome.groups, landed, wave)];
freshPlan = true;
}
const stepCounter = makeStepCounter(groups, current.options.prPerTask ?? false);
try {
PlanGraph.validate(groups);
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
if (wave > 1)
return { kind: 'success', outcomes: allOutcomes };
return { kind: 'blocked', reason: msg, outcomes: [] };
}
if (freshPlan) {
await state.update((s) => ({ ...s, status: 'working', prGroups: groups }));
}
liveGroups = groups;
const waveContext = (await state.readContext?.()) ?? rollingContext;
const orchestrator = await (seams.makeOrchestrator ?? defaultMakeOrchestrator)({
input,
mcp,
rollingContext: waveContext,
fetchHtmlAvailable,
state,
stepCounter,
background,
});
const loop = new WorkLoop({
orchestrator,
github,
state: workLoopState,
home: checkout,
graph,
prContext: new PrContextStore(resolvePath(input.cwd, '.ai-task-master')),
concurrency: effectiveConcurrency,
autoMerge: input.resolved.autoMerge,
selfReview: input.resolved.selfReview,
prPerTask: current.options.prPerTask ?? false,
maxSessions: input.resolved.maxSessions,
maxCiFixAttempts: input.resolved.maxCiFixAttempts,
mergeMethod: input.resolved.mergeMethod,
adminMerge: input.resolved.adminMerge ?? false,
initialSessionCount: liveSessionCount,
progress: makeProgressTee(state.appendProgress ? { append: state.appendProgress.bind(state) } : {}),
stepCounter,
...(input.signal ? { signal: input.signal } : {}),
...(budgetCheck ? { budget: budgetCheck } : {}),
});
const result = await loop.run();
allOutcomes.push(...result.outcomes);
if (result.kind !== 'success')
return { ...result, outcomes: allOutcomes };
landed = [...liveGroups];
const assessment = await assessGoal(input, mcp, fetchHtmlAvailable, deliveredSummary(landed));
if (assessment.complete) {
return { kind: 'success', outcomes: allOutcomes };
}
if (plannedGoals.has(assessment.remaining)) {
harnessProgress(`goal still not met, but the remaining work is unchanged from a wave already run — stopping: ${assessment.remaining}`, { phase: 'planning' });
return { kind: 'success', outcomes: allOutcomes };
}
plannedGoals.add(assessment.remaining);
harnessProgress(`goal not yet met — planning another wave: ${assessment.remaining}`, {
phase: 'planning',
});
waveGoal = assessment.remaining;
}
}

@@ -175,2 +211,8 @@ finally {

}
function deliveredSummary(groups) {
return groups.map((g) => {
const pr = typeof g.pr === 'number' ? ` (PR #${g.pr}, ${g.status})` : ` (${g.status})`;
return `${g.id}: ${g.title}${pr}`;
});
}
export function selfReviewVerifyCommand(configured, cwd) {

@@ -177,0 +219,0 @@ if (configured)

@@ -32,3 +32,3 @@ import { createSubagent, formatSubmitIssues, runWithSchemaRetry, } from '@developerz.ai/ai-claude-compat';

try {
const submitted = await runWithSchemaRetry(agent, PlanSchema, buildUserPrompt(input), {
const submitted = await runWithSchemaRetry(agent, cappedPlanSchema(input.maxPrs), buildUserPrompt(input), {
...(onUsage ? { onUsage } : {}),

@@ -45,10 +45,3 @@ });

}
const plan = submitted.value;
if (input.maxPrs !== null && plan.groups.length > input.maxPrs) {
return {
kind: 'error',
error: `planner emitted ${plan.groups.length} PR groups, exceeding maxPrs ${input.maxPrs}. Raise --max-prs (or pass 0 for unbounded) — the plan is not truncated, because dropping groups would silently drop work.`,
};
}
return { kind: 'ok', plan };
return { kind: 'ok', plan: submitted.value };
}

@@ -59,2 +52,9 @@ catch (err) {

}
function cappedPlanSchema(maxPrs) {
if (maxPrs === null)
return PlanSchema;
return PlanSchema.refine((plan) => plan.groups.length <= maxPrs, {
message: `too many PR groups: emit at most ${maxPrs}. Group everything to fit — never drop the tail, and never leave part of the goal unplanned.`,
});
}
function buildUserPrompt(input) {

@@ -61,0 +61,0 @@ const lines = [`Goal: ${input.goal}`];

export declare const PLANNER_SYSTEM_PREFIX: string;
export declare const GOAL_ASSESSOR_SYSTEM_PREFIX: string;
export declare const WORKER_SYSTEM_PREFIX: string;
export declare const EDITOR_SYSTEM_PREFIX: string;
export declare const EXPLORE_SYSTEM_PROMPT: string;

@@ -7,10 +7,7 @@ export const PLANNER_SYSTEM_PREFIX = [

'',
'COVER THE WHOLE GOAL. The groups together must deliver everything the goal asks for — a plan that',
'implements a subset and leaves the rest unplanned is INVALID, however well-sized its groups are.',
'Size the plan to the goal, not to a number you expect to be comfortable: "implement the whole',
'system" over a greenfield repo is a large plan (a dozen groups or more), and emitting one group',
'for the first component silently ships a fraction of what was asked. Nothing downstream re-plans',
'the remainder — what you leave out is never built. There is no cap on how many groups you may',
'emit unless a `maxPrs:` line appears below; when it does, group everything to fit it rather than',
'dropping the tail.',
'COVER THE WHOLE GOAL. The groups together MUST deliver everything the goal asks. A plan covering a',
'subset is INVALID, however well-sized its groups. Nothing re-plans the remainder — what you omit is',
'NEVER built. Size the plan to the goal: "implement the whole system" on a greenfield repo is a dozen',
'groups or more. No cap on group count unless a `maxPrs:` line appears below; when it does, group',
'everything to fit — never drop the tail.',
'',

@@ -57,2 +54,18 @@ 'Each group = one cohesive PR that delivers a whole capability end to end, tests included —',

].join('\n');
export const GOAL_ASSESSOR_SYSTEM_PREFIX = [
'',
'You judge ONE question: does the goal still have work left?',
'',
'A wave of PR groups just landed. Read the repo as it NOW is (glob/grep/readFile, `explore` when',
'present) and check it against the GOAL — never against the plan. A plan that covered only part of',
'the goal is exactly what you are here to catch.',
'',
'- `complete: true` only when the goal is genuinely delivered: the behaviour exists, it is wired in,',
' and it has tests. "The plan finished" is NOT the test — the plan can be wrong.',
'- `complete: false` → `remaining` names the work still owed, phrased as a GOAL a planner can plan',
' from ("implement the REST API gateway and its schema"), never a critique ("the API is missing").',
'- Polish, refactors, renames, and nice-to-haves are NEVER remaining work. Scope creep here loops',
' forever and spends the operator money.',
'- Unsure → `complete: true`. Report what you can verify; a wrong "false" buys another whole wave.',
].join('\n');
export const WORKER_SYSTEM_PREFIX = [

@@ -59,0 +72,0 @@ '',

{
"name": "@developerz.ai/aitm",
"version": "0.0.53",
"version": "0.0.54",
"description": "Autonomous task orchestrator. Goal in, merged PRs out.",

@@ -46,3 +46,3 @@ "license": "MIT",

"@ai-sdk/mcp": "^1.0.42",
"@developerz.ai/ai-claude-compat": "0.0.53",
"@developerz.ai/ai-claude-compat": "0.0.54",
"@openrouter/ai-sdk-provider": "^2.9.0",

@@ -49,0 +49,0 @@ "ai": "^6.0.182",