@ordewell/core 0.10.11 → 0.11.0

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 (50) hide show
  1. package/AUTHORS.md +37 -0
  2. package/NOTICE +71 -0
  3. package/README.md +1 -1
  4. package/dist/{IFileSystem-1PqRCjhu.d.mts → IRunner-B5l9PCTv.d.ts} +926 -102
  5. package/dist/{IFileSystem-lTRL_G9Z.d.ts → IRunner-DBgDx_Mr.d.mts} +926 -102
  6. package/dist/{ModeResolver-CoJAJD-f.d.ts → ModeResolver-BDAgouuE.d.mts} +9 -2
  7. package/dist/{ModeResolver-6TQXVsde.d.mts → ModeResolver-DyXeZ9ib.d.ts} +9 -2
  8. package/dist/{Task-CfBM0wkV.d.mts → Task-BL4Idm9r.d.mts} +656 -26
  9. package/dist/{Task-CfBM0wkV.d.ts → Task-BL4Idm9r.d.ts} +656 -26
  10. package/dist/{chunk-ESKK5I4F.mjs → chunk-4QXOWEGC.mjs} +1888 -334
  11. package/dist/chunk-4QXOWEGC.mjs.map +1 -0
  12. package/dist/{chunk-KWS3VEER.mjs → chunk-MYXMIVWA.mjs} +6 -6
  13. package/dist/chunk-MYXMIVWA.mjs.map +1 -0
  14. package/dist/{chunk-PFH7WFIB.mjs → chunk-QTZ5XTBY.mjs} +228 -15
  15. package/dist/chunk-QTZ5XTBY.mjs.map +1 -0
  16. package/dist/{chunk-24EL4Z2W.mjs → chunk-YAFNYNFX.mjs} +342 -36
  17. package/dist/chunk-YAFNYNFX.mjs.map +1 -0
  18. package/dist/index.d.mts +1490 -769
  19. package/dist/index.d.ts +1490 -769
  20. package/dist/index.js +32009 -20354
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +27785 -17956
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/parsing.d.mts +2 -2
  25. package/dist/parsing.d.ts +2 -2
  26. package/dist/parsing.js +232 -25
  27. package/dist/parsing.js.map +1 -1
  28. package/dist/parsing.mjs +3 -3
  29. package/dist/plan-utils.d.mts +4 -4
  30. package/dist/plan-utils.d.ts +4 -4
  31. package/dist/plan-utils.js +1156 -96
  32. package/dist/plan-utils.js.map +1 -1
  33. package/dist/plan-utils.mjs +144 -8
  34. package/dist/{runSettingsText-Jb_efnI4.d.mts → runSettingsText-BKwWs052.d.mts} +2882 -621
  35. package/dist/{runSettingsText-C5uv2axI.d.ts → runSettingsText-CAXiTpsj.d.ts} +2882 -621
  36. package/dist/testing.d.mts +10 -4
  37. package/dist/testing.d.ts +10 -4
  38. package/dist/testing.js +30 -7
  39. package/dist/testing.js.map +1 -1
  40. package/dist/testing.mjs +26 -7
  41. package/dist/testing.mjs.map +1 -1
  42. package/package.json +7 -3
  43. package/skills/diagnose/SKILL.md +52 -0
  44. package/skills/explore/SKILL.md +65 -0
  45. package/dist/chunk-24EL4Z2W.mjs.map +0 -1
  46. package/dist/chunk-ESKK5I4F.mjs.map +0 -1
  47. package/dist/chunk-KWS3VEER.mjs.map +0 -1
  48. package/dist/chunk-NJGID6BG.mjs +0 -115
  49. package/dist/chunk-NJGID6BG.mjs.map +0 -1
  50. package/dist/chunk-PFH7WFIB.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/testing.ts"],"sourcesContent":["import type { IConfig } from './interfaces/IConfig';\nimport type { IFileSystem, ToolOutcome } from './interfaces/IFileSystem';\nimport type { InvokedCommand, IRunnerSession, QueuedTaskMessage, StructuredEvent, StructuredTurnEnd, TaskMessageFrom } from './interfaces/IRunner';\nimport type { RunnerCommand } from './models/RunnerCommand';\nimport type { RunnerSessionCost } from './models/Usage';\nimport type { ApprovalDecision } from './interfaces/IApproval';\nimport type { CheckpointAnswer, TaskCompleteArgs } from './services/mcp/tools';\nimport type {\n IsolationAvailability,\n IsolationHandoff,\n IsolationMergeResult,\n IsolationOutcome,\n IsolationPruneResult,\n IsolationRemoval,\n IsolationRun,\n IntegrationDisposal,\n PreparedTask,\n IWorktreeIsolation,\n RepairEvidence,\n SetupFailure,\n TreeSnapshot,\n} from './interfaces/IWorktreeIsolation';\nimport { DEFAULT_RUNNERS, type Task } from './models/Task';\nimport { handoffOf, integrationBranchFor, noRemoval, SELF_REPO } from './services/isolationRecord';\n\n/**\n * Let an already-queued promise chain run to its next await before asserting,\n * without a wall-clock sleep. For a chain whose length is not known, drain the\n * microtask queue repeatedly. Only helps a chain that does not wait on a timer.\n */\nexport async function flushMicrotasks(rounds = 20): Promise<void> {\n for (let i = 0; i < rounds; i += 1) await Promise.resolve();\n}\n\nexport function fakeConfig(overrides: Partial<IConfig> = {}): IConfig {\n return {\n aiProvider: 'openrouter',\n apiKey: 'sk-test',\n planningModel: 'test-model',\n enabledRunners: [...DEFAULT_RUNNERS],\n maxParallelSessions: 3,\n researchEnabled: false,\n researchMaxSteps: 10,\n researchMaxFileSize: 100_000,\n openAiBaseUrl: 'https://api.openai.com/v1',\n openAiApiKey: '',\n openrouterKey: '',\n geminiKey: '',\n openaiCompatibleBaseUrl: '',\n openaiCompatibleApiKey: '',\n orchestratorModel: '',\n researchSubagentModel: '',\n geminiModel: '',\n planMapEnabled: true,\n autonomousMode: true,\n // Off so an orchestrator built without an injected isolation never runs\n // real git against whatever repository the tests happen to run in.\n worktreeIsolation: false,\n workspaceRepos: [],\n worktreeLinks: [],\n // Off so a scripted conflict stays a conflict unless a test opts into repairing it.\n conflictRepairAttempts: 0,\n approvalMode: 'ask',\n approvalPreApproved: [],\n // Off so a test that is not about the allowlist sees every prompt it raises.\n approvalDefaults: false,\n webReadBudget: 40,\n setProviderModelLists: () => {},\n getProviderBaseUrl: () => '',\n // Every vendor holds a key, so no planner turn is refused for want of one\n // unless a test takes it away.\n getProviderApiKey: () => 'sk-test',\n ...overrides,\n };\n}\n\nconst EMPTY_OUTCOME: ToolOutcome = { success: false, output: '', truncated: false };\n\n/**\n * A complete {@link IFileSystem} stub. Lives here rather than in each package's\n * test folder so adding a tool to the interface is one edit, not one per suite.\n */\nexport function fakeFileSystem(overrides: Partial<IFileSystem> = {}): IFileSystem {\n return {\n readFile: async () => EMPTY_OUTCOME,\n readFiles: async () => EMPTY_OUTCOME,\n glob: async () => EMPTY_OUTCOME,\n grep: async () => EMPTY_OUTCOME,\n findSymbol: async () => EMPTY_OUTCOME,\n listDir: async () => EMPTY_OUTCOME,\n bash: async () => EMPTY_OUTCOME,\n getWorkspaceRoot: () => '/workspace',\n ...overrides,\n };\n}\n\nexport class FakeRunnerSession implements IRunnerSession {\n private outputCbs: Array<(text: string) => void> = [];\n private exitCbs: Array<(code: number) => void> = [];\n output = '';\n written: string[] = [];\n killed = false;\n\n state: 'working' | 'idle' = 'working';\n messages: QueuedTaskMessage[] = [];\n /** Every message a turn was started with, in order. */\n delivered: string[] = [];\n /** The ones of {@link delivered} Ordewell sent: the completion reminder (ADR-0018, W1). */\n fromOrdewell: string[] = [];\n interrupts = 0;\n private messageCount = 0;\n private turnEndCbs: Array<(reason: StructuredTurnEnd) => void> = [];\n private eventCbs: Array<(event: StructuredEvent) => void> = [];\n private completeCbs: Array<(report: TaskCompleteArgs) => void> = [];\n private checkpointHandler: ((question: string, signal: AbortSignal) => Promise<CheckpointAnswer>) | null = null;\n\n constructor(public id = 's1', public taskId = 't1', public sessionId: string | null = 'native-1') {}\n\n /** `stop()` was asked for: the runner winds its turn down, still reporting, until {@link emitExit}. */\n stopped = false;\n private markGone!: () => void;\n private readonly gone = new Promise<void>((resolve) => { this.markGone = resolve; });\n\n onOutput(cb: (text: string) => void): void { this.outputCbs.push(cb); }\n onExit(cb: (code: number) => void): void { this.exitCbs.push(cb); }\n kill(): void { this.killed = true; }\n stop(): Promise<void> {\n this.stopped = true;\n return this.gone;\n }\n getOutput(): string { return this.output; }\n\n emitOutput(text: string): void {\n this.output += text;\n for (const cb of this.outputCbs) cb(text);\n }\n emitExit(code: number): void {\n for (const cb of this.exitCbs) cb(code);\n this.markGone();\n }\n write(text: string): void {\n this.written.push(text);\n const message = text.trim();\n if (message) this.sendMessage(message);\n }\n\n turnState(): 'working' | 'idle' { return this.state; }\n onTurnEnd(cb: (reason: StructuredTurnEnd) => void): void { this.turnEndCbs.push(cb); }\n onEvent(cb: (event: StructuredEvent) => void): void { this.eventCbs.push(cb); }\n onTaskComplete(cb: (report: TaskCompleteArgs) => void): void { this.completeCbs.push(cb); }\n /**\n * The runner calls `task_complete`, as `StructuredSession` relays it. Here it\n * is published at once; a test that needs the real session's turn-end\n * ordering drives `StructuredSession` itself.\n */\n reportComplete(report: TaskCompleteArgs): void {\n for (const cb of this.completeCbs) cb(report);\n }\n onToolCheckpoint(handler: (question: string, signal: AbortSignal) => Promise<CheckpointAnswer>): void { this.checkpointHandler = handler; }\n /** The runner calls `checkpoint`, as `StructuredSession` relays it; settles with the answer. */\n callCheckpoint(question: string, signal: AbortSignal = new AbortController().signal): Promise<CheckpointAnswer> {\n if (!this.checkpointHandler) throw new Error('no checkpoint handler attached');\n return this.checkpointHandler(question, signal);\n }\n sendMessage(text: string, from?: TaskMessageFrom): string {\n this.messageCount += 1;\n const id = `msg-${this.messageCount}`;\n if (this.state === 'idle') this.deliver(text, from);\n else this.messages.push({ id, text, ...(from ? { from } : {}) });\n return id;\n }\n /** Interrupts, and the turn that replaces the interrupted one opens with the forced message. */\n forceSend(text: string): string {\n if (this.state === 'idle') return this.sendMessage(text);\n this.messageCount += 1;\n const id = `msg-${this.messageCount}`;\n this.putForced({ id, text, forced: true });\n void this.interrupt();\n return id;\n }\n forceSendQueued(id: string): boolean {\n const message = this.messages.find((m) => m.id === id);\n if (!message) return false;\n if (message.forced) return true;\n this.messages = this.messages.filter((m) => m !== message);\n this.putForced({ ...message, forced: true });\n if (this.state === 'working') void this.interrupt();\n return true;\n }\n private putForced(message: QueuedTaskMessage): void {\n const at = this.messages.findIndex((m) => !m.forced);\n this.messages.splice(at < 0 ? this.messages.length : at, 0, message);\n }\n removeQueued(id: string): boolean {\n const before = this.messages.length;\n this.messages = this.messages.filter((m) => m.id !== id);\n return this.messages.length < before;\n }\n queued(): QueuedTaskMessage[] { return this.messages.map((m) => ({ ...m })); }\n async interrupt(): Promise<void> {\n this.interrupts += 1;\n if (this.state === 'working') this.emitTurnEnd('interrupted');\n }\n nativeSessionId(): string | null { return this.sessionId; }\n /** What `sessionCost()` reports: the runner's running cost as counted, null while unknown. */\n cost: RunnerSessionCost | null = null;\n sessionCost(): RunnerSessionCost | null { return this.cost; }\n\n /** What `commands()` reports: the runner's own commands (#66). */\n runnerCommands: RunnerCommand[] = [];\n /** Every runner command invoked, in order. */\n invoked: InvokedCommand[] = [];\n commands(): RunnerCommand[] { return [...this.runnerCommands]; }\n /** Runs it as a message that starts with it, which is what a runner reads. */\n invokeCommand(name: string, args = ''): string {\n this.invoked.push({ name, ...(args.trim() ? { args: args.trim() } : {}) });\n return this.sendMessage(args.trim() ? `/${name} ${args.trim()}` : `/${name}`);\n }\n\n /** Every answer this session was given, in order. */\n answers: Array<{ id: string; decision: ApprovalDecision }> = [];\n private readonly openPermissions = new Set<string>();\n\n /** The runner asks to use a tool, as `StructuredSession` announces it. */\n requestPermission(id: string, name: string, input: Record<string, unknown>, suggestions: unknown[] = []): void {\n this.openPermissions.add(id);\n this.emitEvent({ type: 'permission_request', id, name, detail: JSON.stringify(input), input, suggestions });\n }\n /** The runner gives up on a request, as an interrupt makes it. */\n withdrawPermission(id: string): void {\n if (this.openPermissions.delete(id)) this.emitEvent({ type: 'permission_withdrawn', id });\n }\n answerPermission(id: string, decision: ApprovalDecision): boolean {\n if (!this.openPermissions.delete(id)) return false;\n this.answers.push({ id, decision });\n this.emitEvent({ type: 'permission_decided', id, decision });\n return true;\n }\n\n emitEvent(event: StructuredEvent): void {\n for (const cb of this.eventCbs) cb(event);\n }\n /** The runner reads a queued message inside the running turn, as `StructuredSession` reports it (ADR-0023). */\n deliverMidTurn(id: string): void {\n const message = this.messages.find((m) => m.id === id);\n if (!message) return;\n this.messages = this.messages.filter((m) => m !== message);\n this.emitEvent({ type: 'message_delivered', messageId: id, text: message.text, ...(message.from ? { from: message.from } : {}) });\n }\n /**\n * The turn ends without a `task_complete` call, and so does the turn the\n * completion reminder opens if one was sent (ADR-0018, W1): the task waits for input.\n */\n waitForInput(): void {\n const reminders = this.fromOrdewell.length;\n this.emitTurnEnd('completed');\n if (this.fromOrdewell.length > reminders) this.emitTurnEnd('completed');\n }\n emitTurnEnd(reason: StructuredTurnEnd): void {\n const next = this.messages.shift();\n if (!next) this.state = 'idle';\n for (const cb of this.turnEndCbs) cb(reason);\n if (next) this.deliver(next.text, next.from);\n }\n\n private deliver(text: string, from?: TaskMessageFrom): void {\n this.state = 'working';\n this.delivered.push(text);\n if (from === 'ordewell') this.fromOrdewell.push(text);\n this.emitEvent({ type: 'turn_start', text, ...(from ? { from } : {}) });\n }\n}\n\nexport type FakeIsolationCall =\n | { op: 'isActive'; workspaceRoot: string }\n | { op: 'stash'; workspaceRoot: string }\n | { op: 'startRun'; workspaceRoot: string }\n | { op: 'prepare' | 'reopen' | 'verifyRepair'; taskId: string }\n | { op: 'integrate'; taskId: string }\n | { op: 'release'; taskId: string; keep: boolean }\n | { op: 'handoff' | 'pruneOrphans' | 'reviewDiff' | 'mergeIntoCheckedOut' | 'sweep' | 'findInHead' | 'snapshotTree' | 'changedSince' }\n | { op: 'discard'; integration: IntegrationDisposal };\n\n/**\n * An in-memory {@link IWorktreeIsolation} for scheduling tests: no git, no\n * filesystem. Every call is logged in `calls`; `prepare` hands back a\n * deterministic fake cwd. Set `availability` to exercise the fallbacks, `outcomes`\n * to script a conflict, and `holdIntegration` to keep a task un-integrated so a\n * test can observe that its dependents wait. `repos` makes the run a group of\n * several; `changes` and `stopsIn` say which of them a task changes and where\n * its landing stops. `repairEvidence` scripts what a conflict repair's\n * evidence check finds, and `removal` what a removal kept back.\n */\nexport class FakeWorktreeIsolation implements IWorktreeIsolation {\n availability: IsolationAvailability = { active: true };\n /** The repo paths `startRun` groups: a group of one at `.` unless set. */\n repos: string[] = [SELF_REPO];\n /** Per task id, the repos it changes; every repo of the group when not listed. */\n changes = new Map<string, string[]>();\n /** Per task id, the repo its landing stops in when its outcome is not `merged`; its first changed repo when not listed. */\n stopsIn = new Map<string, string>();\n /** Per task id, the files a `conflict` outcome names; empty when not listed. */\n conflictFiles = new Map<string, string[]>();\n /** What `mergeIntoCheckedOut` answers; a `merged` one also puts every landed task in HEAD, as git would. */\n mergeResult: IsolationMergeResult = { outcome: 'merged' };\n /** Task ids whose landed work `findInHead` finds in HEAD besides those a merge put there, as a merge by hand would. */\n mergedByHand = new Set<string>();\n /** What `snapshotTree` answers; null as for a workspace with no repo. */\n treeSnapshot: TreeSnapshot | null = {};\n /** What `changedSince` answers: the tracked files an ops task changed. */\n changedFiles: string[] = [];\n /** What `startRun` shares and `prepare` copies, to exercise their notices. */\n shared: string[] = [];\n sharedRepos: string[] = [];\n copied: string[] = [];\n setupFailed: SetupFailure[] = [];\n /** Set to make `startRun` throw, as git does when no repo of the group can be isolated. */\n startRunError: Error | null = null;\n /** Set to make `discard` / `sweep` throw after logging the call. */\n discardError: Error | null = null;\n sweepError: Error | null = null;\n /** What `pruneOrphans` reports as kept back from the sweep. */\n keptOnPrune: IsolationPruneResult['kept'] = [];\n /**\n * What every removal — a `release` that removes, `discard`, `pruneOrphans` —\n * reports it kept back. A refused task keeps its record, as git's does.\n */\n removal: IsolationRemoval = noRemoval();\n /** Per task id; a task not listed integrates as `merged`. */\n outcomes = new Map<string, IsolationOutcome>();\n /** Per task id, what `verifyRepair` finds; a task not listed passes. */\n repairEvidence = new Map<string, RepairEvidence>();\n calls: FakeIsolationCall[] = [];\n private holds = new Map<string, Promise<void>>();\n private verifyHolds = new Map<string, Promise<void>>();\n private runCount = 0;\n\n private log(call: FakeIsolationCall): void { this.calls.push(call); }\n\n /** Task ids in the order `op` was called for them. */\n taskIdsFor(op: 'prepare' | 'reopen' | 'verifyRepair' | 'integrate' | 'release'): string[] {\n return this.calls.flatMap((c) => (c.op === op && 'taskId' in c ? [c.taskId] : []));\n }\n\n /** Make `integrate` for this task wait until the returned function is called. */\n holdIntegration(taskId: string): () => void {\n let open!: () => void;\n this.holds.set(taskId, new Promise<void>((resolve) => { open = resolve; }));\n return open;\n }\n\n /** Make `verifyRepair` for this task wait until the returned function is called. */\n holdVerification(taskId: string): () => void {\n let open!: () => void;\n this.verifyHolds.set(taskId, new Promise<void>((resolve) => { open = resolve; }));\n return open;\n }\n\n async isActive(workspaceRoot: string): Promise<IsolationAvailability> {\n this.log({ op: 'isActive', workspaceRoot });\n return this.availability;\n }\n\n /** Like git: once the tracked changes are stashed, the tree is no longer dirty. */\n async stash(workspaceRoot: string): Promise<void> {\n this.log({ op: 'stash', workspaceRoot });\n if (!this.availability.active && this.availability.reason === 'dirty') this.availability = { active: true };\n }\n\n async startRun(workspaceRoot: string): Promise<IsolationRun> {\n this.log({ op: 'startRun', workspaceRoot });\n if (this.startRunError) throw this.startRunError;\n const id = `run${++this.runCount}`;\n return {\n id,\n workspaceRoot,\n repos: this.repos.map((repo) => ({\n path: repo, root: `${workspaceRoot}/${repo}`.replace(/\\/\\.$/, ''), baseRef: 'base0000', baseBranch: 'main', integrationBranch: integrationBranchFor(id),\n })),\n shared: [...this.shared],\n sharedRepos: [...this.sharedRepos],\n tasks: {},\n };\n }\n\n async prepare(task: Task, run: IsolationRun): Promise<PreparedTask> {\n this.log({ op: 'prepare', taskId: task.id });\n const name = `${task.order}-${task.id}`;\n const cwd = `/fake-worktrees/${run.id}/${name}`;\n const branch = `ordewell/${run.id}/${name}`;\n run.tasks[task.id] = {\n taskId: task.id, order: task.order, title: task.title, branch, workspace: cwd, status: 'active',\n repos: Object.fromEntries(run.repos.map((r) => [r.path, { worktree: r.path === SELF_REPO ? cwd : `${cwd}/${r.path}`, linked: [] }])),\n };\n return { cwd, branch, copied: [...this.copied], ...(this.setupFailed.length > 0 ? { setupFailed: [...this.setupFailed] } : {}) };\n }\n\n /** Like git: the same cwd, each changed repo's tip recorded, the repair counted. */\n async reopen(task: Task, run: IsolationRun): Promise<PreparedTask> {\n this.log({ op: 'reopen', taskId: task.id });\n const record = run.tasks[task.id];\n if (record?.status !== 'conflict') throw new Error(`Task ${task.order} has no conflict to repair`);\n const changed = Object.entries(record.repos).filter(([repo, entry]) => entry.changed || repo === record.conflictRepo).map(([repo]) => repo);\n const files = (record.conflictFiles ?? []).map((file) => (record.conflictRepo && record.conflictRepo !== SELF_REPO ? `${record.conflictRepo}/${file}` : file));\n record.repairs = (record.repairs ?? 0) + 1;\n record.repairBase = Object.fromEntries(changed.map((repo) => [repo, `tip-${repo}`]));\n record.repairedFiles = [...new Set([...(record.repairedFiles ?? []), ...files])];\n record.status = 'repairing';\n return { cwd: record.workspace, branch: record.branch, copied: [] };\n }\n\n async verifyRepair(task: Task, _run: IsolationRun): Promise<RepairEvidence> {\n this.log({ op: 'verifyRepair', taskId: task.id });\n await this.verifyHolds.get(task.id);\n return this.repairEvidence.get(task.id) ?? { ok: true };\n }\n\n /** Like git: the landing is recorded and persisted before the (held) merge, and cleared once it settles. */\n async integrate(task: Task, run: IsolationRun, persist: () => void = () => undefined): Promise<IsolationOutcome> {\n this.log({ op: 'integrate', taskId: task.id });\n const record = run.tasks[task.id];\n if (!record) return 'failed';\n const changed = this.changes.get(task.id) ?? run.repos.map((r) => r.path);\n for (const [repo, entry] of Object.entries(record.repos)) entry.changed = changed.includes(repo) || (entry.changed ?? false);\n if (changed.length > 0) {\n run.landing = { taskId: task.id, tips: Object.fromEntries(changed.map((repo) => [repo, `tip-${repo}`])) };\n persist();\n }\n await this.holds.get(task.id);\n delete run.landing;\n const outcome = this.outcomes.get(task.id) ?? 'merged';\n record.status = outcome;\n delete record.repairBase;\n if (outcome === 'merged') {\n delete record.conflictRepo;\n delete record.conflictFiles;\n } else {\n record.conflictRepo = this.stopsIn.get(task.id) ?? changed[0] ?? SELF_REPO;\n const files = this.conflictFiles.get(task.id);\n if (files) record.conflictFiles = files;\n else delete record.conflictFiles;\n }\n return outcome;\n }\n\n async release(run: IsolationRun, taskId: string, opts: { keep: boolean }): Promise<IsolationRemoval> {\n this.log({ op: 'release', taskId, keep: opts.keep });\n const record = run.tasks[taskId];\n const removal = opts.keep || !record ? noRemoval() : this.removed();\n const refused = removal.refused.some((r) => r.task?.taskId === taskId);\n if (!opts.keep && !refused) delete run.tasks[taskId];\n else if (record?.status === 'active') record.status = 'kept';\n else if (record?.status === 'repairing') {\n record.status = 'conflict';\n delete record.repairBase;\n }\n return removal;\n }\n\n private removed(): IsolationRemoval {\n return { preserved: [...this.removal.preserved], refused: [...this.removal.refused] };\n }\n\n async handoff(run: IsolationRun): Promise<IsolationHandoff> {\n this.log({ op: 'handoff' });\n return handoffOf(run);\n }\n\n async pruneOrphans(): Promise<IsolationPruneResult> {\n this.log({ op: 'pruneOrphans' });\n return { kept: [...this.keptOnPrune], ...this.removed() };\n }\n async reviewDiff(): Promise<string> { this.log({ op: 'reviewDiff' }); return ''; }\n async mergeIntoCheckedOut(run: IsolationRun): Promise<IsolationMergeResult> {\n this.log({ op: 'mergeIntoCheckedOut' });\n if (this.mergeResult.outcome === 'merged') {\n for (const record of Object.values(run.tasks)) if (record.status === 'merged') this.mergedByHand.add(record.taskId);\n }\n return this.mergeResult;\n }\n async findInHead(run: IsolationRun): Promise<string[]> {\n this.log({ op: 'findInHead' });\n const found: string[] = [];\n for (const record of Object.values(run.tasks)) {\n if (record.status !== 'merged' || record.inHead || !this.mergedByHand.has(record.taskId)) continue;\n record.inHead = true;\n found.push(record.taskId);\n }\n return found;\n }\n async snapshotTree(): Promise<TreeSnapshot | null> { this.log({ op: 'snapshotTree' }); return this.treeSnapshot; }\n async changedSince(): Promise<string[]> { this.log({ op: 'changedSince' }); return [...this.changedFiles]; }\n async discard(_run: IsolationRun, opts: { integration: IntegrationDisposal }): Promise<IsolationRemoval> {\n this.log({ op: 'discard', integration: opts.integration });\n if (this.discardError) throw this.discardError;\n return this.removed();\n }\n async sweep(): Promise<void> {\n this.log({ op: 'sweep' });\n if (this.sweepError) throw this.sweepError;\n }\n}\n"],"mappings":";;;;;;;;;;;AA8BA,eAAsB,gBAAgB,SAAS,IAAmB;AAChE,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK,EAAG,OAAM,QAAQ,QAAQ;AAC5D;AAEO,SAAS,WAAW,YAA8B,CAAC,GAAY;AACpE,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,eAAe;AAAA,IACf,gBAAgB,CAAC,GAAG,eAAe;AAAA,IACnC,qBAAqB;AAAA,IACrB,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,eAAe;AAAA,IACf,cAAc;AAAA,IACd,eAAe;AAAA,IACf,WAAW;AAAA,IACX,yBAAyB;AAAA,IACzB,wBAAwB;AAAA,IACxB,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,IACvB,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,gBAAgB;AAAA;AAAA;AAAA,IAGhB,mBAAmB;AAAA,IACnB,gBAAgB,CAAC;AAAA,IACjB,eAAe,CAAC;AAAA;AAAA,IAEhB,wBAAwB;AAAA,IACxB,cAAc;AAAA,IACd,qBAAqB,CAAC;AAAA;AAAA,IAEtB,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf,uBAAuB,MAAM;AAAA,IAAC;AAAA,IAC9B,oBAAoB,MAAM;AAAA;AAAA;AAAA,IAG1B,mBAAmB,MAAM;AAAA,IACzB,GAAG;AAAA,EACL;AACF;AAEA,IAAM,gBAA6B,EAAE,SAAS,OAAO,QAAQ,IAAI,WAAW,MAAM;AAM3E,SAAS,eAAe,YAAkC,CAAC,GAAgB;AAChF,SAAO;AAAA,IACL,UAAU,YAAY;AAAA,IACtB,WAAW,YAAY;AAAA,IACvB,MAAM,YAAY;AAAA,IAClB,MAAM,YAAY;AAAA,IAClB,YAAY,YAAY;AAAA,IACxB,SAAS,YAAY;AAAA,IACrB,MAAM,YAAY;AAAA,IAClB,kBAAkB,MAAM;AAAA,IACxB,GAAG;AAAA,EACL;AACF;AAEO,IAAM,oBAAN,MAAkD;AAAA,EAoBvD,YAAmB,KAAK,MAAa,SAAS,MAAa,YAA2B,YAAY;AAA/E;AAAkB;AAAsB;AAAA,EAAwC;AAAA,EAAhF;AAAA,EAAkB;AAAA,EAAsB;AAAA,EAnBnD,YAA2C,CAAC;AAAA,EAC5C,UAAyC,CAAC;AAAA,EAClD,SAAS;AAAA,EACT,UAAoB,CAAC;AAAA,EACrB,SAAS;AAAA,EAET,QAA4B;AAAA,EAC5B,WAAgC,CAAC;AAAA;AAAA,EAEjC,YAAsB,CAAC;AAAA;AAAA,EAEvB,eAAyB,CAAC;AAAA,EAC1B,aAAa;AAAA,EACL,eAAe;AAAA,EACf,aAAyD,CAAC;AAAA,EAC1D,WAAoD,CAAC;AAAA,EACrD,cAAyD,CAAC;AAAA,EAC1D,oBAAmG;AAAA;AAAA,EAK3G,UAAU;AAAA,EACF;AAAA,EACS,OAAO,IAAI,QAAc,CAAC,YAAY;AAAE,SAAK,WAAW;AAAA,EAAS,CAAC;AAAA,EAEnF,SAAS,IAAkC;AAAE,SAAK,UAAU,KAAK,EAAE;AAAA,EAAG;AAAA,EACtE,OAAO,IAAkC;AAAE,SAAK,QAAQ,KAAK,EAAE;AAAA,EAAG;AAAA,EAClE,OAAa;AAAE,SAAK,SAAS;AAAA,EAAM;AAAA,EACnC,OAAsB;AACpB,SAAK,UAAU;AACf,WAAO,KAAK;AAAA,EACd;AAAA,EACA,YAAoB;AAAE,WAAO,KAAK;AAAA,EAAQ;AAAA,EAE1C,WAAW,MAAoB;AAC7B,SAAK,UAAU;AACf,eAAW,MAAM,KAAK,UAAW,IAAG,IAAI;AAAA,EAC1C;AAAA,EACA,SAAS,MAAoB;AAC3B,eAAW,MAAM,KAAK,QAAS,IAAG,IAAI;AACtC,SAAK,SAAS;AAAA,EAChB;AAAA,EACA,MAAM,MAAoB;AACxB,SAAK,QAAQ,KAAK,IAAI;AACtB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,QAAS,MAAK,YAAY,OAAO;AAAA,EACvC;AAAA,EAEA,YAAgC;AAAE,WAAO,KAAK;AAAA,EAAO;AAAA,EACrD,UAAU,IAA+C;AAAE,SAAK,WAAW,KAAK,EAAE;AAAA,EAAG;AAAA,EACrF,QAAQ,IAA4C;AAAE,SAAK,SAAS,KAAK,EAAE;AAAA,EAAG;AAAA,EAC9E,eAAe,IAA8C;AAAE,SAAK,YAAY,KAAK,EAAE;AAAA,EAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM1F,eAAe,QAAgC;AAC7C,eAAW,MAAM,KAAK,YAAa,IAAG,MAAM;AAAA,EAC9C;AAAA,EACA,iBAAiB,SAAqF;AAAE,SAAK,oBAAoB;AAAA,EAAS;AAAA;AAAA,EAE1I,eAAe,UAAkB,SAAsB,IAAI,gBAAgB,EAAE,QAAmC;AAC9G,QAAI,CAAC,KAAK,kBAAmB,OAAM,IAAI,MAAM,gCAAgC;AAC7E,WAAO,KAAK,kBAAkB,UAAU,MAAM;AAAA,EAChD;AAAA,EACA,YAAY,MAAc,MAAgC;AACxD,SAAK,gBAAgB;AACrB,UAAM,KAAK,OAAO,KAAK,YAAY;AACnC,QAAI,KAAK,UAAU,OAAQ,MAAK,QAAQ,MAAM,IAAI;AAAA,QAC7C,MAAK,SAAS,KAAK,EAAE,IAAI,MAAM,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC,EAAG,CAAC;AAC/D,WAAO;AAAA,EACT;AAAA;AAAA,EAEA,UAAU,MAAsB;AAC9B,QAAI,KAAK,UAAU,OAAQ,QAAO,KAAK,YAAY,IAAI;AACvD,SAAK,gBAAgB;AACrB,UAAM,KAAK,OAAO,KAAK,YAAY;AACnC,SAAK,UAAU,EAAE,IAAI,MAAM,QAAQ,KAAK,CAAC;AACzC,SAAK,KAAK,UAAU;AACpB,WAAO;AAAA,EACT;AAAA,EACA,gBAAgB,IAAqB;AACnC,UAAM,UAAU,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACrD,QAAI,CAAC,QAAS,QAAO;AACrB,QAAI,QAAQ,OAAQ,QAAO;AAC3B,SAAK,WAAW,KAAK,SAAS,OAAO,CAAC,MAAM,MAAM,OAAO;AACzD,SAAK,UAAU,EAAE,GAAG,SAAS,QAAQ,KAAK,CAAC;AAC3C,QAAI,KAAK,UAAU,UAAW,MAAK,KAAK,UAAU;AAClD,WAAO;AAAA,EACT;AAAA,EACQ,UAAU,SAAkC;AAClD,UAAM,KAAK,KAAK,SAAS,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM;AACnD,SAAK,SAAS,OAAO,KAAK,IAAI,KAAK,SAAS,SAAS,IAAI,GAAG,OAAO;AAAA,EACrE;AAAA,EACA,aAAa,IAAqB;AAChC,UAAM,SAAS,KAAK,SAAS;AAC7B,SAAK,WAAW,KAAK,SAAS,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AACvD,WAAO,KAAK,SAAS,SAAS;AAAA,EAChC;AAAA,EACA,SAA8B;AAAE,WAAO,KAAK,SAAS,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE;AAAA,EAAG;AAAA,EAC7E,MAAM,YAA2B;AAC/B,SAAK,cAAc;AACnB,QAAI,KAAK,UAAU,UAAW,MAAK,YAAY,aAAa;AAAA,EAC9D;AAAA,EACA,kBAAiC;AAAE,WAAO,KAAK;AAAA,EAAW;AAAA;AAAA,EAE1D,OAAiC;AAAA,EACjC,cAAwC;AAAE,WAAO,KAAK;AAAA,EAAM;AAAA;AAAA,EAG5D,iBAAkC,CAAC;AAAA;AAAA,EAEnC,UAA4B,CAAC;AAAA,EAC7B,WAA4B;AAAE,WAAO,CAAC,GAAG,KAAK,cAAc;AAAA,EAAG;AAAA;AAAA,EAE/D,cAAc,MAAc,OAAO,IAAY;AAC7C,SAAK,QAAQ,KAAK,EAAE,MAAM,GAAI,KAAK,KAAK,IAAI,EAAE,MAAM,KAAK,KAAK,EAAE,IAAI,CAAC,EAAG,CAAC;AACzE,WAAO,KAAK,YAAY,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,KAAK,KAAK,CAAC,KAAK,IAAI,IAAI,EAAE;AAAA,EAC9E;AAAA;AAAA,EAGA,UAA6D,CAAC;AAAA,EAC7C,kBAAkB,oBAAI,IAAY;AAAA;AAAA,EAGnD,kBAAkB,IAAY,MAAc,OAAgC,cAAyB,CAAC,GAAS;AAC7G,SAAK,gBAAgB,IAAI,EAAE;AAC3B,SAAK,UAAU,EAAE,MAAM,sBAAsB,IAAI,MAAM,QAAQ,KAAK,UAAU,KAAK,GAAG,OAAO,YAAY,CAAC;AAAA,EAC5G;AAAA;AAAA,EAEA,mBAAmB,IAAkB;AACnC,QAAI,KAAK,gBAAgB,OAAO,EAAE,EAAG,MAAK,UAAU,EAAE,MAAM,wBAAwB,GAAG,CAAC;AAAA,EAC1F;AAAA,EACA,iBAAiB,IAAY,UAAqC;AAChE,QAAI,CAAC,KAAK,gBAAgB,OAAO,EAAE,EAAG,QAAO;AAC7C,SAAK,QAAQ,KAAK,EAAE,IAAI,SAAS,CAAC;AAClC,SAAK,UAAU,EAAE,MAAM,sBAAsB,IAAI,SAAS,CAAC;AAC3D,WAAO;AAAA,EACT;AAAA,EAEA,UAAU,OAA8B;AACtC,eAAW,MAAM,KAAK,SAAU,IAAG,KAAK;AAAA,EAC1C;AAAA;AAAA,EAEA,eAAe,IAAkB;AAC/B,UAAM,UAAU,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACrD,QAAI,CAAC,QAAS;AACd,SAAK,WAAW,KAAK,SAAS,OAAO,CAAC,MAAM,MAAM,OAAO;AACzD,SAAK,UAAU,EAAE,MAAM,qBAAqB,WAAW,IAAI,MAAM,QAAQ,MAAM,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC,EAAG,CAAC;AAAA,EAClI;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,UAAM,YAAY,KAAK,aAAa;AACpC,SAAK,YAAY,WAAW;AAC5B,QAAI,KAAK,aAAa,SAAS,UAAW,MAAK,YAAY,WAAW;AAAA,EACxE;AAAA,EACA,YAAY,QAAiC;AAC3C,UAAM,OAAO,KAAK,SAAS,MAAM;AACjC,QAAI,CAAC,KAAM,MAAK,QAAQ;AACxB,eAAW,MAAM,KAAK,WAAY,IAAG,MAAM;AAC3C,QAAI,KAAM,MAAK,QAAQ,KAAK,MAAM,KAAK,IAAI;AAAA,EAC7C;AAAA,EAEQ,QAAQ,MAAc,MAA8B;AAC1D,SAAK,QAAQ;AACb,SAAK,UAAU,KAAK,IAAI;AACxB,QAAI,SAAS,WAAY,MAAK,aAAa,KAAK,IAAI;AACpD,SAAK,UAAU,EAAE,MAAM,cAAc,MAAM,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC,EAAG,CAAC;AAAA,EACxE;AACF;AAsBO,IAAM,wBAAN,MAA0D;AAAA,EAC/D,eAAsC,EAAE,QAAQ,KAAK;AAAA;AAAA,EAErD,QAAkB,CAAC,SAAS;AAAA;AAAA,EAE5B,UAAU,oBAAI,IAAsB;AAAA;AAAA,EAEpC,UAAU,oBAAI,IAAoB;AAAA;AAAA,EAElC,gBAAgB,oBAAI,IAAsB;AAAA;AAAA,EAE1C,cAAoC,EAAE,SAAS,SAAS;AAAA;AAAA,EAExD,eAAe,oBAAI,IAAY;AAAA;AAAA,EAE/B,eAAoC,CAAC;AAAA;AAAA,EAErC,eAAyB,CAAC;AAAA;AAAA,EAE1B,SAAmB,CAAC;AAAA,EACpB,cAAwB,CAAC;AAAA,EACzB,SAAmB,CAAC;AAAA,EACpB,cAA8B,CAAC;AAAA;AAAA,EAE/B,gBAA8B;AAAA;AAAA,EAE9B,eAA6B;AAAA,EAC7B,aAA2B;AAAA;AAAA,EAE3B,cAA4C,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAK7C,UAA4B,UAAU;AAAA;AAAA,EAEtC,WAAW,oBAAI,IAA8B;AAAA;AAAA,EAE7C,iBAAiB,oBAAI,IAA4B;AAAA,EACjD,QAA6B,CAAC;AAAA,EACtB,QAAQ,oBAAI,IAA2B;AAAA,EACvC,cAAc,oBAAI,IAA2B;AAAA,EAC7C,WAAW;AAAA,EAEX,IAAI,MAA+B;AAAE,SAAK,MAAM,KAAK,IAAI;AAAA,EAAG;AAAA;AAAA,EAGpE,WAAW,IAA+E;AACxF,WAAO,KAAK,MAAM,QAAQ,CAAC,MAAO,EAAE,OAAO,MAAM,YAAY,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC,CAAE;AAAA,EACnF;AAAA;AAAA,EAGA,gBAAgB,QAA4B;AAC1C,QAAI;AACJ,SAAK,MAAM,IAAI,QAAQ,IAAI,QAAc,CAAC,YAAY;AAAE,aAAO;AAAA,IAAS,CAAC,CAAC;AAC1E,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,iBAAiB,QAA4B;AAC3C,QAAI;AACJ,SAAK,YAAY,IAAI,QAAQ,IAAI,QAAc,CAAC,YAAY;AAAE,aAAO;AAAA,IAAS,CAAC,CAAC;AAChF,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAS,eAAuD;AACpE,SAAK,IAAI,EAAE,IAAI,YAAY,cAAc,CAAC;AAC1C,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,MAAM,eAAsC;AAChD,SAAK,IAAI,EAAE,IAAI,SAAS,cAAc,CAAC;AACvC,QAAI,CAAC,KAAK,aAAa,UAAU,KAAK,aAAa,WAAW,QAAS,MAAK,eAAe,EAAE,QAAQ,KAAK;AAAA,EAC5G;AAAA,EAEA,MAAM,SAAS,eAA8C;AAC3D,SAAK,IAAI,EAAE,IAAI,YAAY,cAAc,CAAC;AAC1C,QAAI,KAAK,cAAe,OAAM,KAAK;AACnC,UAAM,KAAK,MAAM,EAAE,KAAK,QAAQ;AAChC,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,OAAO,KAAK,MAAM,IAAI,CAAC,UAAU;AAAA,QAC/B,MAAM;AAAA,QAAM,MAAM,GAAG,aAAa,IAAI,IAAI,GAAG,QAAQ,SAAS,EAAE;AAAA,QAAG,SAAS;AAAA,QAAY,YAAY;AAAA,QAAQ,mBAAmB,qBAAqB,EAAE;AAAA,MACxJ,EAAE;AAAA,MACF,QAAQ,CAAC,GAAG,KAAK,MAAM;AAAA,MACvB,aAAa,CAAC,GAAG,KAAK,WAAW;AAAA,MACjC,OAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,MAAY,KAA0C;AAClE,SAAK,IAAI,EAAE,IAAI,WAAW,QAAQ,KAAK,GAAG,CAAC;AAC3C,UAAM,OAAO,GAAG,KAAK,KAAK,IAAI,KAAK,EAAE;AACrC,UAAM,MAAM,mBAAmB,IAAI,EAAE,IAAI,IAAI;AAC7C,UAAM,SAAS,YAAY,IAAI,EAAE,IAAI,IAAI;AACzC,QAAI,MAAM,KAAK,EAAE,IAAI;AAAA,MACnB,QAAQ,KAAK;AAAA,MAAI,OAAO,KAAK;AAAA,MAAO,OAAO,KAAK;AAAA,MAAO;AAAA,MAAQ,WAAW;AAAA,MAAK,QAAQ;AAAA,MACvF,OAAO,OAAO,YAAY,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,YAAY,MAAM,GAAG,GAAG,IAAI,EAAE,IAAI,IAAI,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;AAAA,IACrI;AACA,WAAO,EAAE,KAAK,QAAQ,QAAQ,CAAC,GAAG,KAAK,MAAM,GAAG,GAAI,KAAK,YAAY,SAAS,IAAI,EAAE,aAAa,CAAC,GAAG,KAAK,WAAW,EAAE,IAAI,CAAC,EAAG;AAAA,EACjI;AAAA;AAAA,EAGA,MAAM,OAAO,MAAY,KAA0C;AACjE,SAAK,IAAI,EAAE,IAAI,UAAU,QAAQ,KAAK,GAAG,CAAC;AAC1C,UAAM,SAAS,IAAI,MAAM,KAAK,EAAE;AAChC,QAAI,QAAQ,WAAW,WAAY,OAAM,IAAI,MAAM,QAAQ,KAAK,KAAK,4BAA4B;AACjG,UAAM,UAAU,OAAO,QAAQ,OAAO,KAAK,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK,MAAM,MAAM,WAAW,SAAS,OAAO,YAAY,EAAE,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;AAC1I,UAAM,SAAS,OAAO,iBAAiB,CAAC,GAAG,IAAI,CAAC,SAAU,OAAO,gBAAgB,OAAO,iBAAiB,YAAY,GAAG,OAAO,YAAY,IAAI,IAAI,KAAK,IAAK;AAC7J,WAAO,WAAW,OAAO,WAAW,KAAK;AACzC,WAAO,aAAa,OAAO,YAAY,QAAQ,IAAI,CAAC,SAAS,CAAC,MAAM,OAAO,IAAI,EAAE,CAAC,CAAC;AACnF,WAAO,gBAAgB,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAI,OAAO,iBAAiB,CAAC,GAAI,GAAG,KAAK,CAAC,CAAC;AAC/E,WAAO,SAAS;AAChB,WAAO,EAAE,KAAK,OAAO,WAAW,QAAQ,OAAO,QAAQ,QAAQ,CAAC,EAAE;AAAA,EACpE;AAAA,EAEA,MAAM,aAAa,MAAY,MAA6C;AAC1E,SAAK,IAAI,EAAE,IAAI,gBAAgB,QAAQ,KAAK,GAAG,CAAC;AAChD,UAAM,KAAK,YAAY,IAAI,KAAK,EAAE;AAClC,WAAO,KAAK,eAAe,IAAI,KAAK,EAAE,KAAK,EAAE,IAAI,KAAK;AAAA,EACxD;AAAA;AAAA,EAGA,MAAM,UAAU,MAAY,KAAmB,UAAsB,MAAM,QAAsC;AAC/G,SAAK,IAAI,EAAE,IAAI,aAAa,QAAQ,KAAK,GAAG,CAAC;AAC7C,UAAM,SAAS,IAAI,MAAM,KAAK,EAAE;AAChC,QAAI,CAAC,OAAQ,QAAO;AACpB,UAAM,UAAU,KAAK,QAAQ,IAAI,KAAK,EAAE,KAAK,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI;AACxE,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,KAAK,EAAG,OAAM,UAAU,QAAQ,SAAS,IAAI,MAAM,MAAM,WAAW;AACtH,QAAI,QAAQ,SAAS,GAAG;AACtB,UAAI,UAAU,EAAE,QAAQ,KAAK,IAAI,MAAM,OAAO,YAAY,QAAQ,IAAI,CAAC,SAAS,CAAC,MAAM,OAAO,IAAI,EAAE,CAAC,CAAC,EAAE;AACxG,cAAQ;AAAA,IACV;AACA,UAAM,KAAK,MAAM,IAAI,KAAK,EAAE;AAC5B,WAAO,IAAI;AACX,UAAM,UAAU,KAAK,SAAS,IAAI,KAAK,EAAE,KAAK;AAC9C,WAAO,SAAS;AAChB,WAAO,OAAO;AACd,QAAI,YAAY,UAAU;AACxB,aAAO,OAAO;AACd,aAAO,OAAO;AAAA,IAChB,OAAO;AACL,aAAO,eAAe,KAAK,QAAQ,IAAI,KAAK,EAAE,KAAK,QAAQ,CAAC,KAAK;AACjE,YAAM,QAAQ,KAAK,cAAc,IAAI,KAAK,EAAE;AAC5C,UAAI,MAAO,QAAO,gBAAgB;AAAA,UAC7B,QAAO,OAAO;AAAA,IACrB;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAQ,KAAmB,QAAgB,MAAoD;AACnG,SAAK,IAAI,EAAE,IAAI,WAAW,QAAQ,MAAM,KAAK,KAAK,CAAC;AACnD,UAAM,SAAS,IAAI,MAAM,MAAM;AAC/B,UAAM,UAAU,KAAK,QAAQ,CAAC,SAAS,UAAU,IAAI,KAAK,QAAQ;AAClE,UAAM,UAAU,QAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,MAAM,WAAW,MAAM;AACrE,QAAI,CAAC,KAAK,QAAQ,CAAC,QAAS,QAAO,IAAI,MAAM,MAAM;AAAA,aAC1C,QAAQ,WAAW,SAAU,QAAO,SAAS;AAAA,aAC7C,QAAQ,WAAW,aAAa;AACvC,aAAO,SAAS;AAChB,aAAO,OAAO;AAAA,IAChB;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,UAA4B;AAClC,WAAO,EAAE,WAAW,CAAC,GAAG,KAAK,QAAQ,SAAS,GAAG,SAAS,CAAC,GAAG,KAAK,QAAQ,OAAO,EAAE;AAAA,EACtF;AAAA,EAEA,MAAM,QAAQ,KAA8C;AAC1D,SAAK,IAAI,EAAE,IAAI,UAAU,CAAC;AAC1B,WAAO,UAAU,GAAG;AAAA,EACtB;AAAA,EAEA,MAAM,eAA8C;AAClD,SAAK,IAAI,EAAE,IAAI,eAAe,CAAC;AAC/B,WAAO,EAAE,MAAM,CAAC,GAAG,KAAK,WAAW,GAAG,GAAG,KAAK,QAAQ,EAAE;AAAA,EAC1D;AAAA,EACA,MAAM,aAA8B;AAAE,SAAK,IAAI,EAAE,IAAI,aAAa,CAAC;AAAG,WAAO;AAAA,EAAI;AAAA,EACjF,MAAM,oBAAoB,KAAkD;AAC1E,SAAK,IAAI,EAAE,IAAI,sBAAsB,CAAC;AACtC,QAAI,KAAK,YAAY,YAAY,UAAU;AACzC,iBAAW,UAAU,OAAO,OAAO,IAAI,KAAK,EAAG,KAAI,OAAO,WAAW,SAAU,MAAK,aAAa,IAAI,OAAO,MAAM;AAAA,IACpH;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EACA,MAAM,WAAW,KAAsC;AACrD,SAAK,IAAI,EAAE,IAAI,aAAa,CAAC;AAC7B,UAAM,QAAkB,CAAC;AACzB,eAAW,UAAU,OAAO,OAAO,IAAI,KAAK,GAAG;AAC7C,UAAI,OAAO,WAAW,YAAY,OAAO,UAAU,CAAC,KAAK,aAAa,IAAI,OAAO,MAAM,EAAG;AAC1F,aAAO,SAAS;AAChB,YAAM,KAAK,OAAO,MAAM;AAAA,IAC1B;AACA,WAAO;AAAA,EACT;AAAA,EACA,MAAM,eAA6C;AAAE,SAAK,IAAI,EAAE,IAAI,eAAe,CAAC;AAAG,WAAO,KAAK;AAAA,EAAc;AAAA,EACjH,MAAM,eAAkC;AAAE,SAAK,IAAI,EAAE,IAAI,eAAe,CAAC;AAAG,WAAO,CAAC,GAAG,KAAK,YAAY;AAAA,EAAG;AAAA,EAC3G,MAAM,QAAQ,MAAoB,MAAuE;AACvG,SAAK,IAAI,EAAE,IAAI,WAAW,aAAa,KAAK,YAAY,CAAC;AACzD,QAAI,KAAK,aAAc,OAAM,KAAK;AAClC,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA,EACA,MAAM,QAAuB;AAC3B,SAAK,IAAI,EAAE,IAAI,QAAQ,CAAC;AACxB,QAAI,KAAK,WAAY,OAAM,KAAK;AAAA,EAClC;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/testing.ts"],"sourcesContent":["import type { IConfig } from './interfaces/IConfig';\nimport type { IFileSystem, ToolOutcome } from './interfaces/IFileSystem';\nimport type { InvokedCommand, IRunnerSession, QueuedTaskMessage, ScriptExit, StructuredEvent, StructuredTurnEnd, TaskMessageFrom } from './interfaces/IRunner';\nimport type { RunnerCommand } from './models/RunnerCommand';\nimport type { RunnerSessionCost } from './models/Usage';\nimport type { HandoffCommit } from './models/Handoff';\nimport type { ApprovalDecision } from './interfaces/IApproval';\nimport type { CheckpointAnswer, TaskCompleteArgs } from './services/mcp/tools';\nimport type {\n IsolationAvailability,\n IsolationHandoff,\n IsolationMergeResult,\n IsolationOutcome,\n IsolationPruneResult,\n IsolationRemoval,\n IsolationRun,\n IntegrationDisposal,\n PreparedTask,\n PrepareIntent,\n IWorktreeIsolation,\n RepairEvidence,\n SetupFailure,\n TreeSnapshot,\n} from './interfaces/IWorktreeIsolation';\nimport { DEFAULT_RUNNERS, type Task } from './models/Task';\nimport { handoffOf, integrationBranchFor, noRemoval, SELF_REPO } from './services/isolationRecord';\n\n/**\n * Let an already-queued promise chain run to its next await before asserting,\n * without a wall-clock sleep. For a chain whose length is not known, drain the\n * microtask queue repeatedly. Only helps a chain that does not wait on a timer.\n */\nexport async function flushMicrotasks(rounds = 20): Promise<void> {\n for (let i = 0; i < rounds; i += 1) await Promise.resolve();\n}\n\nexport function fakeConfig(overrides: Partial<IConfig> = {}): IConfig {\n return {\n aiProvider: 'openrouter',\n apiKey: 'sk-test',\n planningModel: 'test-model',\n enabledRunners: [...DEFAULT_RUNNERS],\n maxParallelSessions: 3,\n researchEnabled: false,\n researchMaxSteps: 10,\n researchMaxFileSize: 100_000,\n openAiBaseUrl: 'https://api.openai.com/v1',\n openAiApiKey: '',\n openrouterKey: '',\n geminiKey: '',\n openaiCompatibleBaseUrl: '',\n openaiCompatibleApiKey: '',\n orchestratorModel: '',\n researchSubagentModel: '',\n geminiModel: '',\n planMapEnabled: true,\n autonomousMode: true,\n // Off so an orchestrator built without an injected isolation never runs\n // real git against whatever repository the tests happen to run in.\n worktreeIsolation: false,\n workspaceRepos: [],\n worktreeLinks: [],\n // Off so a scripted conflict stays a conflict unless a test opts into repairing it.\n conflictRepairAttempts: 0,\n approvalMode: 'ask',\n approvalPreApproved: [],\n // Off so a test that is not about the allowlist sees every prompt it raises.\n approvalDefaults: false,\n effectAllowlist: [],\n webReadBudget: 40,\n plannerNativeShell: false,\n setProviderModelLists: () => {},\n getProviderBaseUrl: () => '',\n // Every vendor holds a key, so no planner turn is refused for want of one\n // unless a test takes it away.\n getProviderApiKey: () => 'sk-test',\n ...overrides,\n };\n}\n\nconst EMPTY_OUTCOME: ToolOutcome = { success: false, output: '', truncated: false };\n\n/**\n * A complete {@link IFileSystem} stub. Lives here rather than in each package's\n * test folder so adding a tool to the interface is one edit, not one per suite.\n */\nexport function fakeFileSystem(overrides: Partial<IFileSystem> = {}): IFileSystem {\n return {\n readFile: async () => EMPTY_OUTCOME,\n readFiles: async () => EMPTY_OUTCOME,\n glob: async () => EMPTY_OUTCOME,\n grep: async () => EMPTY_OUTCOME,\n findSymbol: async () => EMPTY_OUTCOME,\n listDir: async () => EMPTY_OUTCOME,\n bash: async () => EMPTY_OUTCOME,\n getWorkspaceRoot: () => '/workspace',\n ...overrides,\n };\n}\n\nexport class FakeRunnerSession implements IRunnerSession {\n private outputCbs: Array<(text: string) => void> = [];\n private exitCbs: Array<(code: number) => void> = [];\n output = '';\n written: string[] = [];\n killed = false;\n\n state: 'working' | 'idle' = 'working';\n messages: QueuedTaskMessage[] = [];\n /** Every message a turn was started with, in order. */\n delivered: string[] = [];\n /** The ones of {@link delivered} Ordewell sent: the completion reminder (ADR-0018, W1). */\n fromOrdewell: string[] = [];\n interrupts = 0;\n private messageCount = 0;\n private turnEndCbs: Array<(reason: StructuredTurnEnd) => void> = [];\n private eventCbs: Array<(event: StructuredEvent) => void> = [];\n private completeCbs: Array<(report: TaskCompleteArgs) => void> = [];\n private checkpointHandler: ((question: string, signal: AbortSignal) => Promise<CheckpointAnswer>) | null = null;\n\n constructor(public id = 's1', public taskId = 't1', public sessionId: string | null = 'native-1') {}\n\n /** `stop()` was asked for: the runner winds its turn down, still reporting, until {@link emitExit}. */\n stopped = false;\n private markGone!: () => void;\n private readonly gone = new Promise<void>((resolve) => { this.markGone = resolve; });\n\n onOutput(cb: (text: string) => void): void { this.outputCbs.push(cb); }\n onExit(cb: (code: number) => void): void { this.exitCbs.push(cb); }\n kill(): void { this.killed = true; }\n stop(): Promise<void> {\n this.stopped = true;\n return this.gone;\n }\n getOutput(): string { return this.output; }\n\n emitOutput(text: string): void {\n this.output += text;\n for (const cb of this.outputCbs) cb(text);\n }\n emitExit(code: number): void {\n for (const cb of this.exitCbs) cb(code);\n this.markGone();\n }\n write(text: string): void {\n this.written.push(text);\n const message = text.trim();\n if (message) this.sendMessage(message);\n }\n\n turnState(): 'working' | 'idle' { return this.state; }\n onTurnEnd(cb: (reason: StructuredTurnEnd) => void): void { this.turnEndCbs.push(cb); }\n onEvent(cb: (event: StructuredEvent) => void): void { this.eventCbs.push(cb); }\n onTaskComplete(cb: (report: TaskCompleteArgs) => void): void { this.completeCbs.push(cb); }\n /**\n * The runner calls `task_complete`, as `StructuredSession` relays it. Here it\n * is published at once; a test that needs the real session's turn-end\n * ordering drives `StructuredSession` itself.\n */\n reportComplete(report: TaskCompleteArgs): void {\n for (const cb of this.completeCbs) cb(report);\n }\n onToolCheckpoint(handler: (question: string, signal: AbortSignal) => Promise<CheckpointAnswer>): void { this.checkpointHandler = handler; }\n private scriptExitCbs: Array<(exit: ScriptExit) => void> = [];\n onScriptExit(cb: (exit: ScriptExit) => void): void { this.scriptExitCbs.push(cb); }\n /** A script task's body ended, as `ScriptSession` hands it on (ADR-0035). */\n reportScriptExit(exit: ScriptExit): void {\n for (const cb of this.scriptExitCbs) cb(exit);\n }\n /** The runner calls `checkpoint`, as `StructuredSession` relays it; settles with the answer. */\n callCheckpoint(question: string, signal: AbortSignal = new AbortController().signal): Promise<CheckpointAnswer> {\n if (!this.checkpointHandler) throw new Error('no checkpoint handler attached');\n return this.checkpointHandler(question, signal);\n }\n sendMessage(text: string, from?: TaskMessageFrom): string {\n this.messageCount += 1;\n const id = `msg-${this.messageCount}`;\n if (this.state === 'idle') this.deliver(text, from);\n else this.messages.push({ id, text, ...(from ? { from } : {}) });\n return id;\n }\n /** Interrupts, and the turn that replaces the interrupted one opens with the forced message. */\n forceSend(text: string): string {\n if (this.state === 'idle') return this.sendMessage(text);\n this.messageCount += 1;\n const id = `msg-${this.messageCount}`;\n this.putForced({ id, text, forced: true });\n void this.interrupt();\n return id;\n }\n forceSendQueued(id: string): boolean {\n const message = this.messages.find((m) => m.id === id);\n if (!message) return false;\n if (message.forced) return true;\n this.messages = this.messages.filter((m) => m !== message);\n this.putForced({ ...message, forced: true });\n if (this.state === 'working') void this.interrupt();\n return true;\n }\n private putForced(message: QueuedTaskMessage): void {\n const at = this.messages.findIndex((m) => !m.forced);\n this.messages.splice(at < 0 ? this.messages.length : at, 0, message);\n }\n removeQueued(id: string): boolean {\n const before = this.messages.length;\n this.messages = this.messages.filter((m) => m.id !== id);\n return this.messages.length < before;\n }\n queued(): QueuedTaskMessage[] { return this.messages.map((m) => ({ ...m })); }\n async interrupt(): Promise<void> {\n this.interrupts += 1;\n if (this.state === 'working') this.emitTurnEnd('interrupted');\n }\n nativeSessionId(): string | null { return this.sessionId; }\n /** What `sessionCost()` reports: the runner's running cost as counted, null while unknown. */\n cost: RunnerSessionCost | null = null;\n sessionCost(): RunnerSessionCost | null { return this.cost; }\n\n /** What `commands()` reports: the runner's own commands (#66). */\n runnerCommands: RunnerCommand[] = [];\n /** Every runner command invoked, in order. */\n invoked: InvokedCommand[] = [];\n commands(): RunnerCommand[] { return [...this.runnerCommands]; }\n /** Runs it as a message that starts with it, which is what a runner reads. */\n invokeCommand(name: string, args = ''): string {\n this.invoked.push({ name, ...(args.trim() ? { args: args.trim() } : {}) });\n return this.sendMessage(args.trim() ? `/${name} ${args.trim()}` : `/${name}`);\n }\n\n /** Every answer this session was given, in order. */\n answers: Array<{ id: string; decision: ApprovalDecision }> = [];\n private readonly openPermissions = new Set<string>();\n\n /** The runner asks to use a tool, as `StructuredSession` announces it. */\n requestPermission(id: string, name: string, input: Record<string, unknown>, suggestions: unknown[] = []): void {\n this.openPermissions.add(id);\n this.emitEvent({ type: 'permission_request', id, name, detail: JSON.stringify(input), input, suggestions });\n }\n /** The runner gives up on a request, as an interrupt makes it. */\n withdrawPermission(id: string): void {\n if (this.openPermissions.delete(id)) this.emitEvent({ type: 'permission_withdrawn', id });\n }\n answerPermission(id: string, decision: ApprovalDecision): boolean {\n if (!this.openPermissions.delete(id)) return false;\n this.answers.push({ id, decision });\n this.emitEvent({ type: 'permission_decided', id, decision });\n return true;\n }\n\n emitEvent(event: StructuredEvent): void {\n for (const cb of this.eventCbs) cb(event);\n }\n /** The runner reads a queued message inside the running turn, as `StructuredSession` reports it (ADR-0023). */\n deliverMidTurn(id: string): void {\n const message = this.messages.find((m) => m.id === id);\n if (!message) return;\n this.messages = this.messages.filter((m) => m !== message);\n this.emitEvent({ type: 'message_delivered', messageId: id, text: message.text, ...(message.from ? { from: message.from } : {}) });\n }\n /**\n * The turn ends without a `task_complete` call, and so does the turn the\n * completion reminder opens if one was sent (ADR-0018, W1): the task waits for input.\n */\n waitForInput(): void {\n const reminders = this.fromOrdewell.length;\n this.emitTurnEnd('completed');\n if (this.fromOrdewell.length > reminders) this.emitTurnEnd('completed');\n }\n emitTurnEnd(reason: StructuredTurnEnd): void {\n const next = this.messages.shift();\n if (!next) this.state = 'idle';\n for (const cb of this.turnEndCbs) cb(reason);\n if (next) this.deliver(next.text, next.from);\n }\n\n private deliver(text: string, from?: TaskMessageFrom): void {\n this.state = 'working';\n this.delivered.push(text);\n if (from === 'ordewell') this.fromOrdewell.push(text);\n this.emitEvent({ type: 'turn_start', text, ...(from ? { from } : {}) });\n }\n}\n\nexport type FakeIsolationCall =\n | { op: 'isActive'; workspaceRoot: string }\n | { op: 'stash'; workspaceRoot: string }\n | { op: 'startRun'; workspaceRoot: string }\n | { op: 'prepare' | 'reopen' | 'verifyRepair'; taskId: string }\n | { op: 'integrate'; taskId: string }\n | { op: 'release'; taskId: string; keep: boolean }\n | { op: 'handoff' | 'pruneOrphans' | 'reviewDiff' | 'mergeIntoCheckedOut' | 'sweep' | 'findInHead' | 'snapshotTree' | 'changedSince' }\n | { op: 'discard'; integration: IntegrationDisposal };\n\n/**\n * An in-memory {@link IWorktreeIsolation} for scheduling tests: no git, no\n * filesystem. Every call is logged in `calls`; `prepare` hands back a\n * deterministic fake cwd. Set `availability` to exercise the fallbacks, `outcomes`\n * to script a conflict, and `holdIntegration` to keep a task un-integrated so a\n * test can observe that its dependents wait. `repos` makes the run a group of\n * several; `changes` and `stopsIn` say which of them a task changes and where\n * its landing stops. `repairEvidence` scripts what a conflict repair's\n * evidence check finds, and `removal` what a removal kept back.\n */\nexport class FakeWorktreeIsolation implements IWorktreeIsolation {\n availability: IsolationAvailability = { active: true };\n /** The repo paths `startRun` groups: a group of one at `.` unless set. */\n repos: string[] = [SELF_REPO];\n /** Per task id, the repos it changes; every repo of the group when not listed. */\n changes = new Map<string, string[]>();\n /** Per task id, the repo its landing stops in when its outcome is not `merged`; its first changed repo when not listed. */\n stopsIn = new Map<string, string>();\n /** Per task id, the files a `conflict` outcome names; empty when not listed. */\n conflictFiles = new Map<string, string[]>();\n /** What `mergeIntoCheckedOut` answers; a `merged` one also puts every landed task in HEAD, as git would. */\n mergeResult: IsolationMergeResult = { outcome: 'merged' };\n /** Task ids whose landed work `findInHead` finds in HEAD besides those a merge put there, as a merge by hand would. */\n mergedByHand = new Set<string>();\n /** What `snapshotTree` answers; null as for a workspace with no repo. */\n treeSnapshot: TreeSnapshot | null = {};\n /** What `changedSince` answers: the tracked files an ops task changed. */\n changedFiles: string[] = [];\n /** What `startRun` shares and `prepare` copies, to exercise their notices. */\n shared: string[] = [];\n sharedRepos: string[] = [];\n copied: string[] = [];\n setupFailed: SetupFailure[] = [];\n /** Set to make `startRun` throw, as git does when no repo of the group can be isolated. */\n startRunError: Error | null = null;\n /** Set to make `discard` / `sweep` throw after logging the call. */\n discardError: Error | null = null;\n sweepError: Error | null = null;\n /** What `pruneOrphans` reports as kept back from the sweep. */\n keptOnPrune: IsolationPruneResult['kept'] = [];\n /**\n * What every removal — a `release` that removes, `discard`, `pruneOrphans` —\n * reports it kept back. A refused task keeps its record, as git's does.\n */\n removal: IsolationRemoval = noRemoval();\n /** Per task id; a task not listed integrates as `merged`. */\n outcomes = new Map<string, IsolationOutcome>();\n /** Per task id, what `verifyRepair` finds; a task not listed passes. */\n repairEvidence = new Map<string, RepairEvidence>();\n calls: FakeIsolationCall[] = [];\n private holds = new Map<string, Promise<void>>();\n private verifyHolds = new Map<string, Promise<void>>();\n private runCount = 0;\n\n private log(call: FakeIsolationCall): void { this.calls.push(call); }\n\n /** Task ids in the order `op` was called for them. */\n taskIdsFor(op: 'prepare' | 'reopen' | 'verifyRepair' | 'integrate' | 'release'): string[] {\n return this.calls.flatMap((c) => (c.op === op && 'taskId' in c ? [c.taskId] : []));\n }\n\n /** Make `integrate` for this task wait until the returned function is called. */\n holdIntegration(taskId: string): () => void {\n let open!: () => void;\n this.holds.set(taskId, new Promise<void>((resolve) => { open = resolve; }));\n return open;\n }\n\n /** Make `verifyRepair` for this task wait until the returned function is called. */\n holdVerification(taskId: string): () => void {\n let open!: () => void;\n this.verifyHolds.set(taskId, new Promise<void>((resolve) => { open = resolve; }));\n return open;\n }\n\n async isActive(workspaceRoot: string): Promise<IsolationAvailability> {\n this.log({ op: 'isActive', workspaceRoot });\n return this.availability;\n }\n\n /** Like git: once the tracked changes are stashed, the tree is no longer dirty. */\n async stash(workspaceRoot: string): Promise<void> {\n this.log({ op: 'stash', workspaceRoot });\n if (!this.availability.active && this.availability.reason === 'dirty') this.availability = { active: true };\n }\n\n async startRun(workspaceRoot: string): Promise<IsolationRun> {\n this.log({ op: 'startRun', workspaceRoot });\n if (this.startRunError) throw this.startRunError;\n const id = `run${++this.runCount}`;\n return {\n id,\n workspaceRoot,\n repos: this.repos.map((repo) => ({\n path: repo, root: `${workspaceRoot}/${repo}`.replace(/\\/\\.$/, ''), baseRef: 'base0000', baseBranch: 'main', integrationBranch: integrationBranchFor(id),\n })),\n shared: [...this.shared],\n sharedRepos: [...this.sharedRepos],\n tasks: {},\n };\n }\n\n async prepare(task: Task, run: IsolationRun, _intent?: PrepareIntent, opts: { readOnly?: boolean } = {}): Promise<PreparedTask> {\n this.log({ op: 'prepare', taskId: task.id });\n const name = `${task.order}-${task.id}`;\n const cwd = `/fake-worktrees/${run.id}/${name}`;\n const branch = `ordewell/${run.id}/${name}`;\n run.tasks[task.id] = {\n taskId: task.id, order: task.order, title: task.title, branch, workspace: cwd, status: 'active',\n repos: Object.fromEntries(run.repos.map((r) => [r.path, { worktree: r.path === SELF_REPO ? cwd : `${cwd}/${r.path}`, linked: [] }])),\n ...(opts.readOnly ? { readOnly: true } : {}),\n };\n return { cwd, branch, copied: [...this.copied], ...(this.setupFailed.length > 0 ? { setupFailed: [...this.setupFailed] } : {}) };\n }\n\n /** Like git: the same cwd, each changed repo's tip recorded, the repair counted. */\n async reopen(task: Task, run: IsolationRun): Promise<PreparedTask> {\n this.log({ op: 'reopen', taskId: task.id });\n const record = run.tasks[task.id];\n if (record?.status !== 'conflict') throw new Error(`Task ${task.order} has no conflict to repair`);\n const changed = Object.entries(record.repos).filter(([repo, entry]) => entry.changed || repo === record.conflictRepo).map(([repo]) => repo);\n const files = (record.conflictFiles ?? []).map((file) => (record.conflictRepo && record.conflictRepo !== SELF_REPO ? `${record.conflictRepo}/${file}` : file));\n record.repairs = (record.repairs ?? 0) + 1;\n record.repairBase = Object.fromEntries(changed.map((repo) => [repo, `tip-${repo}`]));\n record.repairedFiles = [...new Set([...(record.repairedFiles ?? []), ...files])];\n record.status = 'repairing';\n return { cwd: record.workspace, branch: record.branch, copied: [] };\n }\n\n async verifyRepair(task: Task, _run: IsolationRun): Promise<RepairEvidence> {\n this.log({ op: 'verifyRepair', taskId: task.id });\n await this.verifyHolds.get(task.id);\n return this.repairEvidence.get(task.id) ?? { ok: true };\n }\n\n /** Like git: the landing is recorded and persisted before the (held) merge, and cleared once it settles. */\n async integrate(task: Task, run: IsolationRun, persist: () => void = () => undefined, commit?: HandoffCommit): Promise<IsolationOutcome> {\n this.log({ op: 'integrate', taskId: task.id });\n const record = run.tasks[task.id];\n if (!record) return 'failed';\n if (commit) record.commit = commit;\n if (record.readOnly) {\n record.status = 'failed';\n record.landingError = 'a read-only task lands nothing';\n return 'failed';\n }\n const changed = this.changes.get(task.id) ?? run.repos.map((r) => r.path);\n for (const [repo, entry] of Object.entries(record.repos)) entry.changed = changed.includes(repo) || (entry.changed ?? false);\n if (changed.length > 0) {\n run.landing = { taskId: task.id, tips: Object.fromEntries(changed.map((repo) => [repo, `tip-${repo}`])) };\n persist();\n }\n await this.holds.get(task.id);\n delete run.landing;\n const outcome = this.outcomes.get(task.id) ?? 'merged';\n record.status = outcome;\n delete record.repairBase;\n if (outcome === 'merged') {\n delete record.conflictRepo;\n delete record.conflictFiles;\n } else {\n record.conflictRepo = this.stopsIn.get(task.id) ?? changed[0] ?? SELF_REPO;\n const files = this.conflictFiles.get(task.id);\n if (files) record.conflictFiles = files;\n else delete record.conflictFiles;\n }\n return outcome;\n }\n\n async release(run: IsolationRun, taskId: string, opts: { keep: boolean }): Promise<IsolationRemoval> {\n this.log({ op: 'release', taskId, keep: opts.keep });\n const record = run.tasks[taskId];\n // Like git: a read-only task's record goes whole, with nothing kept (ADR-0046).\n if (record?.readOnly) {\n delete run.tasks[taskId];\n return noRemoval();\n }\n const removal = opts.keep || !record ? noRemoval() : this.removed();\n const refused = removal.refused.some((r) => r.task?.taskId === taskId);\n if (!opts.keep && !refused) delete run.tasks[taskId];\n else if (record?.status === 'active') record.status = 'kept';\n else if (record?.status === 'repairing') {\n record.status = 'conflict';\n delete record.repairBase;\n }\n return removal;\n }\n\n private removed(): IsolationRemoval {\n return { preserved: [...this.removal.preserved], refused: [...this.removal.refused] };\n }\n\n async handoff(run: IsolationRun): Promise<IsolationHandoff> {\n this.log({ op: 'handoff' });\n return handoffOf(run);\n }\n\n async pruneOrphans(): Promise<IsolationPruneResult> {\n this.log({ op: 'pruneOrphans' });\n return { kept: [...this.keptOnPrune], ...this.removed() };\n }\n async reviewDiff(): Promise<string> { this.log({ op: 'reviewDiff' }); return ''; }\n async mergeIntoCheckedOut(run: IsolationRun): Promise<IsolationMergeResult> {\n this.log({ op: 'mergeIntoCheckedOut' });\n if (this.mergeResult.outcome === 'merged') {\n for (const record of Object.values(run.tasks)) if (record.status === 'merged') this.mergedByHand.add(record.taskId);\n }\n return this.mergeResult;\n }\n async findInHead(run: IsolationRun): Promise<string[]> {\n this.log({ op: 'findInHead' });\n const found: string[] = [];\n for (const record of Object.values(run.tasks)) {\n if (record.status !== 'merged' || record.inHead || !this.mergedByHand.has(record.taskId)) continue;\n record.inHead = true;\n found.push(record.taskId);\n }\n return found;\n }\n async snapshotTree(): Promise<TreeSnapshot | null> { this.log({ op: 'snapshotTree' }); return this.treeSnapshot; }\n async changedSince(): Promise<string[]> { this.log({ op: 'changedSince' }); return [...this.changedFiles]; }\n async discard(_run: IsolationRun, opts: { integration: IntegrationDisposal }): Promise<IsolationRemoval> {\n this.log({ op: 'discard', integration: opts.integration });\n if (this.discardError) throw this.discardError;\n return this.removed();\n }\n async sweep(): Promise<void> {\n this.log({ op: 'sweep' });\n if (this.sweepError) throw this.sweepError;\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ordewell/core",
3
- "version": "0.10.11",
3
+ "version": "0.11.0",
4
4
  "description": "Ordewell core — planner, orchestrator, runners and verification engine.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Alessandro Costanzo Ciano",
@@ -21,7 +21,7 @@
21
21
  "planner"
22
22
  ],
23
23
  "engines": {
24
- "node": ">=20"
24
+ "node": ">=20.3"
25
25
  },
26
26
  "publishConfig": {
27
27
  "access": "public"
@@ -70,7 +70,9 @@
70
70
  "dist",
71
71
  "skills",
72
72
  "README.md",
73
- "LICENSE"
73
+ "LICENSE",
74
+ "NOTICE",
75
+ "AUTHORS.md"
74
76
  ],
75
77
  "scripts": {
76
78
  "build": "tsup src/index.ts src/parsing.ts src/testing.ts src/plan-utils.ts --dts --sourcemap --format cjs,esm --clean --tsconfig tsconfig.json",
@@ -79,6 +81,7 @@
79
81
  "test": "vitest run",
80
82
  "test:quick": "vitest run --exclude '**/*.e2e.test.ts' --exclude '**/GitWorktreeIsolation.test.ts' --exclude '**/node_modules/**'",
81
83
  "test:changed": "vitest run --changed --passWithNoTests --exclude '**/*.e2e.test.ts' --exclude '**/GitWorktreeIsolation.test.ts' --exclude '**/node_modules/**'",
84
+ "test:sandbox": "vitest run --no-file-parallelism src/services/sandbox/__tests__/plannerSandbox.e2e.test.ts src/services/sandbox/__tests__/sealedRuns.e2e.test.ts src/services/__tests__/PatchCapture.e2e.test.ts",
82
85
  "test:coverage": "vitest run --coverage",
83
86
  "lint": "eslint src --ext .ts",
84
87
  "typecheck": "tsc --noEmit"
@@ -93,6 +96,7 @@
93
96
  },
94
97
  "devDependencies": {
95
98
  "@types/markdown-it": "^14.2.0",
99
+ "@types/node": "^25.9.1",
96
100
  "@types/uuid": "^10.0.0",
97
101
  "tsup": "^8.3.0",
98
102
  "typescript": "^5.9.3",
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: diagnose
3
+ description: Explain a failure before fixing it: a bug, a failing test or build, a regression, a crash. Not for a build with known steps or a question your research answers.
4
+ ---
5
+
6
+ Find the cause before you plan a fix. A fix planned from a guess passes for the wrong reason or moves the failure. The order is fixed: reproduce, hypothesise, investigate, decide, fix.
7
+
8
+ ## 1. Reproduce
9
+
10
+ Get a command that fails, and what it prints when it does. The user may have given one; otherwise search for it. Ask the user only for what you cannot find: the input, the environment, how often it happens.
11
+
12
+ - A command you can run yourself this turn: run it, in a sealed run or with `run_script`, and keep its output.
13
+ - One you cannot run this turn: a research task that runs it and reports the exact command and output.
14
+ - A failure with no command: a research task that writes a failing test in its own throwaway worktree and reports the test's text and its result. The worktree is thrown away, so the text must be in the report. A fix task will add the test itself.
15
+ - A script task fails when the bug reproduces. That is its answer, but it also reads as a failed task, and everything that depends on it is blocked. Use one only when nothing depends on it.
16
+
17
+ If nothing reproduces it, say so and make that the first round: a failure you cannot trigger cannot be checked fixed.
18
+
19
+ ## 2. Hypotheses
20
+
21
+ Write two to four causes that differ in mechanism, not in wording. A hypothesis is a claim about the code or its inputs, such as "the cache is keyed by path, so a rename serves the old entry", not "something is wrong with the cache". Say what each one predicts that the others do not.
22
+
23
+ ## 3. Investigate
24
+
25
+ When one sealed command can settle a hypothesis, test it yourself this turn: run it, and keep the command and its output. Give a hypothesis its own research task only when testing it needs a worktree (a failing test, a build, a long run) or another model. Those run side by side while you keep talking with the user. Each brief stands alone and holds:
26
+
27
+ - the failure: the command and the output you reproduced;
28
+ - the one hypothesis to test, and where to start;
29
+ - a prediction that could prove it wrong: "if this is the cause, then changing X makes the failure go away, and Y shows Z. If it does not, the hypothesis is wrong";
30
+ - the report: the prediction's result first, then evidence for and against with file paths and line numbers, and the command it ran and what it printed. It may name a cause you missed.
31
+
32
+ Tell the user in a line what you are testing, then wait. Each report wakes you.
33
+
34
+ ## 4. Decide from evidence
35
+
36
+ A hypothesis stands when its prediction was tested and held, and the rest are shown wrong or not shown right. A report's confident prose is not evidence; its commands and their output are. Re-run the one command that decides when the plan rests on it.
37
+
38
+ - Two survive, or one survives untested: plan a research task whose experiment separates them, or tries to break the survivor.
39
+ - All fail: say what they ruled out, form new hypotheses from what the reports found, and run another round.
40
+ - One stands: tell the user the cause and the evidence in a few lines, then plan the fix.
41
+
42
+ ## 5. Fix, checked against the reproduction
43
+
44
+ A change task for the fix. It adds the reproduction as a test if the workspace lacks one, and runs it and the wider suite itself before it completes. A research task's test was thrown away, so the fix task depends on that task and reads its report with `read_task`; do not copy the test into the brief. Its brief names the cause and the files, and says to change the cause and nothing near it.
45
+
46
+ Put the fix in a loop only when the user asked to iterate until the reproduction passes, or it is a specific acceptance gate that needs independent repeated checking and fixing; say why in the outline. Then:
47
+
48
+ - If the reproduction is not yet a test the workspace holds, a first change task adds it and changes no production code. Make the loop's check depend on a research task whose test was thrown away: the body tasks may then depend on it too, and read its report with `read_task`.
49
+ - The check is a script task running the reproduction. Add the wider suite as a second command so a fix that breaks something else fails the round.
50
+ - Choose the rounds deliberately. A check that runs out of rounds blocks what follows, which is the right outcome when the fix does not converge.
51
+
52
+ When the fix ends, tell the user whether the reproduction passes and what changed.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: explore
3
+ description: Work an open problem with no known path: which approach to take, whether something is feasible, what the options are. Not for a build with known steps or a failure to explain.
4
+ ---
5
+
6
+ An open problem is done when a claim has survived an attempt to break it, not when the first plausible answer arrives. Plan in rounds, and let each round's reports decide the next.
7
+
8
+ ## 1. Frame it
9
+
10
+ Write down the question, what a finished answer looks like, and how a claim will be judged: a command, a measurement, a review. Ask the user only about what you cannot find yourself, and mostly about the bar: what makes an answer good enough.
11
+
12
+ ## 2. Plan the first round only
13
+
14
+ Plan nothing beyond it. You do not yet know which questions matter, so a later task written now is a guess.
15
+
16
+ Who finds out is decided as WHO EXPLORES says, by what the result is. A question one lookup answers, whose conclusion only decides what you say or ask, is orientation, not a round: read it yourself, or ask one of your own agents if you have any, within the turn. What a later task acts on, or what needs a worktree, another model or the user's eye, is a task of its own. It is a research task, and so a round, only when you must decide something on its report; a read-only task whose report only later tasks read is not.
17
+
18
+ Make the research tasks deliberately different: another approach, another source, a different assumption, one that questions the premise itself. Several variations on one idea are one task done several times. Keep the round within the run's Parallel limit, so none waits.
19
+
20
+ Each brief stands alone and holds the question, where to start, and the report you want: a claim, the evidence for it, what would break it, and the commands it ran with their output. A claim that comes with a command is worth more than one that comes with an argument.
21
+
22
+ Tell the user, in a line each, what the round asks. Then wait. Reports wake you.
23
+
24
+ ## 3. Read what came back
25
+
26
+ Sort each claim: it holds on evidence, it failed, or it is unverified. Drop what failed, and note why in a line, so the same idea is not planned again. Never take a claim for true because its author sounded sure.
27
+
28
+ ## 4. Try to break what looks promising
29
+
30
+ For each promising claim, add a refuter:
31
+
32
+ - A research task given the claim and how to reproduce it, and told to break it. Do not hand it the author's reasoning, which would anchor it. It reports either the break, with how, or what it tried and why the claim survived.
33
+ - A claim that is a change with a checkable bar can be built instead, as a change task inside a check-and-fix loop. The loop's check is the refuter, and whether the bar is met is its verdict.
34
+
35
+ A claim is trusted only after its refuter fails to break it.
36
+
37
+ ## 5. Let one check choose between competing approaches
38
+
39
+ When the question is which of several approaches to take, explore them side by side and choose on a check, not on an opinion:
40
+
41
+ - A shared check, fixed before the explorers start: a test, a build or a measurement the same for every approach. Write it into every explorer's brief, so the candidates are compared on one bar. Only a candidate that passes it is eligible.
42
+ - Explorers: read-only tasks, each trying one deliberately different approach, with no dependencies between them. Each runs the shared check and reports its commands with their output. An explorer that builds a prototype keeps it with `save_patch` before it calls `task_complete`, because its worktree is thrown away with it. Tell it to.
43
+ - The choice: if exactly one candidate passes, no judge is needed; that one is chosen. If several pass, one judge task that depends on every explorer recommends one. It reads their handoffs with `read_task` (with `include_report` where the summary is not enough), tries to break the most promising claim, and cites its evidence: the explorers' checks and its own, not its opinion. If none passes, that is a finding: say so, and plan the next round.
44
+ - Make the judge a research task, so that its recommendation wakes you and the build task is yours to add once you have read it. A recommendation is advice. Adding the build task on one explorer's patch is the pick, and approving that task is the user's, or at Act the supervisor's.
45
+ - The build task you add after the choice depends on the chosen explorer, and on the judge when there is one. It applies that explorer's patch with `apply_patch`, resolves any conflicts the tool reports, and runs the checks itself, since the explorer ran them at its own base. Say so in its brief. An explorer ran the shared check in a worktree it could write, so its patch may change the check itself: tell the builder, and the judge if there is one, to confirm that the patch leaves the check's files alone, or to run the check as the base has it. A patch that deletes or adds a file the worktree also changed applies nothing: the tool says why, and the builder applies the rest by hand.
46
+
47
+ A prototype that was not saved with `save_patch` is gone with the worktree. If an explorer is not asked to save one, the builder re-creates the approach from its report, so ask for a report that is enough to do that.
48
+
49
+ ## 6. Extend the plan
50
+
51
+ Extend the plan as reports arrive, with the next round, not all of it at once:
52
+
53
+ - Prune: remove the pending tasks whose premise just failed. Leave what has run; it is the record.
54
+ - Deepen what survived: narrower questions, a harder refuter, a prototype.
55
+ - Widen only when a round found nothing that survives, and then with a direction the failures pointed away from.
56
+
57
+ At the end of each round tell the user what survived, what died and what the next round is.
58
+
59
+ ## 7. Spend your own context on integration
60
+
61
+ The reports are your input. Do not redo a task's research yourself; read its report, and its output or diff only where the plan rests on it. Your work is to compare claims, reconcile reports that disagree, choose, and see what no single task could.
62
+
63
+ ## 8. Stop
64
+
65
+ Stop when a claim has survived its refuter and the next step is known: plan the build, as vertical slices with a loop at the end if it has a checkable bar, or answer the user if the finding is the result. Stop too when rounds stop producing anything new: say what is established, what is not and what more would cost, and let the user decide.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/services/JsonExtractor.ts","../src/services/ModeResolver.ts","../src/services/ModelAllowlistResolver.ts","../src/services/TaskAssignment.ts","../src/services/PlannedTask.ts"],"sourcesContent":["/** The envelope key a full plan reply is keyed by: `{\"tasks\":[...]}`. */\nexport const PLAN_ENVELOPE_KEY = 'tasks';\n/** The envelope key a targeted-edits reply is keyed by: `{\"taskOps\":[...]}`. */\nexport const TASK_OPS_ENVELOPE_KEY = 'taskOps';\n/** The envelope key a read-only detail request is keyed by: `{\"taskQuery\":{...}}`. */\nexport const TASK_QUERY_ENVELOPE_KEY = 'taskQuery';\n\n/** Thrown when an LLM plan response cannot be parsed into a valid plan. Carries the raw text so callers can log it or retry. */\nexport class PlanParseError extends Error {\n readonly raw: string;\n /**\n * True when the reply was cut off mid-object (output-token limit) rather\n * than malformed — the two need different repairs: a truncated emission will\n * be cut at the same point again unless the retry frees context or asks for\n * terser output.\n */\n readonly truncated: boolean;\n /**\n * True when the JSON parsed fine and a *shape* rule rejected it. The generic\n * \"could not be parsed as JSON\" corrective is a lie for these, so the model\n * re-sends the same object; a semantic rejection must be repaired by naming\n * the rule it broke.\n */\n readonly semantic: boolean;\n constructor(message: string, raw: string, opts?: { truncated?: boolean; semantic?: boolean }) {\n super(message);\n this.name = 'PlanParseError';\n this.raw = raw;\n this.truncated = opts?.truncated ?? false;\n this.semantic = opts?.semantic ?? false;\n }\n}\n/**\n * Strip the noise reasoning models wrap around their answer: markdown code\n * fences and `<think>…</think>` chain-of-thought blocks (which routinely contain\n * stray braces that would otherwise derail brace-balancing). Reasoning captured\n * out-of-band by the service never reaches here, but a model that inlines its\n * thinking in the content channel still must not corrupt parsing.\n */\nexport function stripModelNoise(raw: string): string {\n return raw\n .replace(/<think>[\\s\\S]*?<\\/think>/gi, '')\n .replace(/```json\\s*/gi, '')\n .replace(/```\\s*/g, '')\n .trim();\n}\n\nconst THINK_OPEN = '<think>';\nconst THINK_CLOSE = '</think>';\nconst FENCE = '```';\nconst FENCE_JSON_TAG = 'json';\n\n/**\n * Whether a reply that begins with `prefix` opens with a JSON object once the\n * noise {@link stripModelNoise} removes is skipped — the streaming counterpart\n * of it, answering from the start of a reply that has not finished arriving.\n * Undefined while the prefix cannot tell yet: blank, inside an unclosed think\n * block, or partway through a fence or a tag.\n */\nexport function opensWithJsonObject(prefix: string): boolean | undefined {\n let rest = prefix;\n for (;;) {\n rest = rest.trimStart();\n if (!rest) return undefined;\n const head = rest.slice(0, THINK_OPEN.length).toLowerCase();\n if (head === THINK_OPEN) {\n const close = rest.toLowerCase().indexOf(THINK_CLOSE);\n if (close < 0) return undefined;\n rest = rest.slice(close + THINK_CLOSE.length);\n continue;\n }\n if (rest.startsWith(FENCE)) {\n rest = rest.slice(FENCE.length);\n const tag = rest.slice(0, FENCE_JSON_TAG.length).toLowerCase();\n if (tag === FENCE_JSON_TAG) rest = rest.slice(FENCE_JSON_TAG.length);\n else if (tag === rest.toLowerCase() && FENCE_JSON_TAG.startsWith(tag)) return undefined;\n continue;\n }\n if (THINK_OPEN.startsWith(head) || FENCE.startsWith(rest)) return undefined;\n return rest.startsWith('{');\n }\n}\n\n/**\n * Scan from `start` (which must point at a `{`) for the matching balanced close,\n * ignoring braces inside strings. Returns the object substring and whether it was\n * balanced; an unbalanced result means the input was truncated mid-object.\n */\nfunction balancedObjectAt(text: string, start: number): { json: string; balanced: boolean } {\n let depth = 0;\n let inString = false;\n let escaped = false;\n for (let i = start; i < text.length; i++) {\n const ch = text[i];\n if (inString) {\n if (escaped) escaped = false;\n else if (ch === '\\\\') escaped = true;\n else if (ch === '\"') inString = false;\n continue;\n }\n if (ch === '\"') inString = true;\n else if (ch === '{') depth++;\n else if (ch === '}') {\n depth--;\n if (depth === 0) return { json: text.slice(start, i + 1), balanced: true };\n }\n }\n return { json: text.slice(start), balanced: false };\n}\n\n/**\n * Scan an LLM response for every balanced `{…}` object carrying a `\"<key>\":`\n * entry. Strips fences and `<think>` blocks first. A matching object is\n * skipped over wholesale, so its nested braces don't produce fragment\n * candidates; non-matching objects are descended into, so a match wrapped in\n * an unrelated outer object is still found. `fallback` preserves the legacy\n * behavior for callers with no keyed match: the first balanced object, else\n * the stripped text so a JSON.parse error still surfaces.\n */\nexport function extractObjectsWithKey(raw: string, key: string): {\n matches: string[];\n fallback: { json: string; balanced: boolean };\n sawBrace: boolean;\n /** An object ran to EOF unclosed — the classic signature of truncated output. */\n sawUnbalanced: boolean;\n} {\n const stripped = stripModelNoise(raw);\n const keyRe = new RegExp(`\"${key}\"\\\\s*:`);\n\n const matches: string[] = [];\n let firstObject: string | null = null;\n let sawBrace = false;\n let sawUnbalanced = false;\n let inString = false;\n let escaped = false;\n for (let i = 0; i < stripped.length; i++) {\n const ch = stripped[i];\n if (inString) {\n if (escaped) escaped = false;\n else if (ch === '\\\\') escaped = true;\n else if (ch === '\"') inString = false;\n continue;\n }\n if (ch === '\"') { inString = true; continue; }\n if (ch !== '{') continue;\n\n sawBrace = true;\n const obj = balancedObjectAt(stripped, i);\n // A stray brace in a reasoning preamble produces an unbalanced candidate,\n // which we skip so it can't swallow the real object that follows.\n if (obj.balanced && keyRe.test(obj.json)) {\n matches.push(obj.json);\n i += obj.json.length - 1;\n continue;\n }\n if (!obj.balanced) sawUnbalanced = true;\n if (!firstObject && obj.balanced) firstObject = obj.json;\n }\n\n return {\n matches,\n fallback: firstObject !== null ? { json: firstObject, balanced: true } : { json: stripped, balanced: false },\n sawBrace,\n sawUnbalanced,\n };\n}\n\n/**\n * Pull the JSON object out of an LLM response and report whether it was balanced.\n * Strips fences and `<think>` blocks, then walks every top-level `{…}` and\n * returns the FIRST one that contains a `\"tasks\"` key — so a brace appearing\n * earlier in reasoning (or an unrelated preamble object) can't make us parse the\n * wrong thing. Falls back to the first balanced object and, failing that, the\n * first `{` onwards so a genuine `JSON.parse` error still surfaces.\n */\nexport function extractObjectWithBalance(raw: string): { json: string; balanced: boolean; sawBrace: boolean } {\n const { matches, fallback, sawBrace } = extractObjectsWithKey(raw, PLAN_ENVELOPE_KEY);\n if (matches.length > 0) return { json: matches[0], balanced: true, sawBrace };\n return { ...fallback, sawBrace };\n}\n\n/**\n * Pull the JSON object out of an LLM response. Strips markdown code fences and\n * `<think>` blocks, prefers the object containing a `tasks` key, and tolerates a\n * prose preamble and trailing prose. Returns the fence-stripped text unchanged\n * if no `{` is present so the caller's JSON.parse produces a clear failure.\n */\nexport function extractJsonObject(raw: string): string {\n return extractObjectWithBalance(raw).json;\n}\n\n/** Drop trailing commas before a closing `}` or `]` — a common model quirk. */\nexport function stripTrailingCommas(json: string): string {\n return json.replace(/,(\\s*[}\\]])/g, '$1');\n}\n\n/**\n * Escape raw control characters that appear INSIDE string literals — budget\n * models routinely emit multi-line `prompt` strings with literal newlines,\n * which strict JSON.parse rejects (\"bad control character\"). Characters\n * outside strings (formatting whitespace) are left untouched.\n */\nexport function escapeControlCharsInStrings(json: string): string {\n let out = '';\n let inString = false;\n let escaped = false;\n for (const ch of json) {\n if (inString) {\n if (escaped) { out += ch; escaped = false; continue; }\n if (ch === '\\\\') { out += ch; escaped = true; continue; }\n if (ch === '\"') { out += ch; inString = false; continue; }\n if (ch === '\\n') { out += '\\\\n'; continue; }\n if (ch === '\\r') { out += '\\\\r'; continue; }\n if (ch === '\\t') { out += '\\\\t'; continue; }\n out += ch;\n continue;\n }\n if (ch === '\"') inString = true;\n out += ch;\n }\n return out;\n}\n","import { flattenTasks, type RunnerId, type Task } from '../models/Task';\n\nexport interface RunnerModeInfo {\n id: string;\n label: string;\n description: string;\n autonomous?: boolean;\n safe?: boolean;\n readOnly?: boolean;\n sandboxed?: boolean;\n /** Why this mode cannot be used on this host (a failed probe); such a mode is shown, never assigned. */\n unavailable?: string;\n}\n\n/** The slice of RunnerRegistry this module needs — structural, so nothing here depends on the manifest layer. */\nexport interface ManifestLookup {\n getManifest(runner: RunnerId): { modes?: RunnerModeInfo[] } | undefined;\n}\n\n/**\n * A runner's modes are declared only in its manifest, never through a service,\n * so every caller that needs them fuses registry and runner list by hand. This\n * is that fusion, in one place: the planner prompt, the mode pickers on each\n * surface, and the runner-change retarget all read modes the same way.\n *\n * A runner with no modes maps to `[]` rather than being dropped — callers\n * distinguish \"declares no modes\" from \"not asked about\".\n */\nexport function runnerModesFrom(registry: ManifestLookup, runners: RunnerId[]): Record<RunnerId, RunnerModeInfo[]> {\n return Object.fromEntries(runners.map((r) => [r, registry.getManifest(r)?.modes ?? []] as const));\n}\n\n/**\n * Find the mode ID a runner resolves to under the given toggle state.\n * Tag-based first (autonomous/safe); falls back to positional last non-plan\n * if no tag is present on any mode (degrades gracefully on untagged manifests).\n * Returns undefined if `modes` is empty or undefined.\n */\nexport function resolveDefaultMode(\n modes: readonly RunnerModeInfo[] | undefined,\n autonomousDefault = true,\n): string | undefined {\n if (!modes || modes.length === 0) return undefined;\n const tag: 'autonomous' | 'safe' = autonomousDefault ? 'autonomous' : 'safe';\n const tagged = modes.find((m) => m[tag]);\n if (tagged) return tagged.id;\n for (let i = modes.length - 1; i >= 0; i--) {\n if (!modes[i].readOnly && modes[i].id !== 'plan') return modes[i].id;\n }\n return undefined;\n}\n\n/** The user-facing name of the `autonomousMode` boolean's two states. */\nexport function autonomyLevelLabel(autonomous: boolean): 'Full' | 'Guarded' {\n return autonomous ? 'Full' : 'Guarded';\n}\n\n/** `full`/`guarded` name the levels; `on`/`off` and `auto` are older words for them. Null for anything else. */\nexport function parseAutonomyLevel(arg: string | undefined): boolean | null {\n switch (arg?.trim().toLowerCase()) {\n case 'full': case 'on': return true;\n case 'guarded': case 'auto': case 'off': return false;\n default: return null;\n }\n}\n\n/**\n * The mode a manifest tags `readOnly` — never found by id, so a runner with no\n * tag has none. A mode this host cannot run is still the runner's read-only\n * mode: it is how a task already on it is recognized, and why a move of that\n * task is refused rather than made writable.\n */\nexport function taggedReadOnlyMode(modes: readonly RunnerModeInfo[] | undefined): string | undefined {\n return modes?.find((m) => m.readOnly)?.id;\n}\n\n/** The read-only mode a task can be given on this host: the tagged one, unless it is unavailable here. */\nexport function readOnlyMode(modes: readonly RunnerModeInfo[] | undefined): string | undefined {\n const mode = modes?.find((m) => m.readOnly);\n return mode && !mode.unavailable ? mode.id : undefined;\n}\n\n/** Why a mode cannot be used on this host, when it cannot. */\nexport function unavailableModeReason(modes: readonly RunnerModeInfo[] | undefined, id: string | undefined): string | undefined {\n return id === undefined ? undefined : modes?.find((m) => m.id === id)?.unavailable;\n}\n\nfunction unavailableModeError(runner: RunnerId, id: string, reason: string): string {\n return `Mode \"${id}\" of runner \"${runner}\" is unavailable on this host: ${reason}`;\n}\n\n/** What a task fails with at spawn when its planned mode is one this host cannot run; undefined when it can. */\nexport function unavailableTaskModeMessage(\n manifest: { displayName: string; modes?: readonly RunnerModeInfo[] },\n mode: string | undefined,\n): string | undefined {\n const reason = unavailableModeReason(manifest.modes, mode);\n return reason ? `Mode \"${mode}\" of ${manifest.displayName} is unavailable on this host: ${reason}` : undefined;\n}\n\n/** What the planner says about a task's mode (ADR-0031): read-only or not, and the mode the task has now, if any. */\nexport interface ModeRequest {\n readOnly?: boolean;\n current?: string;\n}\n\nexport type PlannedMode = { ok: true; mode: string | undefined } | { ok: false; error: string };\n\n/**\n * The mode a task the planner writes lands on (ADR-0031). The planner names no\n * mode: a read-only task gets its runner's read-only mode; any other keeps the\n * mode it has when the runner offers it — a mode the user picked survives an\n * edit that does not ask for a change — and otherwise gets the runner's mode\n * for the level. `readOnly: false` is the one request that moves a task off\n * its read-only mode. Undefined mode when the runner's modes are unknown.\n */\nexport function plannedMode(\n runner: RunnerId,\n runnerModes: Partial<Record<RunnerId, readonly RunnerModeInfo[]>> | undefined,\n { readOnly, current }: ModeRequest,\n autonomousDefault = true,\n): PlannedMode {\n if (!runnerModes) return { ok: true, mode: undefined };\n const modes = runnerModes[runner] ?? [];\n const readOnlyId = readOnlyMode(modes);\n if (readOnly === true) {\n if (readOnlyId) return { ok: true, mode: readOnlyId };\n const able = readOnlyRunners(runnerModes);\n const reason = unavailableModeReason(modes, taggedReadOnlyMode(modes));\n return {\n ok: false,\n error: `Runner \"${runner}\" has no read-only mode${reason ? ' available on this host' : ''}: assign a read-only task to ${able.length > 0 ? `one of: ${able.join(', ')}` : 'no runner: none of the enabled runners has one'}, or drop \"readOnly\"${reason ? `. ${reason}` : ''}`,\n };\n }\n const keep = current !== undefined && modes.some((m) => m.id === current) && !(readOnly === false && current === taggedReadOnlyMode(modes));\n const mode = keep ? current : resolveDefaultMode(modes, autonomousDefault);\n // Refused, never swapped for another mode: that would hand a task the\n // planner or user meant to be sandboxed a level of access nobody chose (ADR-0001).\n const reason = unavailableModeReason(modes, mode);\n if (mode !== undefined && reason) return { ok: false, error: unavailableModeError(runner, mode, reason) };\n return { ok: true, mode };\n}\n\n/**\n * A plan the planner restates over an existing one comes back with every mode\n * decided afresh. A restatement says read-only or not for each task, so that\n * answer stands; what it cannot say is a mode the user picked. So where a\n * task it names was planned before and is not read-only now, the earlier mode\n * is kept when its runner offers it — the rule an edit follows.\n */\nexport function carryPlannedModes(\n before: readonly Task[],\n rewrite: Task[],\n catalog: { runnerModes: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>; autonomousDefault?: boolean },\n): Task[] {\n const prior = new Map(flattenTasks(before).map((t) => [t.id, t]));\n const carry = (t: Task): Task => {\n const subtasks = (t.subtasks ?? []).map(carry);\n const was = prior.get(t.id);\n if (t.type === 'user' || !was || was.type === 'user' || t.taskMode === taggedReadOnlyMode(catalog.runnerModes[t.assignedRunner])) return { ...t, subtasks };\n const mode = plannedMode(t.assignedRunner, catalog.runnerModes, { readOnly: false, current: was.taskMode }, catalog.autonomousDefault ?? true);\n return { ...t, subtasks, ...(mode.ok && mode.mode !== undefined ? { taskMode: mode.mode } : {}) };\n };\n return rewrite.map(carry);\n}\n\n/** The runners a read-only task may go to, as the planner's prompt names them. */\nexport function readOnlyRunners(runnerModes: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>): RunnerId[] {\n return Object.entries(runnerModes).filter(([, ms]) => readOnlyMode(ms)).map(([id]) => id);\n}\n","import type { DiscoveredModel, RunnerId } from '../models/Task';\n\n/**\n * The ids a runner may actually run: the user's allowlist with the ids that\n * provably belong to a *different* runner dropped. `undefined` means no\n * restriction.\n *\n * Model ids are scoped to the agent that lists them, so an OpenRouter slug\n * allowlisted for Claude Code does not limit it — it points it at something it\n * cannot spawn, and the plan only dies once a task is already running. But\n * \"this runner didn't list it\" is not enough to call an id wrong: discovery can\n * be stale or return no list at all. What settles it is\n * whether *another* runner listed the id. So:\n *\n * - listed for this runner → keep;\n * - listed for no runner → unknown, and the user said it explicitly, so keep it\n * and let the runner validate last (the same call `resolveAssignment` makes);\n * - listed only for other runners → provably not this runner's, so drop it.\n *\n * When that leaves nothing, the whole allowlist was about some other runner\n * (a settings file written before a surface scoped its picker to one). No\n * restriction is the only safe reading — the alternative is handing the planner\n * a list of models that cannot run.\n */\nexport function effectiveAllowlist(\n allowlist: string[] | undefined,\n runner: string,\n modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>> | undefined,\n): string[] | undefined {\n if (!allowlist || allowlist.length === 0) return undefined;\n if (!modelsByRunner) return allowlist;\n\n const own = new Set((modelsByRunner[runner] ?? []).map((m) => m.modelId));\n const elsewhere = new Set<string>();\n for (const [other, models] of Object.entries(modelsByRunner)) {\n if (other === runner) continue;\n for (const m of models ?? []) elsewhere.add(m.modelId);\n }\n\n const kept = allowlist.filter((id) => own.has(id) || !elsewhere.has(id));\n return kept.length > 0 ? kept : undefined;\n}\n\nexport function filterModelsForPrompt(\n modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>,\n perRunnerAllowlist: Partial<Record<RunnerId, string[]>>,\n): Partial<Record<RunnerId, DiscoveredModel[]>> {\n const result: Partial<Record<RunnerId, DiscoveredModel[]>> = {};\n for (const [runner, models] of Object.entries(modelsByRunner)) {\n if (!models) continue;\n const allowed = effectiveAllowlist(perRunnerAllowlist[runner], runner, modelsByRunner);\n if (!allowed) {\n result[runner] = models;\n continue;\n }\n const allowedSet = new Set(allowed);\n const intersection = models.filter((m) => allowedSet.has(m.modelId));\n // A set allowlist is a hard constraint: the planner must never see models\n // outside it. An allowed id no runner has listed is one discovery doesn't\n // cover, so synthesize an entry rather than leaking the full list.\n const covered = new Set(intersection.map((m) => m.modelId));\n const missing = allowed\n .filter((id) => !covered.has(id))\n .map((id) => ({ modelId: id, modelLabel: id.split('/').pop() || id, variants: [] }));\n result[runner] = [...intersection, ...missing];\n }\n return result;\n}\n\n/** Canonical low→high ordering of the well-known effort ladder. */\nconst EFFORT_LADDER = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'];\n/** Runner-specific spellings mapped onto the ladder. */\nconst EFFORT_ALIASES: Record<string, string> = { disabled: 'none', adaptive: 'medium' };\n\n/**\n * The one rule for an effort and a model: the effort a task keeps on a model,\n * whether it was written with it or carried from the model before. Kept where\n * the model offers it; otherwise mapped to the nearest rung the model\n * supports (so a Claude-style \"xhigh\" on a low/medium/high model becomes\n * \"high\"); undefined — the runner's own default — when no mapping exists or\n * the model offers no levels, which is how a model discovery does not list\n * reads (`variants` empty). Core's resolver and every surface's model picker\n * call it, so a model change lands on the same effort wherever it is made.\n */\nexport function clampThinkingEffort(\n effort: string | undefined,\n variants: { id: string }[],\n): string | undefined {\n if (!effort) return undefined;\n if (variants.length === 0) return undefined;\n const ids = new Set(variants.map((v) => v.id));\n if (ids.has(effort)) return effort;\n const canon = EFFORT_ALIASES[effort] ?? effort;\n if (ids.has(canon)) return canon;\n const idx = EFFORT_LADDER.indexOf(canon);\n if (idx < 0) return undefined;\n for (let d = 1; d < EFFORT_LADDER.length; d++) {\n const lower = EFFORT_LADDER[idx - d];\n if (lower && ids.has(lower)) return lower;\n const higher = EFFORT_LADDER[idx + d];\n if (higher && ids.has(higher)) return higher;\n }\n return undefined;\n}\n","import type { DiscoveredModel, RunnerId, Task, TaskModelAssignment } from '../models/Task';\nimport { clampThinkingEffort, effectiveAllowlist } from './ModelAllowlistResolver';\nimport { plannedMode, readOnlyMode, readOnlyRunners, taggedReadOnlyMode, unavailableModeReason, type RunnerModeInfo } from './ModeResolver';\n\n/**\n * Who writes a task's assignment. It decides what a model the writer names\n * meets where the task's runner does not offer it or the allowlist excludes it\n * (ADR-0003), which runners it may put a task on, and whether a live runner's\n * hold applies (ADR-0041):\n *\n * - `plan`: a whole plan the planner submitted, and every planner task as it\n * lands. A stray model is replaced, and the planner is told what moved.\n * - `edit`: the planner's edit. A stray model is refused, which the planner\n * answers in the same reply.\n * - `user`: a user's direct edit. A stray model is refused, with the reason.\n * - `userAdd`: a task the user adds. A stray model is replaced, and the user\n * is told what moved.\n * - `runner`: a switch made with the runner's own `/model`, written as the\n * user's edit. The runner already runs it, so neither the allowlist nor the\n * live runner's hold applies.\n */\nexport type AssignmentWriter = 'plan' | 'edit' | 'user' | 'userAdd' | 'runner';\n\n/**\n * What an assignment is resolved against: the discovered models, the manifest\n * modes and the allowlist, as the session holds them when the write lands —\n * the same catalog the planner's per-turn block shows, so nothing is refused\n * that the planner was never told about.\n */\nexport interface AssignmentCatalog {\n /** A runner absent, or listed with nothing, has models discovery could not find, not none. */\n modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>;\n /** Absent when no runner's modes are known: no mode is then derived. */\n runnerModes?: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>;\n /** Raw (unfiltered) allowlist, keyed by runner; read through {@link effectiveAllowlist}. */\n perRunnerAllowlist?: Partial<Record<RunnerId, string[]>>;\n /** Full (true) or Guarded: the mode a derived task lands on unless it is read-only (ADR-0001). Full when absent. */\n autonomousDefault?: boolean;\n /**\n * Whether a task's runner is live: an attempt of it is claimed, running,\n * quiet or waiting on someone. Its model, effort and runner are then that\n * runner's (ADR-0041): the process was started on them, and only the\n * runner's own command moves them. Absent means none is.\n */\n liveRunner?: (taskId: string) => boolean;\n}\n\n/** What a runner offers a task, as discovery and its manifest have it. */\nexport interface RunnerCatalog {\n models: DiscoveredModel[];\n modes: RunnerModeInfo[];\n}\n\n/** A change the resolver made to what was written, named so it is never silent (ADR-0001). */\nexport interface AssignmentAdjustment {\n taskId: string;\n title: string;\n field: 'assignedModel' | 'thinkingEffort';\n from: string | null;\n to: string | null;\n}\n\nexport interface AssignmentWrite {\n writer: AssignmentWriter;\n /** The task as written: the plan's task with the edit laid over it, or a new task. Its subtasks are not read. */\n task: Task;\n /** The task as the plan holds it; absent for a new task. */\n before?: Task;\n /**\n * What a new task is made from — the task a split part or merge starts\n * as. Its runner and model are read as `before`'s are: a part moved to\n * another runner carries a model chosen for the old one.\n */\n from?: Pick<Task, 'assignedRunner' | 'assignedModel' | 'taskMode'>;\n /** The planner's answer to read-only or not (ADR-0001). Absent, the task stays as it was. */\n readOnly?: boolean;\n catalog: AssignmentCatalog;\n}\n\nexport type AssignmentOutcome =\n | { ok: true; task: Task; adjustments: AssignmentAdjustment[] }\n | { ok: false; field: 'assignedRunner' | 'assignedModel' | 'taskMode'; error: string };\n\n/**\n * Why a task's model or effort cannot change while its runner is live, for\n * every writer but the runner itself. The plan follows a switch made with the\n * runner's own command instead (ADR-0041), so that is offered beside stopping it.\n */\nexport function liveModelRefusal(task: Pick<Task, 'title'>): string {\n return `\"${task.title}\" has a live runner, so its model and effort cannot change now: stop the task first, or use the runner's own /model`;\n}\n\n/**\n * Why a task cannot move to another runner while its runner is live: the move\n * re-derives the model from the new runner, under the same hold as\n * {@link liveModelRefusal}, and no runner's own command can make it.\n */\nexport function liveRunnerRefusal(task: Pick<Task, 'title'>): string {\n return `\"${task.title}\" has a live runner, so its runner cannot change now: stop the task first`;\n}\n\n/**\n * The runners a writer may put a task on (ADR-0003, V1): a submitted plan the\n * runners enabled now; a planner edit the plan's own as well, so an edit is\n * checked as it is made and as it lands against one set; a user any runner.\n * Undefined means any.\n */\nexport function assignableRunners(writer: AssignmentWriter, enabled: readonly RunnerId[], planRunners: readonly RunnerId[]): RunnerId[] | undefined {\n if (writer === 'plan') return [...enabled];\n if (writer === 'edit') return [...new Set([...planRunners, ...enabled])];\n return undefined;\n}\n\nfunction sameAssignment(a: TaskModelAssignment | undefined, b: TaskModelAssignment | undefined): boolean {\n return a?.modelId === b?.modelId && a?.thinkingEffort === b?.thinkingEffort;\n}\n\n/** Whether `mode` is the mode `runner`'s manifest tags read-only. */\nfunction readOnlyOn(mode: string | undefined, runner: RunnerId, catalog: AssignmentCatalog): boolean {\n return mode !== undefined && mode === taggedReadOnlyMode(catalog.runnerModes?.[runner]);\n}\n\n/**\n * The task's mode as written. The writer's own pick stands where the runner\n * offers it; otherwise a task the writer creates, moves, turns into an AI task\n * or says read-only or not of gets the mode the planner path gives it\n * (ADR-0001's fill-in table): its read-only mode, else the mode it has when\n * the runner offers it, else the runner's mode for the level. Silence keeps\n * what is, so a mode the user picked by hand survives every other edit.\n */\nfunction modeFor(write: AssignmentWrite, task: Task, moved: boolean): { ok: true; mode: string | undefined } | { ok: false; error: string } {\n const { writer, before, catalog } = write;\n const prior = before ?? write.from;\n const runner = task.assignedRunner;\n const modes = catalog.runnerModes?.[runner];\n const picked = (writer === 'user' || writer === 'userAdd') && task.taskMode !== undefined && task.taskMode !== prior?.taskMode;\n if (picked) {\n if (modes && modes.length > 0 && !modes.some((m) => m.id === task.taskMode)) {\n return { ok: false, error: `Runner \"${runner}\" has no task mode \"${task.taskMode}\": valid modes: ${modes.map((m) => m.id).join(', ')}` };\n }\n const unavailable = unavailableModeReason(modes, task.taskMode);\n if (unavailable) return { ok: false, error: `Mode \"${task.taskMode}\" of runner \"${runner}\" is unavailable on this host: ${unavailable}` };\n return { ok: true, mode: task.taskMode };\n }\n const becameAi = before?.type === 'user';\n if (writer === 'runner' || !catalog.runnerModes || (before && !moved && !becameAi && write.readOnly === undefined)) return { ok: true, mode: task.taskMode };\n // A move carries the old runner's mode id, which says read-only only on that runner.\n const carried = prior !== undefined && task.taskMode === prior.taskMode && readOnlyOn(prior.taskMode, prior.assignedRunner, catalog);\n const readOnly = write.readOnly ?? (carried || readOnlyOn(task.taskMode, runner, catalog));\n if (readOnly && !readOnlyMode(modes) && writer !== 'plan' && writer !== 'edit') {\n const reason = unavailableModeReason(modes, taggedReadOnlyMode(modes));\n const able = readOnlyRunners(catalog.runnerModes).filter((r) => r !== runner);\n return {\n ok: false,\n error: `\"${task.title}\" is read-only, and runner \"${runner}\" has no read-only mode${reason ? ' available on this host' : ''}: give it another mode first${able.length > 0 ? `, or move it to one of: ${able.join(', ')}` : ''}${reason ? `. ${reason}` : ''}`,\n };\n }\n const mode = plannedMode(runner, catalog.runnerModes, { readOnly, current: task.taskMode }, catalog.autonomousDefault ?? true);\n return mode.ok ? { ok: true, mode: mode.mode ?? task.taskMode } : mode;\n}\n\n/**\n * Resolve what one task runs on — its runner, model, thinking effort and mode\n * — for whichever writer wrote it. Every writer calls this, so they cannot\n * disagree. Returns the task with what it resolved, and each change made to\n * what was written, named; or a refusal.\n *\n * - **The live runner's hold.** While its runner is live a task's runner,\n * model and effort are that runner's (ADR-0041): any other writer is\n * refused a change, a model cleared as much as one replaced.\n * - **A model is its runner's.** A model the writer left alone stays, even off\n * the allowlist: the allowlist shapes what is written, not a model the user\n * switched to (ADR-0043). A model the task carries from another runner — a\n * move, or a part made from a task on another one — stays only where its new\n * runner lists it, or nothing is known against it, and the allowlist admits\n * it. A model the writer names is held to the runner and the allowlist as\n * its {@link AssignmentWriter} row says: the runner already runs it\n * (`runner`), or it is refused (`edit`, `user`), or replaced (`plan`,\n * `userAdd`) when only another runner lists it (it provably cannot run here)\n * or the allowlist excludes it. A model listed by no runner is not one any\n * runner disowns, so it stands, and the runner validates last.\n * - **Filling a model.** A missing, carried-off or replaced model is refilled\n * from the runner: the allowlist's first model the runner lists, else the\n * allowlist's first, else the runner's own first, else none, which the\n * runner fills with its own default.\n * - **Effort follows its model.** Whenever the model is listed, its effort is\n * held to what it offers ({@link clampThinkingEffort}, the one mapping rule:\n * the nearest level the model has, none when it has none); a model put in\n * here that discovery does not list gets none. A model discovery does not\n * list keeps the effort written with it, which the runner validates.\n * - **The mode** is the writer's pick where it has one, else derived as\n * {@link modeFor} says.\n *\n * Which runners a writer may use is the plan's rule ({@link assignableRunners},\n * `checkPlanInvariants`); this never moves a task to another runner.\n */\nexport function resolveAssignment(write: AssignmentWrite): AssignmentOutcome {\n const { writer, before, catalog } = write;\n const written = write.task;\n const prior = before ?? write.from;\n const moved = prior !== undefined && prior.assignedRunner !== written.assignedRunner;\n const title = (before ?? written).title;\n\n const live = writer !== 'runner' && !!before && !!catalog.liveRunner?.(before.id);\n if (live && moved) return { ok: false, field: 'assignedRunner', error: liveRunnerRefusal({ title }) };\n if (live && !sameAssignment(before?.assignedModel, written.assignedModel)) return { ok: false, field: 'assignedModel', error: liveModelRefusal({ title }) };\n if (written.type === 'user') return { ok: true, task: written, adjustments: [] };\n\n const mode = modeFor(write, written, moved);\n if (!mode.ok) return { ok: false, field: 'taskMode', error: mode.error };\n\n // A live runner holds its model and effort as they are, even where its model's levels have changed since.\n const model: ModelResult = live ? { ok: true, assignment: written.assignedModel } : modelFor(write, moved);\n if (!model.ok) return { ok: false, field: 'assignedModel', error: model.error };\n\n const task = model.assignment === written.assignedModel && mode.mode === written.taskMode\n ? written\n : { ...written, assignedModel: model.assignment, taskMode: mode.mode };\n const adjustments: AssignmentAdjustment[] = [];\n const note = (field: AssignmentAdjustment['field'], from: string | undefined, to: string | undefined) => {\n if ((from ?? null) !== (to ?? null)) adjustments.push({ taskId: written.id, title: written.title, field, from: from ?? null, to: to ?? null });\n };\n note('assignedModel', written.assignedModel?.modelId, task.assignedModel?.modelId);\n note('thinkingEffort', written.assignedModel?.thinkingEffort, task.assignedModel?.thinkingEffort);\n return { ok: true, task, adjustments };\n}\n\ntype ModelResult = { ok: true; assignment: TaskModelAssignment | undefined } | { ok: false; error: string };\n\n/** The model half of {@link resolveAssignment}. */\nfunction modelFor(write: AssignmentWrite, moved: boolean): ModelResult {\n const { writer, before, catalog } = write;\n const prior = before ?? write.from;\n const written = write.task.assignedModel;\n const runner = write.task.assignedRunner;\n const models = catalog.modelsByRunner;\n const own = models[runner] ?? [];\n const listing = (id: string) => own.find((m) => m.modelId === id);\n // A model the task ran with on another runner is evidence that runner lists it, which the allowlist is read with (R1).\n const carriedFrom = moved && written && prior?.assignedModel?.modelId === written.modelId ? prior.assignedRunner : undefined;\n const seen = carriedFrom && written && !models[carriedFrom]?.some((m) => m.modelId === written.modelId)\n ? { ...models, [carriedFrom]: [...(models[carriedFrom] ?? []), { modelId: written.modelId, modelLabel: written.modelLabel, variants: [] }] }\n : models;\n const allowed = effectiveAllowlist(catalog.perRunnerAllowlist?.[runner], runner, seen);\n const allows = (id: string) => !allowed || allowed.includes(id);\n\n // Held to what its model offers, unless that model is one discovery does not list.\n const settled = (assignment: TaskModelAssignment): TaskModelAssignment => {\n const known = listing(assignment.modelId);\n if (!known) return assignment;\n const thinkingEffort = clampThinkingEffort(assignment.thinkingEffort, known.variants);\n const availableVariants = known.variants.map((v) => v.id);\n if (thinkingEffort === assignment.thinkingEffort && assignment.availableVariants?.join() === availableVariants.join()) return assignment;\n const next: TaskModelAssignment = { ...assignment, availableVariants };\n if (thinkingEffort === undefined) delete next.thinkingEffort;\n else next.thinkingEffort = thinkingEffort;\n return next;\n };\n // The allowlist's order is its priority, among the models the runner lists\n // first; an allowlisted id discovery does not list still beats one the user excluded (R1).\n const refill = (effort: string | undefined): TaskModelAssignment | undefined => {\n const id = allowed?.find((a) => listing(a)) ?? allowed?.[0] ?? own[0]?.modelId;\n if (id === undefined) return undefined;\n const known = listing(id);\n const thinkingEffort = clampThinkingEffort(effort, known?.variants ?? []);\n return {\n modelId: id,\n modelLabel: known?.modelLabel ?? id,\n ...(thinkingEffort !== undefined ? { thinkingEffort } : {}),\n ...(known ? { availableVariants: known.variants.map((v) => v.id) } : {}),\n };\n };\n\n if (!written) return { ok: true, assignment: writer === 'runner' ? undefined : refill(undefined) };\n if (writer === 'runner') return { ok: true, assignment: settled(written) };\n\n const id = written.modelId;\n const same = prior?.assignedModel?.modelId === id;\n if (before && same && !moved) return { ok: true, assignment: settled(written) };\n\n const listedElsewhere = Object.entries(models).some(([other, list]) => other !== runner && list?.some((m) => m.modelId === id));\n if (same) {\n // Carried from a task on another runner, it was chosen for that one: it\n // needs this one's listing, or nothing known against it.\n const fits = !moved || !!listing(id) || (own.length === 0 && !listedElsewhere);\n return { ok: true, assignment: fits && allows(id) ? settled(written) : refill(written.thinkingEffort) };\n }\n\n if (writer === 'edit' || writer === 'user') {\n // Allowlisted ids are kept even unlisted (ADR-0003, R1); an empty list is discovery that found nothing.\n const offered = own.length === 0 || !!listing(id) || !!allowed?.includes(id);\n if (!offered) return { ok: false, error: `Runner \"${runner}\" does not offer model \"${id}\"` };\n if (!allows(id)) return { ok: false, error: `Model \"${id}\" is excluded by the allowlist for runner \"${runner}\"` };\n return { ok: true, assignment: settled(written) };\n }\n // Only another runner lists it: it provably cannot run here.\n const disowned = !listing(id) && listedElsewhere;\n return { ok: true, assignment: disowned || !allows(id) ? refill(written.thinkingEffort) : settled(written) };\n}\n\n/**\n * The planner's tasks as they land, subtasks included: each resolved as the\n * planner's ({@link resolveAssignment}, `plan`) against the catalog read now\n * and against the plan's task of the same id, so a model or allowlist that\n * changed since the write was checked is applied and named. A task the\n * planner left alone keeps its model; one whose runner is live keeps all of\n * its assignment. Modes were decided when the task was written, and are not\n * revisited. Nothing is refused here: a write the catalog no longer allows is\n * kept as it was written, and the runner validates last.\n */\nexport function resolveAssignments(\n tasks: readonly Task[],\n planned: readonly Task[],\n catalog: AssignmentCatalog,\n): { tasks: Task[]; adjustments: AssignmentAdjustment[] } {\n const prior = new Map<string, Task>();\n const index = (list: readonly Task[]) => list.forEach((t) => { prior.set(t.id, t); index(t.subtasks ?? []); });\n index(planned);\n const models = { ...catalog, runnerModes: undefined };\n const adjustments: AssignmentAdjustment[] = [];\n const walk = (list: readonly Task[]): Task[] => list.map((task) => {\n const outcome = resolveAssignment({ writer: 'plan', task, before: prior.get(task.id), catalog: models });\n const resolved = outcome.ok ? outcome.task : task;\n if (outcome.ok) adjustments.push(...outcome.adjustments);\n return { ...resolved, subtasks: walk(task.subtasks ?? []) };\n });\n return { tasks: walk(tasks), adjustments };\n}\n","import { dependencyTargetErrors, findDependencyCycle } from './PlanTree';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Task, RunnerId, TaskModelAssignment, DEFAULT_RUNNERS, createTask, flattenTasksWithParents, inheritedSkills, skillNames } from '../models/Task';\nimport { taggedReadOnlyMode, type RunnerModeInfo } from './ModeResolver';\nimport { resolveAssignment, type AssignmentAdjustment, type AssignmentCatalog } from './TaskAssignment';\n\n/** One reason a planned task was refused, pinned to the task and field it is about. */\nexport interface PlanTaskError {\n /** Absent when the envelope itself is wrong rather than one of its tasks. */\n taskId?: string;\n field: string;\n message: string;\n}\n\n/** What a planned task may be given: the runners it may run on and their modes. */\nexport interface IntakeCatalog {\n runners: readonly RunnerId[];\n /** Absent when no runner's modes are known: the mode is then guessed (ADR-0031). */\n runnerModes?: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>;\n autonomousDefault?: boolean;\n /**\n * An edit's assignments are resolved against the catalog as it is planned,\n * and a model it names that the runner does not offer is refused; a\n * submitted plan's models are resolved as it lands instead (ADR-0003).\n */\n models?: AssignmentCatalog;\n}\n\n/** Where a planned task lands, which decides what it inherits. */\nexport interface Placement {\n /** The task it is a subtask of. */\n parent?: Pick<Task, 'title' | 'sliceType' | 'autonomy'> & { readOnly: boolean };\n /**\n * A task made from others — a merge, or a split part: what the planner\n * leaves out comes from these fields, and it stays read-only when they were.\n */\n from?: { fields: Partial<Task>; readOnly: boolean };\n /**\n * An edit's tasks get ids of their own, so a planner cannot name one after\n * a task already in the plan. A submitted plan keeps the planner's, which\n * its dependencies are written against; one it left out is made up.\n */\n freshIds?: boolean;\n /** Where a made-up id comes from: a new one each time, unless the caller keeps them. */\n newId?: () => string;\n}\n\nexport interface PlannedTask {\n task: Task;\n /** Rules the task, or one of its subtasks, breaks on its own. */\n errors: PlanTaskError[];\n /**\n * What the planner asked for that is not its to set — a mode, or read-only\n * on a manual task (ADR-0031). A whole plan lands without it; an edit is\n * refused.\n */\n ignored: PlanTaskError[];\n /** The planner's id for each task of the tree that was given a fresh one. */\n renamed: Map<string, string>;\n /** What the tree's assignments were adjusted to beyond what the planner wrote, for the planner to be told. */\n adjustments: AssignmentAdjustment[];\n}\n\n/** The planner names no mode (ADR-0031): it says read-only, and the mode follows. */\nexport const MODE_NOT_THE_PLANNERS = '\"taskMode\" is the user\\'s to choose: set \"readOnly\": true on a task that must not change files, and leave it out otherwise';\n\n/** A manual task runs in no mode, so it cannot be read-only. */\nexport const MANUAL_NOT_READ_ONLY = '\"readOnly\" is for AI tasks: a manual task runs in no mode';\n\n/** An ops task acts — runs commands — in the session's mode (ADR-0020); a read-only mode refuses that. */\nexport const OPS_NOT_READ_ONLY = '\"readOnly\" is not for an ops task: it runs commands, in the session\\'s mode, and a read-only mode refuses them';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction text(value: unknown): string | undefined {\n return typeof value === 'string' || typeof value === 'number' ? String(value) : undefined;\n}\n\n/**\n * Why a task may not be an ops task (ADR-0020), or null when it may: the flag\n * decides where the task runs, and a manual task runs nowhere, a subtask\n * where its parent does.\n */\nexport function opsPlacementRefusal(type: Task['type'], isSubtask: boolean): string | null {\n if (type !== 'ai') return 'is a manual task: only an AI task can be an ops task';\n if (isSubtask) return 'is a subtask, which runs with its parent: only a top-level task can be an ops task';\n return null;\n}\n\n/** How an error names a task, so the planner can find the object it wrote. */\nexport function taskWhere(title: string, parentTitle?: string): string {\n return parentTitle !== undefined ? `subtask \"${title}\" of \"${parentTitle}\"` : `Task \"${title}\"`;\n}\n\n/**\n * What each planned task asked to attach, as the planner wrote it. Kept beside\n * the task rather than on it: {@link skillNames} drops a name no skill could\n * have, and the plan's skill check still owes a warning for it. A copy of the\n * task (assignment coercion) does not carry it, so check what was planned.\n */\nconst skillsAsked = new WeakMap<Task, unknown[]>();\n\n/** The `skills` a planned task was submitted with, before normalization; undefined for a task not planned here. */\nexport function requestedSkills(task: Task): readonly unknown[] | undefined {\n return skillsAsked.get(task);\n}\n\n/** The model as a task stores it; a top-level `thinkingEffort` is the model's, as the effort has one home. */\nfunction modelOf(raw: Record<string, unknown>, from: TaskModelAssignment | undefined): TaskModelAssignment | undefined {\n const effort = text(raw.thinkingEffort);\n if (isRecord(raw.assignedModel)) {\n const m = raw.assignedModel;\n return {\n modelId: text(m.modelId) ?? '',\n modelLabel: text(m.modelLabel) ?? '',\n thinkingEffort: text(m.thinkingEffort) ?? effort,\n };\n }\n return from && effort !== undefined ? { ...from, thinkingEffort: effort } : from;\n}\n\n/**\n * Build a task from what a planner wrote, by whichever route it arrived: a\n * submitted plan, or an edit that adds, merges or splits. Every fallback a\n * planner's omission meets is here, so the routes cannot drift apart:\n *\n * - the runner is the one named, else the task's it is made from, else the\n * first one allowed — never a substitute for one named but unknown, which\n * {@link checkPlanInvariants} refuses;\n * - an AI task's prompt is its own, else its description, else its title, so\n * it is never unschedulable;\n * - the assignment — model, effort, and the mode decided from read-only or\n * not, never named (ADR-0031) — is the resolver's (`resolveAssignment`),\n * as it is for every writer: a model a merge or split carries is read as\n * what the task was made from, so a part moved to another runner gets that\n * runner's;\n * - an AI subtask with no slice classification takes its parent's: an `ai`\n * sub-step of an AFK slice is AFK. Not a blanket default, which would strip\n * a human gate the planner intended; a manual subtask must still say HITL.\n */\nexport function plannedTask(spec: unknown, catalog: IntakeCatalog, at: Placement = {}): PlannedTask {\n const raw = isRecord(spec) ? spec : {};\n const { parent, from, freshIds, newId = uuidv4 } = at;\n const base = from?.fields ?? {};\n const errors: PlanTaskError[] = [];\n const ignored: PlanTaskError[] = [];\n const renamed = new Map<string, string>();\n const adjustments: AssignmentAdjustment[] = [];\n\n const title = text(raw.title) ?? '';\n const where = taskWhere(title, parent?.title);\n const plannerId = text(raw.id);\n const id = freshIds || !plannerId ? newId() : plannerId;\n if (freshIds && plannerId) renamed.set(plannerId, id);\n\n const type: Task['type'] = raw.type === 'user' || raw.type === 'ai' ? raw.type : base.type ?? 'ai';\n const ai = type === 'ai';\n const assignedRunner = text(raw.assignedRunner) ?? base.assignedRunner ?? catalog.runners[0] ?? DEFAULT_RUNNERS[0];\n const description = text(raw.description) ?? base.description ?? title;\n\n // A manual task stores no ops flag, so this is the one place it can still be seen.\n if (raw.ops === true && !ai) errors.push({ taskId: id, field: 'ops', message: `${where} ${opsPlacementRefusal(type, !!parent)}` });\n if (raw.taskMode !== undefined) ignored.push({ taskId: id, field: 'taskMode', message: `${where}: ${MODE_NOT_THE_PLANNERS}` });\n if (!ai && raw.readOnly === true) ignored.push({ taskId: id, field: 'readOnly', message: `${where}: ${MANUAL_NOT_READ_ONLY}` });\n const ops = raw.ops !== undefined ? raw.ops === true : base.ops;\n if (ai && ops && raw.readOnly === true) ignored.push({ taskId: id, field: 'readOnly', message: `${where}: ${OPS_NOT_READ_ONLY}` });\n // A read-only task's subtasks run as tasks of their own, so they would\n // otherwise get write access the planner said the work must not have.\n const asked = typeof raw.readOnly === 'boolean' ? raw.readOnly : undefined;\n const readOnly = ai && !ops && (asked ?? from?.readOnly ?? parent?.readOnly ?? false);\n\n const assignedModel = ai ? modelOf(raw, base.assignedModel) : undefined;\n const slice = (value: unknown): 'AFK' | 'HITL' | undefined => (value === 'AFK' || value === 'HITL' ? value : undefined);\n // Explicit child values always win; inheritance only fills a gap, and only for `ai` tasks.\n const autonomy = ai ? slice(raw.autonomy) ?? base.autonomy ?? parent?.autonomy : undefined;\n const sliceType = slice(raw.sliceType) ?? base.sliceType ?? (ai ? parent?.sliceType : undefined);\n\n const subtasks: Task[] = [];\n if (Array.isArray(raw.subtasks)) {\n for (const s of raw.subtasks) {\n const child = plannedTask(s, catalog, { parent: { title, sliceType, autonomy, readOnly }, freshIds, newId });\n subtasks.push(child.task);\n errors.push(...child.errors);\n ignored.push(...child.ignored);\n adjustments.push(...child.adjustments);\n for (const [was, now] of child.renamed) renamed.set(was, now);\n }\n }\n\n const fields = {\n id,\n order: Number(raw.order ?? 0),\n title,\n description,\n type,\n dependencies: Array.isArray(raw.dependencies) ? raw.dependencies.map(String) : base.dependencies ?? [],\n prompt: text(raw.prompt) || base.prompt || (ai ? description || title || undefined : undefined),\n userSteps: Array.isArray(raw.userSteps)\n ? raw.userSteps.map((s) => (isRecord(s) ? s : {})).map((s) => ({ order: Number(s.order ?? 0), instruction: String(s.instruction ?? ''), completed: false }))\n : base.userSteps,\n subtasks,\n assignedModel,\n assignedRunner,\n autonomy,\n sliceType,\n userStoriesCovered: Array.isArray(raw.userStoriesCovered) ? raw.userStoriesCovered.map(String) : base.userStoriesCovered,\n ops,\n skills: from ? inheritedSkills(raw.skills, base.skills) : skillNames(raw.skills),\n };\n let taskMode = base.taskMode;\n let landedModel = assignedModel;\n if (ai && catalog.runners.includes(assignedRunner)) {\n const resolved = resolveAssignment({\n writer: catalog.models ? 'edit' : 'plan',\n task: { ...createTask(fields), taskMode },\n from: from ? { assignedRunner: base.assignedRunner ?? assignedRunner, assignedModel: base.assignedModel, taskMode: base.taskMode } : undefined,\n readOnly,\n catalog: { modelsByRunner: {}, ...catalog.models, runnerModes: catalog.runnerModes, autonomousDefault: catalog.autonomousDefault },\n });\n if (!resolved.ok) {\n // The planner names no mode: one it cannot have is about the read-only answer it gave (ADR-0031).\n errors.push({ taskId: id, field: resolved.field === 'taskMode' ? 'readOnly' : resolved.field, message: `${where}: ${resolved.error}` });\n } else {\n taskMode = resolved.task.taskMode;\n landedModel = resolved.task.assignedModel;\n adjustments.push(...resolved.adjustments);\n }\n }\n // With no runner modes to read, `plan` is the guess that keeps a read-only task from writing.\n if (taskMode === undefined && readOnly && !catalog.runnerModes) taskMode = 'plan';\n\n const task = createTask({ ...fields, assignedModel: landedModel, taskMode });\n if (Array.isArray(raw.skills)) skillsAsked.set(task, raw.skills);\n return { task, errors, ignored, renamed, adjustments };\n}\n\n/**\n * The rules a plan's tasks are held to together, subtasks included, whichever\n * route they arrived by and whoever wrote them, the planner or the user.\n * `runners` are the ones the writer may put a task on (`assignableRunners`),\n * any when undefined; with `runnerModes`, an ops task is held off its\n * runner's read-only mode.\n */\nexport function checkPlanInvariants(\n tasks: readonly Task[],\n runners: readonly RunnerId[] | undefined,\n runnerModes?: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>,\n): PlanTaskError[] {\n const rows = flattenTasksWithParents(tasks);\n const errors: PlanTaskError[] = [];\n const seen = new Set<string>();\n for (const { task: t, parent } of rows) {\n const where = taskWhere(t.title, parent?.title);\n const fail = (field: string, message: string) => errors.push({ taskId: t.id, field, message });\n if (seen.has(t.id)) fail('id', `${where} has id \"${t.id}\", which another task in the plan has too: every task, subtasks included, needs an id of its own`);\n seen.add(t.id);\n if (t.autonomy === 'AFK' && t.userSteps && t.userSteps.length > 0) {\n fail('userSteps', `${where} has autonomy \"AFK\" but contains userSteps. AFK tasks must not have user touchpoints.`);\n }\n if (t.type === 'user' && t.sliceType && t.sliceType !== 'HITL') {\n fail('sliceType', `User ${where} has sliceType \"${t.sliceType}\". User tasks must have sliceType \"HITL\".`);\n }\n const opsRefused = t.ops ? opsPlacementRefusal(t.type, parent !== null) : null;\n if (opsRefused) fail('ops', `${where} ${opsRefused}`);\n if (t.ops && t.taskMode !== undefined && t.taskMode === taggedReadOnlyMode(runnerModes?.[t.assignedRunner])) {\n fail('taskMode', `${where} is an ops task, which runs commands, so it cannot run in its runner's read-only mode`);\n }\n if (runners && t.type !== 'user' && !runners.includes(t.assignedRunner)) {\n fail('assignedRunner', `${where} has invalid assignedRunner \"${t.assignedRunner}\". Expected one of: ${runners.join(', ')}`);\n }\n }\n for (const { task: t, parent } of rows) {\n // Self-dependencies are reported with the cycle below, including its full path.\n for (const error of dependencyTargetErrors(tasks, t.id, t.dependencies.filter((id) => id !== t.id))) {\n errors.push({ taskId: t.id, field: 'dependencies', message: `${taskWhere(t.title, parent?.title)}: ${error}` });\n }\n }\n const cycle = findCycle(rows.map((r) => r.task));\n // Pinned to a task on the cycle: a plan whose only fault is a rule a task\n // breaks is worth naming back to the planner, not re-asking for as JSON.\n if (cycle) errors.push({ taskId: cycle[0].id, field: 'dependencies', message: `Dependency cycle detected: ${[...cycle, cycle[0]].map((t) => `\"${t.title}\"`).join(' → ')}` });\n return errors;\n}\n\n/** The tasks on one dependency cycle, in dependency order, or null when there is none. A self-dependency is a cycle of one. */\nexport function findCycle(tasks: readonly Task[]): Task[] | null {\n return findDependencyCycle(tasks);\n}\n\n/** The title of a task on a dependency cycle, or null when there is none. */\nexport function detectCycle(tasks: readonly Task[]): string | null {\n return findCycle(tasks)?.[0].title ?? null;\n}\n"],"mappings":"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","names":["text","reason","id"]}