Skip to content

Commit 198ccc0

Browse files
recuu-pfegclaude
andcommitted
refactor: final CLI cleanup — manager split + dedup + constants
Manager split (926 → ~350 lines): - manager-actions.ts: response parsing, action execution, validation - manager-prompt.ts: system prompt, codebase summary, conversation history Dedup: - summarizeTool: removed from llm-provider, import from agent-engine - createAuthHeaders: consolidated 10 manual header constructions - Validation constants: VALID_OWNERS etc. consolidated in manager-actions Fixes: - setup.ts: removed duplicate fetchAndResetToRemote import All 71 tests pass. Build succeeds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7ae7b43 commit 198ccc0

23 files changed

Lines changed: 652 additions & 578 deletions

src/agent-engine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ function parseStreamJsonEvents(event: Record<string, unknown>): AgentActivity[]
438438
return activities;
439439
}
440440

441-
function summarizeToolInput(toolName: string, input?: Record<string, unknown>): string {
441+
export function summarizeToolInput(toolName: string, input?: Record<string, unknown>): string {
442442
if (!input) return toolName;
443443
switch (toolName) {
444444
case "Read":

src/agent-templates.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* templates can be loaded from the API or a YAML config file.
77
*/
88

9+
import { execSync } from "node:child_process";
910
import type { ApiClient, Task } from "./api-client.js";
1011
import * as ui from "./ui.js";
1112
import { logError, CLI_ERR } from "./error-logger.js";
@@ -447,7 +448,7 @@ export async function executeActions(
447448
break;
448449
}
449450
case "git_auth_check": {
450-
const { execSync: exec } = await import("node:child_process");
451+
const exec = execSync;
451452
try {
452453
// Use ls-remote without --exit-code (empty repos return exit 2 with --exit-code)
453454
exec("git ls-remote origin", {
@@ -502,7 +503,6 @@ export async function executeActions(
502503
case "shell": {
503504
const cmd = action.params?.command as string | undefined;
504505
if (cmd) {
505-
const { execSync } = await import("node:child_process");
506506
execSync(cmd, { cwd: ctx.config.workingDir, stdio: "pipe" });
507507
ui.info( `[${phase}] ${label}`);
508508
}
@@ -535,11 +535,3 @@ export function getDefaultTemplates(): AgentTemplate[] {
535535
return DEFAULT_TEMPLATES;
536536
}
537537

538-
/**
539-
* Get the list of default templates.
540-
* In the future, this will merge system defaults with user-defined templates
541-
* loaded from the API or a config file.
542-
*/
543-
export function getTemplates(): AgentTemplate[] {
544-
return DEFAULT_TEMPLATES;
545-
}

src/api-client.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ export interface ApiClient {
117117
recordFailure(data: { task_id: string; failure_type: string; summary: string; agent_name?: string; sprint?: number; review_comment?: string; files_involved?: string }): Promise<void>;
118118
}
119119

120+
/** Create standard auth headers for API requests. */
121+
export function createAuthHeaders(apiKey: string): Record<string, string> {
122+
return { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` };
123+
}
124+
120125
/** Retry a fetch call on 5xx errors (D1 transient failures) */
121126
export async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 2): Promise<Response> {
122127
for (let attempt = 0; attempt <= maxRetries; attempt++) {
@@ -128,10 +133,7 @@ export async function fetchWithRetry(url: string, options: RequestInit, maxRetri
128133
}
129134

130135
export function createApiClient(apiUrl: string, apiKey: string): ApiClient {
131-
const headers = {
132-
"Content-Type": "application/json",
133-
Authorization: `Bearer ${apiKey}`,
134-
};
136+
const headers = createAuthHeaders(apiKey);
135137

136138
return {
137139
async fetchWorkspace(): Promise<WorkspaceInfo> {

src/commands/plan.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Sprint Plan command (Strategist agent)
33
*/
44

5-
import { createApiClient, type Task } from "../api-client.js";
5+
import { createApiClient, createAuthHeaders, type Task } from "../api-client.js";
66
import { resolveModelForRole } from "../agent-engine.js";
77
import * as ui from "../ui.js";
88

@@ -20,7 +20,7 @@ export async function handleSprintPlan(apiUrl: string, apiKey: string): Promise<
2020

2121
ui.info(`[strategist] Planning Sprint #${sprint.number}...`);
2222

23-
const headers = { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` };
23+
const headers = createAuthHeaders(apiKey);
2424

2525
// Fetch backlog, analytics, failures, retro
2626
const [backlogRes, analyticsRes, failuresRes] = await Promise.all([

src/commands/propose.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Propose command (Strategist agent — analyze data sources and suggest improvement tasks)
33
*/
44

5-
import { createApiClient, type Task } from "../api-client.js";
5+
import { createApiClient, createAuthHeaders, type Task } from "../api-client.js";
66
import { resolveModelForRole } from "../agent-engine.js";
77
import * as ui from "../ui.js";
88

@@ -12,7 +12,7 @@ export async function handlePropose(apiUrl: string, apiKey: string): Promise<voi
1212

1313
try {
1414
ui.info("[strategist] Analyzing data sources for improvement proposals...");
15-
const headers = { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` };
15+
const headers = createAuthHeaders(apiKey);
1616

1717
// Gather data sources
1818
const [failuresRes, backlogRes, analyticsRes] = await Promise.all([

src/commands/review.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Review command
33
*/
44

5-
import { createApiClient, type Task } from "../api-client.js";
5+
import { createApiClient, createAuthHeaders, type Task } from "../api-client.js";
66
import * as ui from "../ui.js";
77
import { parseTaskLabels } from "../utils/parse-labels.js";
88
import { spawnClaudeOnce } from "../utils/spawn-claude.js";
@@ -92,7 +92,7 @@ export async function handleReview(apiUrl: string, apiKey: string, taskId?: stri
9292
try {
9393
await fetch(`${apiUrl}/api/v1/tasks/${task.id}/review-report`, {
9494
method: "POST",
95-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
95+
headers: createAuthHeaders(apiKey),
9696
body: JSON.stringify(report),
9797
});
9898
console.log("\nReview saved to API.");

src/commands/run-loop.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
import type { AgentRunner } from "../runner.js";
6-
import type { Task } from "../api-client.js";
6+
import { createAuthHeaders, type Task } from "../api-client.js";
77
import { buildAgentPrompt } from "../prompt.js";
88
import { getEngine, resolveModel, resolveModelForRole } from "../agent-engine.js";
99
import { matchTemplate, executeActions, type ActionContext } from "../agent-templates.js";
@@ -141,7 +141,7 @@ export async function runLoop(cliArgs: CliArgs, runner: AgentRunner, shutdownSta
141141
// Auto-run Strategist proposals when sprint enters retrospective (once only)
142142
if (sprint?.status === "retrospective") {
143143
try {
144-
const headers = { Authorization: `Bearer ${cliArgs.apiKey}`, "Content-Type": "application/json" };
144+
const headers = createAuthHeaders(cliArgs.apiKey);
145145
const plansRes = await fetch(`${cliArgs.apiUrl}/api/v1/sprints/${sprint.number}/plan`, { headers });
146146
if (plansRes.ok) {
147147
const plan = (await plansRes.json()) as { status: string; id: string };
@@ -186,7 +186,7 @@ export async function runLoop(cliArgs: CliArgs, runner: AgentRunner, shutdownSta
186186
try {
187187
await fetch(`${cliArgs.apiUrl}/api/v1/sprints/${sprint.number}`, {
188188
method: "PATCH",
189-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${cliArgs.apiKey}` },
189+
headers: createAuthHeaders(cliArgs.apiKey),
190190
body: JSON.stringify({ status: "review" }),
191191
});
192192
wsServer?.broadcast({ type: "data_update" as const, entity: "sprint", task_id: String(sprint.number), changes: { status: "review" }, timestamp: new Date().toISOString() });
@@ -220,7 +220,7 @@ export async function runLoop(cliArgs: CliArgs, runner: AgentRunner, shutdownSta
220220
for (const sub of subtasks) {
221221
await fetch(`${cliArgs.apiUrl}/api/v1/tasks`, {
222222
method: "POST",
223-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${cliArgs.apiKey}` },
223+
headers: createAuthHeaders(cliArgs.apiKey),
224224
body: JSON.stringify({ ...sub, sprint: sprintNum, parent_task: t.id, status: "todo" }),
225225
});
226226
}
@@ -515,7 +515,7 @@ export async function runLoop(cliArgs: CliArgs, runner: AgentRunner, shutdownSta
515515

516516
await fetch(`${cliArgs.apiUrl}/api/v1/sprints/${sprintData.sprint.number}/retro`, {
517517
method: "POST",
518-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${cliArgs.apiKey}` },
518+
headers: createAuthHeaders(cliArgs.apiKey),
519519
body: JSON.stringify({
520520
agent_name: agentConfig.name,
521521
went_well: safeWentWell,

src/error-logger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export function logError(
113113
* Wrap an async function with error logging.
114114
* Returns the result or undefined on error.
115115
*/
116-
export async function withErrorLog<T>(
116+
async function withErrorLog<T>(
117117
code: CliErrorCode,
118118
context: Record<string, unknown>,
119119
fn: () => Promise<T>,

src/handlers/git-merge.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
* Merges the agent's worktree branch into the base branch.
44
*/
55

6+
import { execSync } from "node:child_process";
7+
import { existsSync, rmSync } from "node:fs";
8+
import { join } from "node:path";
69
import type { TemplateAction, ActionContext } from "../agent-templates.js";
710
import * as ui from "../ui.js";
811
import { logError, CLI_ERR } from "../error-logger.js";
@@ -14,9 +17,9 @@ export async function handleGitMerge(
1417
phase: "pre" | "post"
1518
): Promise<void> {
1619
const label = action.label ?? "git_merge";
17-
const { execSync: gitExec } = await import("node:child_process");
18-
const { existsSync: gitExists } = await import("node:fs");
19-
const { join: gitJoin } = await import("node:path");
20+
const gitExec = execSync;
21+
const gitExists = existsSync;
22+
const gitJoin = join;
2023
// workingDir is the worktree path — we need the main repo root
2124
const worktreePath = ctx.config.workingDir;
2225
const repoDir = resolveRepoRoot(worktreePath);
@@ -49,7 +52,6 @@ export async function handleGitMerge(
4952
ctx.mergeSkipped = true;
5053
// Clean up the empty branch
5154
if (gitExists(worktreeDir)) {
52-
const { rmSync } = await import("node:fs");
5355
try { rmSync(worktreeDir, { recursive: true, force: true }); } catch { /* non-fatal */ }
5456
}
5557
try { gitExec("git worktree prune", { cwd: repoDir, stdio: "pipe" }); } catch { /* non-fatal */ }
@@ -72,7 +74,6 @@ export async function handleGitMerge(
7274
ui.warn(`[${phase}] ${label}: only metadata files changed (${diffFiles.join(", ")}) — skipping merge`);
7375
ctx.mergeSkipped = true;
7476
if (gitExists(worktreeDir)) {
75-
const { rmSync } = await import("node:fs");
7677
try { rmSync(worktreeDir, { recursive: true, force: true }); } catch { /* non-fatal */ }
7778
}
7879
try { gitExec("git worktree prune", { cwd: repoDir, stdio: "pipe" }); } catch { /* non-fatal */ }
@@ -89,7 +90,6 @@ export async function handleGitMerge(
8990

9091
// Clean up worktree
9192
if (gitExists(worktreeDir)) {
92-
const { rmSync } = await import("node:fs");
9393
try { rmSync(worktreeDir, { recursive: true, force: true }); } catch { /* non-fatal */ }
9494
}
9595
try { gitExec("git worktree prune", { cwd: repoDir, stdio: "pipe" }); } catch { /* non-fatal */ }

src/handlers/git-push.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Pushes the base branch to the remote origin.
44
*/
55

6+
import { execSync } from "node:child_process";
67
import type { TemplateAction, ActionContext } from "../agent-templates.js";
78
import * as ui from "../ui.js";
89
import { logError, CLI_ERR } from "../error-logger.js";
@@ -15,7 +16,7 @@ export async function handleGitPush(
1516
): Promise<void> {
1617
const label = action.label ?? "git_push";
1718
if (ctx.mergeSkipped) { ui.info(`[${phase}] ${label}: skipped (no merge)`); return; }
18-
const { execSync: pushExec } = await import("node:child_process");
19+
const pushExec = execSync;
1920
// Resolve repo root (workingDir may be a worktree)
2021
const pushRepoDir = resolveRepoRoot(ctx.config.workingDir);
2122
// Stash any unstaged changes (e.g. inject_memory's CLAUDE.md modifications)

0 commit comments

Comments
 (0)