export const AGENT_TASK_TIMEOUT_MS = 6_000_000
+export const COMPLETION_SIGNAL = '<promise>COMPLETE</promise>'
+
+export const MAX_PARALLEL = 5
+
// ── Git ──────────────────────────────────────────────────────────────────────
export const GIT_BASE_BRANCH = 'main'
export const GITHUB_MAX_PRS_FETCH = 200
+export const MAX_TITLE_CHARS = 200
+
// ── Validation ───────────────────────────────────────────────────────────────
+export const MAX_STDERR_CHARS = 500
+
export const VALIDATION_COMMAND =
'pnpm format && pnpm typecheck && pnpm lint && pnpm build && pnpm test'
export const VALIDATION_TIMEOUT_MS = 300_000
-// ── Limits & Protocol ────────────────────────────────────────────────────────
-
-export const COMPLETION_SIGNAL = '<promise>COMPLETE</promise>'
+// ── Deduplication ────────────────────────────────────────────────────────────
export const CONTEXT_HASH_RADIUS = 3
-export const GRACE_TIMEOUT_MS = 30_000
-
export const HASH_PREFIX_LENGTH = 16
-
-export const MAX_PARALLEL = 5
-
-export const MAX_STDERR_CHARS = 500
-
-export const MAX_TITLE_CHARS = 200
import type { LoopResult, TaskSpec } from './types.js'
-import {
- GIT_BASE_BRANCH,
- GIT_PUSH_TIMEOUT_MS,
- GIT_TIMEOUT_MS,
- MAX_STDERR_CHARS,
- VALIDATION_COMMAND,
- VALIDATION_TIMEOUT_MS,
-} from './constants.js'
+import { GIT_BASE_BRANCH, GIT_PUSH_TIMEOUT_MS, GIT_TIMEOUT_MS } from './constants.js'
import { execFileAsync, toErrorMessage } from './utils.js'
/**
return { isDraft, prArgs }
}
-/**
- * Extracts stderr from a caught error, truncated to 500 chars.
- * @param err - The caught error value.
- * @returns Stderr string or empty string if unavailable.
- */
-export function extractStderr (err: unknown): string {
- return err instanceof Error && 'stderr' in err
- ? String((err as { stderr: unknown }).stderr).slice(0, MAX_STDERR_CHARS)
- : ''
-}
-
/**
* Pushes the branch to origin. When rebase succeeded, uses force-with-lease
* with a rescue-branch fallback. When rebase was aborted, does a plain push.
}
}
}
-
-/**
- * Runs the full validation suite.
- * @param cwd - Working directory (worktree path).
- * @param spec - Optional task specification (used for logging).
- * @returns `true` if validation passed, `false` otherwise.
- */
-export async function runValidation (cwd: string, spec?: TaskSpec): Promise<boolean> {
- try {
- await execFileAsync('sh', ['-c', VALIDATION_COMMAND], {
- cwd,
- maxBuffer: 8 * 1024 * 1024,
- timeout: VALIDATION_TIMEOUT_MS,
- })
- return true
- } catch (err: unknown) {
- if (err && typeof err === 'object' && 'killed' in err && (err as { killed: boolean }).killed) {
- const label = spec ? `#${spec.id}` : 'mid-loop'
- console.warn(` ${label}: Validation timed out after ${String(VALIDATION_TIMEOUT_MS)}ms.`)
- } else if (spec) {
- const stderr = extractStderr(err)
- console.warn(` #${spec.id}: Validation failed.${stderr ? `\n${stderr}` : ''}`)
- }
- return false
- }
-}
GIT_BASE_BRANCH,
HASH_PREFIX_LENGTH,
} from './constants.js'
-import { runValidation } from './finalizer.js'
import { parseFindingsSafe } from './types.js'
import { execFileAsync } from './utils.js'
+import { runValidation } from './validation.js'
/** Options for configuring the refinement loop. */
export interface RefinementLoopOptions {
idleTimeoutSeconds: AGENT_IDLE_TIMEOUT_S,
maxIterations: 1,
name: `Critic #${spec.id} R${String(round)}`,
- promptArgs: strategy.buildCriticArgs(spec, nonce, baseBranch),
+ promptArgs: { ...strategy.buildCriticArgs(spec, baseBranch), NONCE: nonce },
promptFile: strategy.criticPromptFile,
signal,
})
idleTimeoutSeconds: AGENT_IDLE_TIMEOUT_S,
maxIterations: 1,
name: `Critic #${spec.id} R${String(round)} retry`,
- promptArgs: strategy.buildCriticArgs(spec, nonce, baseBranch),
+ promptArgs: { ...strategy.buildCriticArgs(spec, baseBranch), NONCE: nonce },
promptFile: strategy.criticPromptFile,
signal,
})
import type { FinalizationConfig, LoopStrategy } from '../../types.js'
import { GIT_TIMEOUT_MS } from '../../constants.js'
-import { attemptRebase, buildPrArgs, pushBranch, runValidation } from '../../finalizer.js'
+import { attemptRebase, buildPrArgs, pushBranch } from '../../finalizer.js'
import { execFileAsync, toErrorMessage } from '../../utils.js'
+import { runValidation } from '../../validation.js'
export const implementStrategy: FinalizationConfig & LoopStrategy = {
actorPromptFile: './.sandcastle/strategies/implement/implement-prompt.md',
TASK_ID: spec.id,
}),
- buildCriticArgs: (spec, nonce, baseBranch) => ({
+ buildCriticArgs: (spec, baseBranch) => ({
BASE_BRANCH: baseBranch,
BRANCH: spec.branch,
- NONCE: nonce,
}),
criticPromptFile: './.sandcastle/strategies/implement/critic-prompt.md',
import { z } from 'zod'
/** Zod schema for a single critic finding. */
-export const FindingSchema = z.object({
+const FindingSchema = z.object({
category: z.string(),
confidence: z.enum(['HIGH', 'MEDIUM', 'LOW']),
description: z.string(),
actorPromptFile: string
/** Builds promptArgs for the actor run from task spec and previous findings. */
buildActorArgs: (spec: TaskSpec, findings: Finding[]) => Record<string, string>
- /** Builds promptArgs for the critic run from task spec, nonce, and base branch. */
- buildCriticArgs: (spec: TaskSpec, nonce: string, baseBranch: string) => Record<string, string>
+ /** Builds promptArgs for the critic run from task spec and base branch. */
+ buildCriticArgs: (spec: TaskSpec, baseBranch: string) => Record<string, string>
/** Model for the critic agent. Defaults to AGENT_CRITIC_MODEL constant. */
criticModel?: string
/** Path to the critic prompt file. */
--- /dev/null
+import type { TaskSpec } from './types.js'
+
+import { MAX_STDERR_CHARS, VALIDATION_COMMAND, VALIDATION_TIMEOUT_MS } from './constants.js'
+import { execFileAsync } from './utils.js'
+
+/**
+ * Runs the full validation suite.
+ * @param cwd - Working directory (worktree path).
+ * @param spec - Optional task specification (used for logging).
+ * @returns `true` if validation passed, `false` otherwise.
+ */
+export async function runValidation (cwd: string, spec?: TaskSpec): Promise<boolean> {
+ try {
+ await execFileAsync('sh', ['-c', VALIDATION_COMMAND], {
+ cwd,
+ maxBuffer: 8 * 1024 * 1024,
+ timeout: VALIDATION_TIMEOUT_MS,
+ })
+ return true
+ } catch (err: unknown) {
+ if (err && typeof err === 'object' && 'killed' in err && (err as { killed: boolean }).killed) {
+ const label = spec ? `#${spec.id}` : 'mid-loop'
+ console.warn(` ${label}: Validation timed out after ${String(VALIDATION_TIMEOUT_MS)}ms.`)
+ } else if (spec) {
+ const stderr = extractStderr(err)
+ console.warn(` #${spec.id}: Validation failed.${stderr ? `\n${stderr}` : ''}`)
+ }
+ return false
+ }
+}
+
+/**
+ * Extracts stderr from a caught error, truncated to 500 chars.
+ * @param err - The caught error value.
+ * @returns Stderr string or empty string if unavailable.
+ */
+function extractStderr (err: unknown): string {
+ return err instanceof Error && 'stderr' in err
+ ? String((err as { stderr: unknown }).stderr).slice(0, MAX_STDERR_CHARS)
+ : ''
+}