skimpyclaw 0.3.6 → 0.3.9

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.
Files changed (73) hide show
  1. package/README.md +14 -6
  2. package/dist/__tests__/api.test.js +1 -0
  3. package/dist/__tests__/channels.test.js +1 -1
  4. package/dist/__tests__/code-agents-orchestrator.test.js +74 -7
  5. package/dist/__tests__/code-agents-preflight.test.d.ts +1 -0
  6. package/dist/__tests__/code-agents-preflight.test.js +88 -0
  7. package/dist/__tests__/code-agents-sandbox.test.d.ts +1 -0
  8. package/dist/__tests__/code-agents-sandbox.test.js +163 -0
  9. package/dist/__tests__/code-agents-utils.test.js +12 -1
  10. package/dist/__tests__/context-manager.test.d.ts +1 -0
  11. package/dist/__tests__/context-manager.test.js +236 -0
  12. package/dist/__tests__/package-manager-detection.test.js +5 -5
  13. package/dist/__tests__/setup.test.js +7 -5
  14. package/dist/__tests__/skills.test.js +2 -2
  15. package/dist/__tests__/structured-context.test.d.ts +1 -0
  16. package/dist/__tests__/structured-context.test.js +100 -0
  17. package/dist/__tests__/tools.test.js +65 -3
  18. package/dist/agent.js +4 -5
  19. package/dist/api.js +10 -58
  20. package/dist/audit.js +5 -51
  21. package/dist/channels/telegram/handlers.js +2 -60
  22. package/dist/channels/telegram/index.js +0 -7
  23. package/dist/channels.js +1 -1
  24. package/dist/cli.js +151 -16
  25. package/dist/code-agents/executor.d.ts +9 -4
  26. package/dist/code-agents/executor.js +187 -13
  27. package/dist/code-agents/index.d.ts +1 -1
  28. package/dist/code-agents/index.js +30 -22
  29. package/dist/code-agents/orchestrator.d.ts +8 -2
  30. package/dist/code-agents/orchestrator.js +318 -27
  31. package/dist/code-agents/structured-context.d.ts +7 -0
  32. package/dist/code-agents/structured-context.js +54 -0
  33. package/dist/code-agents/types.d.ts +2 -0
  34. package/dist/code-agents/utils.d.ts +4 -0
  35. package/dist/code-agents/utils.js +38 -2
  36. package/dist/code-agents/worktree.d.ts +40 -0
  37. package/dist/code-agents/worktree.js +215 -0
  38. package/dist/config.d.ts +1 -0
  39. package/dist/config.js +5 -3
  40. package/dist/cron.js +18 -4
  41. package/dist/dashboard/assets/{index-CkonC7Cd.js → index-BoTHPby4.js} +20 -20
  42. package/dist/dashboard/assets/{index-EAg6lqF5.css → index-D4mufvBg.css} +1 -1
  43. package/dist/dashboard/index.html +2 -2
  44. package/dist/discord.js +4 -40
  45. package/dist/exec-approval.js +1 -1
  46. package/dist/file-lock.js +1 -1
  47. package/dist/gateway.js +3 -10
  48. package/dist/providers/anthropic.js +9 -5
  49. package/dist/providers/codex.js +10 -6
  50. package/dist/providers/context-manager.d.ts +22 -0
  51. package/dist/providers/context-manager.js +100 -0
  52. package/dist/providers/openai.js +9 -5
  53. package/dist/providers/types.d.ts +1 -0
  54. package/dist/security.js +9 -0
  55. package/dist/setup.js +122 -27
  56. package/dist/skills.js +9 -2
  57. package/dist/subagent.js +33 -2
  58. package/dist/tools/bash-tool.js +8 -0
  59. package/dist/tools/browser-tool.js +2 -1
  60. package/dist/tools/definitions.d.ts +0 -27
  61. package/dist/tools/definitions.js +0 -18
  62. package/dist/tools/execute-context.d.ts +4 -4
  63. package/dist/tools/file-tools.d.ts +1 -1
  64. package/dist/tools/file-tools.js +1 -1
  65. package/dist/tools.d.ts +5 -5
  66. package/dist/tools.js +87 -98
  67. package/dist/types.d.ts +14 -22
  68. package/dist/usage.d.ts +1 -0
  69. package/dist/usage.js +30 -46
  70. package/dist/utils.d.ts +18 -0
  71. package/dist/utils.js +71 -0
  72. package/dist/voice.js +9 -7
  73. package/package.json +26 -21
