{"version":3,"file":"planner-BiH9msqq.mjs","names":[],"sources":["../src/workers/planner.ts"],"sourcesContent":["/**\n * planner.ts — the plan class: plan, spawn, integrate.\n *\n * `pai worker run --class plan -p \"<goal>\"` does not run one worker but a\n * small orchestration:\n *\n *   1. a planner worker (the plan class's provider) reads the repository and\n *      writes `<logDir>/plans/<planner id>.json` — 5–50 sub-tasks, fewer only\n *      when the goal itself names a smaller count, each with title, brief,\n *      class, files and acceptance;\n *   2. the runner validates the plan and spawns the sub-tasks as children of\n *      the planner worker (`parent` = the planner id, so `ps` shows the\n *      tree), at most `workers.tree.maxChildren` at a time;\n *   3. each child that finishes delivers its structured report to the\n *      planner's inbox as a `kind: \"result\"` handoff (see handoff.ts);\n *   4. the run finishes with a summary report and the branches to merge.\n *\n * The planner's prompt carries the prompt rules from the self-driving\n * codebases write-up: domain-specific instructions only, constraints over\n * step lists, explicit quantity ranges, no checkbox style.\n */\n\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readWorkersSection } from \"./config.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { ledgerPath, workersLogDir } from \"./paths.js\";\nimport { loadStatuses, newWorkerId, type WorkerStatus } from \"./status.js\";\nimport { parseRunnerArgs, shortText } from \"./args.js\";\nimport { swapPromptArg } from \"./chain.js\";\nimport { runWorker, printResult, type RunOptions, type StreamEvent } from \"./run.js\";\nimport type { WorkerReport } from \"./report.js\";\n\n/** Where a planner run's plan file lives: <logDir>/plans/<planner id>.json. */\nexport function planPathFor(logDir: string, plannerId: string): string {\n  return join(logDir, \"plans\", `${plannerId}.json`);\n}\n\nexport interface PlannerTask {\n  title: string;\n  brief: string;\n  /** Task class of the child (implement, research, …); default implement. */\n  class?: string;\n  /** Files the sub-task likely touches. */\n  files?: string[];\n  /** How to tell the sub-task is done. */\n  acceptance?: string[];\n}\n\nexport const MIN_TASKS = 5;\nexport const MAX_TASKS = 50;\n\n/**\n * Validate a raw plan file into its tasks. Accepts 1–MAX_TASKS tasks: the\n * prompt asks for MIN_TASKS–MAX_TASKS, but a goal may itself name a smaller\n * explicit count (\"one sub-task per file\" with three files), and that\n * explicit range wins. Everything else — missing file, damaged JSON, tasks\n * without a title or brief — throws with a message the operator can act on.\n */\nexport function validatePlan(raw: unknown): PlannerTask[] {\n  if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n    throw new Error(\"plan file must be a JSON object\");\n  }\n  const tasksRaw = (raw as Record<string, unknown>).tasks;\n  if (!Array.isArray(tasksRaw)) throw new Error('plan file needs a \"tasks\" array');\n  if (tasksRaw.length < 1) throw new Error(\"plan file has no tasks\");\n  if (tasksRaw.length > MAX_TASKS) {\n    throw new Error(`plan file has ${tasksRaw.length} tasks; the maximum is ${MAX_TASKS}`);\n  }\n  const tasks: PlannerTask[] = [];\n  for (let i = 0; i < tasksRaw.length; i++) {\n    const t = tasksRaw[i];\n    if (typeof t !== \"object\" || t === null || Array.isArray(t)) {\n      throw new Error(`task ${i + 1} must be an object`);\n    }\n    const o = t as Record<string, unknown>;\n    const title = typeof o.title === \"string\" ? o.title.trim() : \"\";\n    const brief = typeof o.brief === \"string\" ? o.brief.trim() : \"\";\n    if (!title) throw new Error(`task ${i + 1} needs a non-empty \"title\"`);\n    if (!brief) throw new Error(`task ${i + 1} needs a non-empty \"brief\"`);\n    const strArr = (v: unknown): string[] | undefined =>\n      Array.isArray(v) && v.every((x) => typeof x === \"string\") ? (v as string[]) : undefined;\n    const files = strArr(o.files);\n    const acceptance = strArr(o.acceptance);\n    tasks.push({\n      title,\n      brief,\n      ...(typeof o.class === \"string\" && o.class.trim() ? { class: o.class.trim() } : {}),\n      ...(files?.length ? { files } : {}),\n      ...(acceptance?.length ? { acceptance } : {}),\n    });\n  }\n  return tasks;\n}\n\n/** The planner worker's instructions, goal and prompt rules included. */\nexport function plannerPrompt(goal: string, planFile: string, maxChildren: number): string {\n  return [\n    \"You are a PLANNER worker. Turn the goal below into a plan file and stop —\",\n    \"you do not implement anything, and you do not spawn workers yourself:\",\n    \"the runner executes your plan on your behalf (the sub-tasks run as your\",\n    \"children and report back to you).\",\n    \"\",\n    `Write the plan with the Write tool to ${planFile} as one JSON object:`,\n    '{\"tasks\":[{\"title\":\"…\",\"brief\":\"…\",\"class\":\"implement\",\"files\":[\"…\"],\"acceptance\":[\"…\"]}]}',\n    \"\",\n    \"Prompt rules for the plan itself (they are yours too):\",\n    \"- Domain-specific instructions only: name real files, commands and\",\n    \"  constraints of this repository, never generic advice.\",\n    \"- Constraints over step lists: state what must hold (and what must not\",\n    \"  change), not a numbered procedure to follow.\",\n    \"- Explicit quantity ranges: how many, how much, how long — never\",\n    '  \"several\", \"some\", \"as needed\".',\n    \"- No checkbox style: no `[ ]` items, no step numbering theatre.\",\n    \"\",\n    `Emit between ${MIN_TASKS} and ${MAX_TASKS} sub-tasks — unless the goal itself`,\n    'names a smaller explicit count (\"one sub-task per file\" with three files',\n    \"means three); an explicit count in the goal always wins.\",\n    `At most ${maxChildren} of them run at a time, so independent tasks are better`,\n    \"than long chains. Each task's class picks its provider: implement, draft,\",\n    \"review, research, spotcheck, simple, complex, image.\",\n    \"\",\n    \"Read the repository first (Glob/Grep/Read) so tasks name real files.\",\n    \"Every sub-task runs in its own worktree of the same repository: name files\",\n    \"relative to the repository root and never an absolute path or a worktree\",\n    \"directory in a title, brief, files or acceptance entry.\",\n    \"\",\n    \"## Goal\",\n    \"\",\n    goal,\n  ].join(\"\\n\");\n}\n\n/** The child's prompt for one planned sub-task. */\nexport function taskPrompt(goal: string, task: PlannerTask, index: number, of: number): string {\n  return [\n    `You are sub-task ${index + 1} of ${of} of a planned goal. Do exactly this sub-task;`,\n    \"the other sub-tasks are other workers' business.\",\n    \"\",\n    `# ${task.title}`,\n    \"\",\n    task.brief,\n    ...(task.files?.length ? [\"\", \"Files likely touched: \" + task.files.join(\", \")] : []),\n    ...(task.acceptance?.length\n      ? [\"\", \"Done means:\", ...task.acceptance.map((a) => `- ${a}`)]\n      : []),\n    \"\",\n    \"## The overall goal (context only — your scope is the sub-task above)\",\n    \"\",\n    goal,\n  ].join(\"\\n\");\n}\n\nexport interface PlannerDeps {\n  /** Stage runner; tests inject a mock, production uses runWorker. */\n  runStage?: (opts: RunOptions) => Promise<number>;\n  /** logDir override for tests. */\n  logDir?: string;\n  /** maxChildren override for tests. */\n  maxChildren?: number;\n}\n\n/**\n * The planner orchestration; returns the process exit code. The planner id is\n * minted here and preset on the phase-1 run, so the plan file's name is known\n * before the worker starts and the prompt can name its exact path.\n */\nexport async function runPlanner(opts: RunOptions, deps: PlannerDeps = {}): Promise<number> {\n  const runStage = deps.runStage ?? runWorker;\n  const { workers: config } = readWorkersSection();\n  const logDir = deps.logDir ?? workersLogDir(config);\n  const maxChildren = deps.maxChildren ?? config.tree.maxChildren;\n  const parsed = parseRunnerArgs(opts.claudeArgs);\n  const goal = parsed.prompt ?? \"\";\n  const plannerId = newWorkerId();\n  const planFile = planPathFor(logDir, plannerId);\n  mkdirSync(join(logDir, \"plans\"), { recursive: true });\n\n  // --- phase 1: the planner worker writes the plan file\n  process.stderr.write(`planner ${plannerId}: writing plan (${planFile})\\n`);\n  const rc1 = await runStage({\n    ...opts,\n    id: plannerId,\n    claudeArgs: swapPromptArg(opts.claudeArgs, plannerPrompt(goal, planFile, maxChildren)),\n    quiet: true,\n    _planner: true,\n  });\n  if (rc1 !== 0) return rc1;\n  if (!existsSync(planFile)) {\n    process.stderr.write(\n      `planner ${plannerId}: no plan file at ${planFile} — the planner worker did not write one. ` +\n        `Re-run, or write the plan yourself and run the tasks with pai worker run.\\n`\n    );\n    appendLedger(ledgerPath(logDir), \"WORKER-PLAN-END\", { planner: plannerId, rc: 1, failed: \"plan\" });\n    return 1;\n  }\n  let tasks: PlannerTask[];\n  try {\n    tasks = validatePlan(JSON.parse(readFileSync(planFile, \"utf8\")));\n  } catch (e) {\n    process.stderr.write(`planner ${plannerId}: invalid plan file ${planFile}: ${(e as Error).message}\\n`);\n    appendLedger(ledgerPath(logDir), \"WORKER-PLAN-END\", { planner: plannerId, rc: 1, failed: \"plan\" });\n    return 1;\n  }\n  appendLedger(ledgerPath(logDir), \"WORKER-PLAN\", {\n    planner: plannerId,\n    tasks: tasks.length,\n    plan: planFile,\n  });\n\n  // --- phase 2: run the sub-tasks as children, maxChildren at a time\n  const results: { task: PlannerTask; rc: number }[] = [];\n  for (let i = 0; i < tasks.length; i += maxChildren) {\n    const wave = tasks.slice(i, i + maxChildren);\n    process.stderr.write(\n      `planner ${plannerId}: sub-tasks ${i + 1}–${i + wave.length} of ${tasks.length}\\n`\n    );\n    const rcs = await Promise.all(\n      wave.map((task, w) =>\n        runStage({\n          className: task.class ?? \"implement\",\n          providerFlag: opts.providerFlag,\n          modelFlag: opts.modelFlag,\n          label: shortText(task.title, 40),\n          noPane: opts.noPane,\n          mcpFlag: opts.mcpFlag,\n          claudeArgs: swapPromptArg(opts.claudeArgs, taskPrompt(goal, task, i + w, tasks.length)),\n          cwd: opts.cwd,\n          worktreeFlag: opts.worktreeFlag,\n          parent: plannerId,\n          quiet: true,\n        })\n      )\n    );\n    for (let w = 0; w < wave.length; w++) results.push({ task: wave[w], rc: rcs[w] });\n  }\n\n  // --- phase 3: summary from the children's statuses and inbox handoffs\n  const failed = results.filter((r) => r.rc !== 0).length;\n  appendLedger(ledgerPath(logDir), \"WORKER-PLAN-END\", {\n    planner: plannerId,\n    rc: failed ? 1 : 0,\n    failed,\n  });\n  printPlannerSummary(logDir, plannerId, results, parsed.outputFormat, opts.quiet === true);\n  return failed ? 1 : 0;\n}\n\n/** Compose and print the planner's summary report (text or json). */\nfunction printPlannerSummary(\n  logDir: string,\n  plannerId: string,\n  results: { task: PlannerTask; rc: number }[],\n  fmt: \"text\" | \"json\" | \"stream-json\",\n  quiet: boolean\n): void {\n  const children = statusesOfChildren(logDir, plannerId);\n  const ok = results.filter((r) => r.rc === 0).length;\n  const unmerged = children.filter((c) => c.branch && !c.merged);\n  const report: WorkerReport = {\n    checks: results.map((r, i) => ({\n      name: r.task.title,\n      ok: r.rc === 0,\n      detail: r.rc === 0 ? shortText(children[i]?.last, 80) : `rc=${r.rc}`,\n    })),\n    ...(unmerged.length\n      ? { open: unmerged.map((c) => `branch to merge: pai worker merge ${c.id} (${c.branch})`) }\n      : {}),\n    notes: `${ok}/${results.length} sub-tasks ok${unmerged.length ? `; ${unmerged.length} branch(es) to merge` : \"\"}`,\n  };\n  if (quiet || fmt === \"stream-json\") return;\n  const resultEvent: StreamEvent = {\n    type: \"result\",\n    result: [\n      `planner ${plannerId}: ${report.notes}`,\n      ...(unmerged.length ? [\"branches to merge:\"] : []),\n      ...unmerged.map((c) => `  pai worker merge ${c.id}   # ${c.branch}`),\n    ].join(\"\\n\"),\n    is_error: ok !== results.length,\n  };\n  printResult(fmt, resultEvent, ok === results.length ? 0 : 1, logDir, plannerId, report, {\n    plan: results.length,\n    ...(unmerged.length ? { branches: unmerged.map((c) => c.branch!) } : {}),\n  });\n}\n\n/** Children of the planner, oldest first, from the status files. */\nfunction statusesOfChildren(logDir: string, plannerId: string): WorkerStatus[] {\n  return loadStatuses(logDir).filter((s) => s.parent === plannerId);\n}\n"],"mappings":"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"}