import assert from "node:assert/strict"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import * as path from "node:path"; import test from "node:test"; import { loadWorkflowConfig } from "./config.ts"; import { DEFAULT_WORKFLOW_CONFIG, MAX_CONCURRENCY } from "./domain.ts"; async function withTempDir(run: (directory: string) => Promise) { const directory = await mkdtemp(path.join(tmpdir(), "workflow-config-")); try { await run(directory); } finally { await rm(directory, { recursive: true, force: true }); } } async function writeJson(file: string, value: unknown) { await mkdir(path.dirname(file), { recursive: true }); await writeFile(file, `${JSON.stringify(value)}\n`); } test("configuration defaults to concurrency 8 with immutable ceiling 32", async () => { await withTempDir(async (directory) => { const config = await loadWorkflowConfig({ cwd: directory, agentDir: path.join(directory, "agent"), projectTrusted: false, }); assert.deepEqual(config, DEFAULT_WORKFLOW_CONFIG); assert.equal(config.concurrency.ceiling, MAX_CONCURRENCY); assert.deepEqual(config.standardAgents, { enabled: true, roles: { scout: ["discover"], worker: ["implement"], }, }); }); }); test("a lowered ceiling clamps an inherited default", async () => { await withTempDir(async (directory) => { const agentDir = path.join(directory, "agent"); await writeJson(path.join(agentDir, "workflow.json"), { concurrency: { ceiling: 4 }, }); const globalOnly = await loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false, }); assert.deepEqual(globalOnly.concurrency, { default: 4, ceiling: 4 }); await writeJson(path.join(agentDir, "workflow.json"), { concurrency: { default: 6, ceiling: 16 }, }); await writeJson(path.join(directory, ".pi", "workflow.json"), { concurrency: { ceiling: 3 }, }); const project = await loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: true, }); assert.deepEqual(project.concurrency, { default: 3, ceiling: 3 }); }); }); test("trusted project configuration layers over global configuration and may lower limits", async () => { await withTempDir(async (directory) => { const agentDir = path.join(directory, "agent"); await writeJson(path.join(agentDir, "workflow.json"), { concurrency: { default: 6, ceiling: 16 }, maxAssignmentsPerRun: 64, analysisTools: ["read", "grep", "find"], worktrees: { root: ".worktrees/global" }, }); await writeJson(path.join(directory, ".pi", "workflow.json"), { concurrency: { default: 4, ceiling: 8 }, maxReviewCycles: 5, analysisTools: ["read", "find", "ls"], worktrees: { root: ".worktrees/project" }, }); const config = await loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: true, invocationConcurrency: 7, }); assert.deepEqual(config.concurrency, { default: 7, ceiling: 8 }); assert.equal(config.maxAssignmentsPerRun, 64); assert.equal(config.maxReviewCycles, 5); assert.deepEqual(config.analysisTools, ["read", "find"]); assert.equal(config.worktrees.root, path.normalize(".worktrees/project")); }); }); test("untrusted project configuration is never applied", async () => { await withTempDir(async (directory) => { const agentDir = path.join(directory, "agent"); await writeJson(path.join(directory, ".pi", "workflow.json"), { concurrency: { default: 1, ceiling: 1 }, unknownField: true, }); const config = await loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false, }); assert.deepEqual(config.concurrency, { default: 8, ceiling: 32 }); }); }); test("trusted configuration can replace or disable standard-agent role overlays", async () => { await withTempDir(async (directory) => { const agentDir = path.join(directory, "agent"); await writeJson(path.join(agentDir, "workflow.json"), { standardAgents: { roles: { "codebase-analyzer": ["discover"], worker: ["implement", "verify"], }, }, }); let config = await loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false, }); assert.deepEqual(config.standardAgents.roles, { "codebase-analyzer": ["discover"], worker: ["implement", "verify"], }); await writeJson(path.join(directory, ".pi", "workflow.json"), { standardAgents: { enabled: false }, }); config = await loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: true, }); assert.equal(config.standardAgents.enabled, false); }); }); test("invalid standard-agent names and roles fail before a run starts", async () => { await withTempDir(async (directory) => { const agentDir = path.join(directory, "agent"); await writeJson(path.join(agentDir, "workflow.json"), { standardAgents: { roles: { Worker: ["implement"] } }, }); await assert.rejects( loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false }), /lowercase kebab case/, ); await writeJson(path.join(agentDir, "workflow.json"), { standardAgents: { roles: { worker: ["remediate"] } }, }); await assert.rejects( loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false }), /unsupported role remediate/, ); }); }); test("configuration cannot raise ceilings or activate unaudited tools", async () => { await withTempDir(async (directory) => { const agentDir = path.join(directory, "agent"); await writeJson(path.join(agentDir, "workflow.json"), { concurrency: { ceiling: 33 }, }); await assert.rejects( loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false }), /between 1 and 32/, ); await writeJson(path.join(agentDir, "workflow.json"), { analysisTools: ["read", "bash"], }); await assert.rejects( loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false }), /unsupported tools: bash/, ); }); }); test("project configuration cannot raise a lowered global ceiling", async () => { await withTempDir(async (directory) => { const agentDir = path.join(directory, "agent"); await writeJson(path.join(agentDir, "workflow.json"), { concurrency: { default: 4, ceiling: 8 }, }); await writeJson(path.join(directory, ".pi", "workflow.json"), { concurrency: { ceiling: 16 }, }); await assert.rejects( loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: true }), /cannot raise the current ceiling/, ); }); }); test("invalid numeric and path values fail before a run starts", async () => { await withTempDir(async (directory) => { const agentDir = path.join(directory, "agent"); await writeJson(path.join(agentDir, "workflow.json"), { maxAssignmentsPerRun: 1.5, }); await assert.rejects( loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false }), /must be an integer/, ); await writeJson(path.join(agentDir, "workflow.json"), { worktrees: { root: "../outside" }, }); await assert.rejects( loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false }), /must stay inside the repository/, ); await writeJson(path.join(agentDir, "workflow.json"), { concurrency: { default: 8, ceiling: 8 }, }); await assert.rejects( loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false, invocationConcurrency: 9, }), /between 1 and 8/, ); }); });