@@ -1,9 +1,10 @@
1
1
  // Code Agent Executor - Background execution logic
2
- import { spawn, exec } from 'child_process';
2
+ import { spawn, exec, execSync } from 'child_process';
3
3
  import { createWriteStream, existsSync, readFileSync } from 'fs';
4
4
  import { join } from 'path';
5
5
  // SKIMPYCLAW_ROOT for log paths
6
6
  const SKIMPYCLAW_ROOT = join(import.meta.dirname || process.cwd(), '..', '..');
7
+ import { toErrorMessage } from '../utils.js';
7
8
  import { VALIDATE_TIMEOUT_MS } from './types.js';
8
9
  import { getCodeAgentsDir, ensureCodeAgentsDir, writeCodeAgentTask, setCodeAgentCanceller, deleteCodeAgentCanceller, getCodeAgent, } from './registry.js';
9
10
  import { buildCodeAgentArgs, notifyCodeAgentResult } from './utils.js';
@@ -51,15 +52,184 @@ export function detectPackageManager(workdir) {
51
52
  // 3. Fallback
52
53
  return 'pnpm';
53
54
  }
55
+ /**
56
+ * Detect monorepo workspaces from package.json.
57
+ * Returns workspace glob patterns or null if not a monorepo.
58
+ */
59
+ function getWorkspacePatterns(workdir) {
60
+ try {
61
+ const pkgPath = join(workdir, 'package.json');
62
+ if (!existsSync(pkgPath))
63
+ return null;
64
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
65
+ // yarn/npm: "workspaces": ["packages/*"] or "workspaces": { "packages": [...] }
66
+ const ws = pkg.workspaces;
67
+ if (Array.isArray(ws))
68
+ return ws;
69
+ if (ws && Array.isArray(ws.packages))
70
+ return ws.packages;
71
+ // pnpm: check pnpm-workspace.yaml
72
+ const pnpmWsPath = join(workdir, 'pnpm-workspace.yaml');
73
+ if (existsSync(pnpmWsPath)) {
74
+ const content = readFileSync(pnpmWsPath, 'utf-8');
75
+ const matches = content.match(/- ['"]?([^'"\n]+)['"]?/g);
76
+ if (matches)
77
+ return matches.map(m => m.replace(/^- ['"]?|['"]?$/g, ''));
78
+ }
79
+ return null;
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ /**
86
+ * Find which monorepo packages have changed files (git diff).
87
+ * Returns package directories relative to workdir.
88
+ */
89
+ function getChangedPackageDirs(workdir) {
90
+ try {
91
+ // Get changed files vs HEAD (staged + unstaged + untracked)
92
+ const diff = execSync('git diff --name-only HEAD 2>/dev/null; git diff --name-only --cached 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null', { cwd: workdir, timeout: 5000, encoding: 'utf-8' }).trim();
93
+ if (!diff)
94
+ return [];
95
+ const files = [...new Set(diff.split('\n').filter(Boolean))];
96
+ // Extract unique top-level package directories (e.g. "packages/image-studio/src/foo.ts" → "packages/image-studio")
97
+ const pkgDirs = new Set();
98
+ for (const f of files) {
99
+ const parts = f.split('/');
100
+ // Look for package.json at each depth to find package boundary
101
+ for (let depth = 1; depth <= Math.min(parts.length - 1, 4); depth++) {
102
+ const candidate = parts.slice(0, depth).join('/');
103
+ if (existsSync(join(workdir, candidate, 'package.json'))) {
104
+ pkgDirs.add(candidate);
105
+ break;
106
+ }
107
+ }
108
+ }
109
+ return [...pkgDirs];
110
+ }
111
+ catch {
112
+ return [];
113
+ }
114
+ }
115
+ /**
116
+ * Build scoped validation commands for a monorepo by detecting changed packages.
117
+ * Returns a combined command that builds/tests only affected packages, or null
118
+ * if this doesn't look like a monorepo or no packages were changed.
119
+ */
120
+ function buildMonorepoValidationCommand(workdir) {
121
+ const wsPatterns = getWorkspacePatterns(workdir);
122
+ if (!wsPatterns)
123
+ return null;
124
+ const changedDirs = getChangedPackageDirs(workdir);
125
+ if (changedDirs.length === 0)
126
+ return null;
127
+ const pm = detectPackageManager(workdir);
128
+ const parts = [];
129
+ for (const dir of changedDirs) {
130
+ const pkgJsonPath = join(workdir, dir, 'package.json');
131
+ if (!existsSync(pkgJsonPath))
132
+ continue;
133
+ try {
134
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
135
+ const pkgName = pkg.name;
136
+ const scripts = pkg.scripts || {};
137
+ if (!pkgName)
138
+ continue;
139
+ // Build with workspace command
140
+ if (scripts.build) {
141
+ if (pm === 'pnpm')
142
+ parts.push(`pnpm --filter ${pkgName} run build`);
143
+ else if (pm === 'yarn')
144
+ parts.push(`yarn workspace ${pkgName} build`);
145
+ else if (pm === 'bun')
146
+ parts.push(`bun --filter ${pkgName} run build`);
147
+ else
148
+ parts.push(`npm -w ${pkgName} run build`);
149
+ }
150
+ // Test: prefer package-scoped test, fall back to root test runner scoped to path
151
+ if (scripts.test) {
152
+ if (pm === 'pnpm')
153
+ parts.push(`pnpm --filter ${pkgName} run test`);
154
+ else if (pm === 'yarn')
155
+ parts.push(`yarn workspace ${pkgName} test`);
156
+ else if (pm === 'bun')
157
+ parts.push(`bun --filter ${pkgName} run test`);
158
+ else
159
+ parts.push(`npm -w ${pkgName} run test`);
160
+ }
161
+ else {
162
+ // No package-level test script — try running root test scoped to the package path
163
+ // This handles monorepos like wp-calypso with `jest --testPathPattern`
164
+ const rootPkg = JSON.parse(readFileSync(join(workdir, 'package.json'), 'utf-8'));
165
+ const rootScripts = rootPkg.scripts || {};
166
+ // Check for common monorepo test patterns
167
+ if (rootScripts['test-packages']) {
168
+ if (pm === 'yarn')
169
+ parts.push(`yarn test-packages ${dir}`);
170
+ else
171
+ parts.push(`${pm} run test-packages ${dir}`);
172
+ }
173
+ }
174
+ }
175
+ catch { /* skip this package */ }
176
+ }
177
+ if (parts.length === 0)
178
+ return null;
179
+ console.log(`[validation] Monorepo: scoped to ${changedDirs.length} package(s): ${changedDirs.join(', ')}`);
180
+ return parts.join(' && ');
181
+ }
182
+ /**
183
+ * Walk up from a directory to find a monorepo root (directory with workspaces).
184
+ * Returns the root path or null if not inside a monorepo.
185
+ */
186
+ function findMonorepoRoot(startDir) {
187
+ let dir = startDir;
188
+ const root = '/';
189
+ while (dir !== root) {
190
+ if (getWorkspacePatterns(dir))
191
+ return dir;
192
+ const parent = join(dir, '..');
193
+ if (parent === dir)
194
+ break;
195
+ dir = parent;
196
+ }
197
+ return null;
198
+ }
54
199
  /**
55
200
  * Build the validation command for a project directory.
56
- * Checks for `build` and `test` scripts in package.json, then runs them
57
- * with the detected package manager. Falls back to `<pm> build && <pm> test`.
201
+ *
202
+ * Resolution order:
203
+ * 1. Per-project override from config `codeAgents.validationCommands`
204
+ * 2. Monorepo auto-detection: scope to changed packages only
205
+ * - Works both when workdir is the repo root AND when it's a package subdir
206
+ * 3. Auto-detect from package.json scripts (build + test)
207
+ * 4. Empty string (skip validation) if no scripts found
58
208
  */
59
- export function buildValidationCommand(workdir) {
209
+ export function buildValidationCommand(workdir, validationCommands) {
210
+ // 1. Check per-project overrides
211
+ if (validationCommands) {
212
+ const dirName = workdir.split('/').pop() || '';
213
+ for (const [key, cmd] of Object.entries(validationCommands)) {
214
+ if (key === dirName || workdir === key || workdir.endsWith(`/${key}`)) {
215
+ return cmd;
216
+ }
217
+ }
218
+ }
219
+ // 2. Monorepo auto-detection — check workdir and parent dirs
220
+ const monorepoCmd = buildMonorepoValidationCommand(workdir);
221
+ if (monorepoCmd)
222
+ return monorepoCmd;
223
+ // Also check if workdir is a subpackage inside a monorepo
224
+ const monorepoRoot = findMonorepoRoot(workdir);
225
+ if (monorepoRoot && monorepoRoot !== workdir) {
226
+ const rootCmd = buildMonorepoValidationCommand(monorepoRoot);
227
+ if (rootCmd)
228
+ return rootCmd;
229
+ }
230
+ // 3. Simple project — use root package.json scripts
60
231
  const pm = detectPackageManager(workdir);
61
232
  const run = pm === 'npm' ? 'npm run' : pm;
62
- // Check which scripts exist in package.json
63
233
  let hasBuild = false;
64
234
  let hasTest = false;
65
235
  try {
@@ -77,19 +247,23 @@ export function buildValidationCommand(workdir) {
77
247
  parts.push(`${run} build`);
78
248
  if (hasTest)
79
249
  parts.push(`${run} test`);
80
- // If neither build nor test scripts exist, still try — the scripts
81
- // might be defined in a workspace root or the commands may work anyway
82
250
  if (parts.length === 0) {
83
- parts.push(`${run} build`, `${run} test`);
251
+ return '';
84
252
  }
85
253
  return parts.join(' && ');
86
254
  }
87
255
  /** Run build/test validation. Shared by solo agents and team orchestrator. */
88
- export function runValidation(workdir) {
89
- const cmd = buildValidationCommand(workdir);
256
+ export function runValidation(workdir, validationCommands) {
257
+ const cmd = buildValidationCommand(workdir, validationCommands);
258
+ if (!cmd) {
259
+ // No build/test scripts found — nothing to validate, pass by default
260
+ return Promise.resolve({ passed: true, output: 'PASS (no build/test scripts found)' });
261
+ }
262
+ // If workdir is inside a monorepo, run from the repo root so workspace commands work
263
+ const execDir = findMonorepoRoot(workdir) || workdir;
90
264
  return new Promise((resolve) => {
91
265
  exec(cmd, {
92
- cwd: workdir,
266
+ cwd: execDir,
93
267
  timeout: VALIDATE_TIMEOUT_MS,
94
268
  maxBuffer: 5 * 1024 * 1024,
95
269
  }, (error, vStdout, vStderr) => {
@@ -315,7 +489,7 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
315
489
  caTask.outputPreview = agentOutput.slice(0, 500);
316
490
  caTask.liveOutput = undefined;
317
491
  writeCodeAgentTask(caTask);
318
- const validationCmd = buildValidationCommand(workdir);
492
+ const validationCmd = buildValidationCommand(workdir, options?.validationCommands);
319
493
  const runValidationPromise = () => new Promise((res) => {
320
494
  const validationProc = exec(validationCmd, {
321
495
  cwd: workdir,
@@ -521,7 +695,7 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
521
695
  await notifyCodeAgentResult(caTask, (id) => getCodeAgent(id) ?? null);
522
696
  }
523
697
  catch (err) {
524
- const errMsg = err instanceof Error ? err.message : String(err);
698
+ const errMsg = toErrorMessage(err);
525
699
  addEvent(traceId, { type: 'error', summary: errMsg.slice(0, 200), durationMs: Date.now() - startedAt.getTime() });
526
700
  await endTrace(traceId, 'error');
527
701
  Object.assign(caTask, {
@@ -4,7 +4,7 @@ export type { CodeAgentTask, DecomposedSubtask, CodeAgentBackgroundOptions, Buil
4
4
  export { CODE_AGENT_TIMEOUT_MS, VALIDATE_TIMEOUT_MS } from './types.js';
5
5
  export { getActiveCodeAgents, getRecentCodeAgents, getAllCodeAgents, getCodeAgent, cancelCodeAgent, restoreCodeAgentTasks, getCodeAgentsDir, } from './registry.js';
6
6
  export { runCodeAgentBackground, runValidation } from './executor.js';
7
- export { runTeamOrchestrator, computeWaves, decomposeTask, synthesizeResults, } from './orchestrator.js';
7
+ export { runTeamOrchestrator, computeWaves, decomposeTask, synthesizeResults, gatherCodebaseContext, } from './orchestrator.js';
8
8
  export { setCodeAgentConfig, getCodeAgentConfig, buildCodeAgentArgs, resolveSelectedCodeAgent, resolveWorkdir, resolveModelAlias, readTeamState, } from './utils.js';
9
9
  export { parseStreamJsonForLive, parseClaudeOutput, parseCodexOutput } from './parser.js';
10
10
  export type { ClaudeOutputResult } from './parser.js';
@@ -5,7 +5,7 @@ import { isPathAllowed } from '../tools/path-utils.js';
5
5
  import { getNextCodeAgentId, storeCodeAgentTask, writeCodeAgentTask, getActiveCodeAgents, getRecentCodeAgents, getCodeAgent, } from './registry.js';
6
6
  import { runCodeAgentBackground } from './executor.js';
7
7
  import { runTeamOrchestrator } from './orchestrator.js';
8
- import { resolveSelectedCodeAgent, resolveWorkdir, resolveModelAlias, } from './utils.js';
8
+ import { resolveSelectedCodeAgent, resolveWorkdir, resolveModelAlias, getCodingCliPreflightError, } from './utils.js';
9
9
  // Re-export timeout constants
10
10
  export { CODE_AGENT_TIMEOUT_MS, VALIDATE_TIMEOUT_MS } from './types.js';
11
11
  // Re-export registry functions
@@ -13,7 +13,7 @@ export { getActiveCodeAgents, getRecentCodeAgents, getAllCodeAgents, getCodeAgen
13
13
  // Re-export executor functions
14
14
  export { runCodeAgentBackground, runValidation } from './executor.js';
15
15
  // Re-export orchestrator functions
16
- export { runTeamOrchestrator, computeWaves, decomposeTask, synthesizeResults, } from './orchestrator.js';
16
+ export { runTeamOrchestrator, computeWaves, decomposeTask, synthesizeResults, gatherCodebaseContext, } from './orchestrator.js';
17
17
  // Re-export utility functions
18
18
  export { setCodeAgentConfig, getCodeAgentConfig, buildCodeAgentArgs, resolveSelectedCodeAgent, resolveWorkdir, resolveModelAlias, readTeamState, } from './utils.js';
19
19
  // Re-export parser functions
@@ -80,7 +80,7 @@ export async function executeCodeWithAgent(input, config, context) {
80
80
  catch { /* gateway not running */ }
81
81
  }
82
82
  const resolvedModel = resolveModelAlias(rawModel, context?.fullConfig?.models?.aliases);
83
- const configDefault = context?.fullConfig?.subagents?.defaultCodeAgent || 'claude';
83
+ const configDefault = context?.fullConfig?.codeAgents?.defaultAgent || 'claude';
84
84
  const requestedAgent = input.agent;
85
85
  const agent = resolveSelectedCodeAgent(requestedAgent, configDefault, resolvedModel);
86
86
  if (!agent) {
@@ -99,12 +99,15 @@ export async function executeCodeWithAgent(input, config, context) {
99
99
  : '';
100
100
  return `Error: Working directory not allowed. Permitted: ${config.allowedPaths.join(', ')}${projectNames}`;
101
101
  }
102
- // Concurrency check — share limit with subagents
103
- const maxConcurrent = context?.fullConfig?.subagents?.maxConcurrent ?? 5;
102
+ // Concurrency check
103
+ const maxConcurrent = context?.fullConfig?.codeAgents?.maxConcurrent ?? 5;
104
104
  const activeCount = getActiveCodeAgents().length;
105
105
  if (activeCount >= maxConcurrent) {
106
- return `Error: Concurrency limit reached (${activeCount}/${maxConcurrent} coding agents running). Wait for one to finish or increase subagents.maxConcurrent.`;
106
+ return `Error: Concurrency limit reached (${activeCount}/${maxConcurrent} coding agents running). Wait for one to finish or increase codeAgents.maxConcurrent.`;
107
107
  }
108
+ const cliPreflightError = getCodingCliPreflightError();
109
+ if (cliPreflightError)
110
+ return cliPreflightError;
108
111
  const validate = input.validate !== false; // default true
109
112
  // Create task with unique ID
110
113
  const id = getNextCodeAgentId();
@@ -122,8 +125,14 @@ export async function executeCodeWithAgent(input, config, context) {
122
125
  storeCodeAgentTask(caTask);
123
126
  writeCodeAgentTask(caTask);
124
127
  // Fire-and-forget: spawn background process
125
- const resolvedInput = { ...input, model: resolvedModel };
126
- runCodeAgentBackground(id, agent, task, workdir, validate, resolvedInput, startedAt).catch((err) => {
128
+ const configTimeout = context?.fullConfig?.codeAgents?.timeoutMinutes ?? 30;
129
+ const soloTimeout = Math.min(input.timeout_minutes || configTimeout, 60);
130
+ const resolvedInput = { ...input, model: resolvedModel, timeout_minutes: soloTimeout };
131
+ runCodeAgentBackground(id, agent, task, workdir, validate, resolvedInput, startedAt, {
132
+ defaultTimeoutMinutes: soloTimeout,
133
+ maxTimeoutMinutes: 60,
134
+ validationCommands: context?.fullConfig?.codeAgents?.validationCommands,
135
+ }).catch((err) => {
127
136
  console.error(`[code-agent] Background error for ${id}:`, err);
128
137
  });
129
138
  const taskPreview = task.length > 100 ? task.slice(0, 100) + '...' : task;
@@ -136,18 +145,14 @@ export async function executeCodeWithTeam(input, config, context) {
136
145
  const task = input.task;
137
146
  if (!task)
138
147
  return 'Error: task is required';
139
- // Resolve model alias first so agent auto-selection can inspect it.
140
- // Fall back to current session model so codex/kimi models auto-select the right CLI.
141
- let rawTeamModel = input.model;
142
- if (!rawTeamModel) {
143
- try {
144
- const { getCurrentModel } = await import('../gateway.js');
145
- rawTeamModel = getCurrentModel();
146
- }
147
- catch { /* gateway not running */ }
148
- }
149
- const resolvedModel = resolveModelAlias(rawTeamModel, context?.fullConfig?.models?.aliases);
150
- const configDefault = context?.fullConfig?.subagents?.defaultCodeAgent || 'claude';
148
+ // Resolve model alias. Only fall back to session model when an explicit model
149
+ // was requested otherwise the session model (e.g. gpt-5.3-codex) would
150
+ // override agent selection even when the user wants claude.
151
+ const rawTeamModel = input.model;
152
+ const resolvedModel = rawTeamModel
153
+ ? resolveModelAlias(rawTeamModel, context?.fullConfig?.models?.aliases)
154
+ : undefined;
155
+ const configDefault = context?.fullConfig?.codeAgents?.defaultAgent || 'claude';
151
156
  const requestedAgent = input.agent;
152
157
  const agent = resolveSelectedCodeAgent(requestedAgent, configDefault, resolvedModel);
153
158
  if (!agent) {
@@ -167,13 +172,16 @@ export async function executeCodeWithTeam(input, config, context) {
167
172
  : '';
168
173
  return `Error: Working directory not allowed. Permitted: ${config.allowedPaths.join(', ')}${projectNames}`;
169
174
  }
175
+ const validate = input.validate !== false;
170
176
  // Concurrency check — need room for teamSize children
171
- const maxConcurrent = context?.fullConfig?.subagents?.maxConcurrent ?? 5;
177
+ const maxConcurrent = context?.fullConfig?.codeAgents?.maxConcurrent ?? 5;
172
178
  const activeCount = getActiveCodeAgents().length;
173
179
  if (activeCount + teamSize > maxConcurrent) {
174
180
  return `Error: Concurrency limit — need ${teamSize} slots but only ${maxConcurrent - activeCount} available (${activeCount}/${maxConcurrent} running). Wait for agents to finish.`;
175
181
  }
176
- const validate = input.validate !== false;
182
+ const cliPreflightError = getCodingCliPreflightError();
183
+ if (cliPreflightError)
184
+ return cliPreflightError;
177
185
  // Create parent task
178
186
  const id = getNextCodeAgentId();
179
187
  const startedAt = new Date();
@@ -7,16 +7,22 @@ import type { DecomposedSubtask, ChildResult } from './types.js';
7
7
  * Throws if there's a cycle in the dependency graph.
8
8
  */
9
9
  export declare function computeWaves(subtasks: DecomposedSubtask[]): number[][];
10
+ /**
11
+ * Gather lightweight codebase context to improve task decomposition.
12
+ * Returns a short summary of the project structure (file tree, package.json scripts).
13
+ * Capped at ~2000 chars to keep the decomposition prompt small.
14
+ */
15
+ export declare function gatherCodebaseContext(workdir: string): string;
10
16
  /**
11
17
  * Use a quick model call to decompose a complex task into N subtasks with optional dependency info.
12
18
  * Falls back to numbered subtask splitting on parse error.
13
19
  * Falls back to all-independent if dependency info is missing or invalid.
14
20
  */
15
- export declare function decomposeTask(task: string, teamSize: number, config: Config): Promise<DecomposedSubtask[]>;
21
+ export declare function decomposeTask(task: string, teamSize: number, config: Config, workdir?: string): Promise<DecomposedSubtask[]>;
16
22
  /**
17
23
  * Use a quick model call to synthesize results from multiple subtask completions.
18
24
  */
19
- export declare function synthesizeResults(originalTask: string, results: ChildResult[], config: Config): Promise<string>;
25
+ export declare function synthesizeResults(originalTask: string, results: ChildResult[], config: Config, workdir?: string): Promise<string>;
20
26
  /**
21
27
  * Team orchestrator — decomposes task, spawns parallel agents, monitors, synthesizes.
22
28
  */