@ordewell/core 0.4.22 → 0.5.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.
- package/dist/{ITerminalRunner-CuNYBnqK.d.mts → ITerminalRunner-DXlJop_f.d.mts} +26 -1
- package/dist/{ITerminalRunner-Da1gIAFX.d.ts → ITerminalRunner-nupxhtPb.d.ts} +26 -1
- package/dist/{ModeResolver-Ob-ihQbI.d.ts → ModeResolver-BBVktyiP.d.ts} +1 -1
- package/dist/{ModeResolver-mtIWrLXn.d.mts → ModeResolver-D1-2xJsE.d.mts} +1 -1
- package/dist/Task-CfTjRQeS.d.mts +586 -0
- package/dist/Task-CfTjRQeS.d.ts +586 -0
- package/dist/{chunk-DV3YIKRA.mjs → chunk-GWPIYDQW.mjs} +31 -7
- package/dist/chunk-GWPIYDQW.mjs.map +1 -0
- package/dist/chunk-HQICT7HQ.mjs +90 -0
- package/dist/chunk-HQICT7HQ.mjs.map +1 -0
- package/dist/{chunk-T4KGVP4N.mjs → chunk-JVMDEHRQ.mjs} +1 -1
- package/dist/chunk-JVMDEHRQ.mjs.map +1 -0
- package/dist/{chunk-MKUTJDT2.mjs → chunk-XWOUIA6A.mjs} +2 -2
- package/dist/index.d.mts +606 -107
- package/dist/index.d.ts +606 -107
- package/dist/index.js +3357 -1172
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3347 -1264
- package/dist/index.mjs.map +1 -1
- package/dist/order-labels.d.mts +1 -1
- package/dist/order-labels.d.ts +1 -1
- package/dist/{parsing-BWfpLch9.d.mts → parsing-BJECTMOD.d.mts} +2 -2
- package/dist/{parsing-BJoHbA-t.d.ts → parsing-Bv3g2Tu_.d.ts} +2 -2
- package/dist/parsing.d.mts +3 -3
- package/dist/parsing.d.ts +3 -3
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +2 -2
- package/dist/{plan-utils-s6VL8NDF.d.mts → plan-utils-B5oIZkuI.d.mts} +30 -5
- package/dist/{plan-utils-DJm2a7Zp.d.ts → plan-utils-Cy-RsBSV.d.ts} +30 -5
- package/dist/plan-utils.d.mts +3 -3
- package/dist/plan-utils.d.ts +3 -3
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +2 -2
- package/dist/testing.d.mts +81 -3
- package/dist/testing.d.ts +81 -3
- package/dist/testing.js +152 -0
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +136 -0
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/Task-4rDGr8Su.d.mts +0 -271
- package/dist/Task-4rDGr8Su.d.ts +0 -271
- package/dist/chunk-DV3YIKRA.mjs.map +0 -1
- package/dist/chunk-T4KGVP4N.mjs.map +0 -1
- /package/dist/{chunk-MKUTJDT2.mjs.map → chunk-XWOUIA6A.mjs.map} +0 -0
package/dist/testing.js.map
CHANGED
|
@@ -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 { ITerminalSession } from './interfaces/ITerminalRunner';\n\nexport function fakeConfig(overrides: Partial<IConfig> = {}): IConfig {\n return {\n aiProvider: 'openrouter',\n apiKey: 'sk-test',\n planningModel: 'test-model',\n enabledRunners: ['claude-code'],\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 approvalMode: 'ask',\n approvalPreApproved: [],\n setProviderModelLists: () => {},\n getProviderBaseUrl: () => '',\n getProviderApiKey: () => '',\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 FakeTerminalSession implements ITerminalSession {\n private outputCbs: Array<(text: string) => void> = [];\n private exitCbs: Array<(code: number) => void> = [];\n output = '';\n written: string[] = [];\n killed = false;\n\n constructor(public id = 's1', public taskId = 't1') {}\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 getOutput(): string { return this.output; }\n write(text: string): void { this.written.push(text); }\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 }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,SAAS,WAAW,YAA8B,CAAC,GAAY;AACpE,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,eAAe;AAAA,IACf,gBAAgB,CAAC,aAAa;AAAA,IAC9B,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,IAChB,cAAc;AAAA,IACd,qBAAqB,CAAC;AAAA,IACtB,uBAAuB,MAAM;AAAA,IAAC;AAAA,IAC9B,oBAAoB,MAAM;AAAA,IAC1B,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,sBAAN,MAAsD;AAAA,EAO3D,YAAmB,KAAK,MAAa,SAAS,MAAM;AAAjC;AAAkB;AAAA,EAAgB;AAAA,EAAlC;AAAA,EAAkB;AAAA,EAN7B,YAA2C,CAAC;AAAA,EAC5C,UAAyC,CAAC;AAAA,EAClD,SAAS;AAAA,EACT,UAAoB,CAAC;AAAA,EACrB,SAAS;AAAA,EAIT,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,YAAoB;AAAE,WAAO,KAAK;AAAA,EAAQ;AAAA,EAC1C,MAAM,MAAoB;AAAE,SAAK,QAAQ,KAAK,IAAI;AAAA,EAAG;AAAA,EAErD,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;AAAA,EACxC;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts","../src/services/isolationRecord.ts"],"sourcesContent":["import type { IConfig } from './interfaces/IConfig';\nimport type { IFileSystem, ToolOutcome } from './interfaces/IFileSystem';\nimport type { ITerminalSession } from './interfaces/ITerminalRunner';\nimport type {\n IsolationAvailability,\n IsolationHandoff,\n IsolationMergeResult,\n IsolationOutcome,\n IsolationRun,\n PreparedTask,\n IWorktreeIsolation,\n} from './interfaces/IWorktreeIsolation';\nimport type { Task } from './models/Task';\nimport { handoffOf, integrationBranchFor, SELF_REPO } from './services/isolationRecord';\n\nexport function fakeConfig(overrides: Partial<IConfig> = {}): IConfig {\n return {\n aiProvider: 'openrouter',\n apiKey: 'sk-test',\n planningModel: 'test-model',\n enabledRunners: ['claude-code'],\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 approvalMode: 'ask',\n approvalPreApproved: [],\n setProviderModelLists: () => {},\n getProviderBaseUrl: () => '',\n getProviderApiKey: () => '',\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 FakeTerminalSession implements ITerminalSession {\n private outputCbs: Array<(text: string) => void> = [];\n private exitCbs: Array<(code: number) => void> = [];\n output = '';\n written: string[] = [];\n killed = false;\n\n constructor(public id = 's1', public taskId = 't1') {}\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 getOutput(): string { return this.output; }\n write(text: string): void { this.written.push(text); }\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 }\n}\n\nexport type FakeIsolationCall =\n | { op: 'isActive'; workspaceRoot: string }\n | { op: 'stash'; workspaceRoot: string }\n | { op: 'startRun'; workspaceRoot: string }\n | { op: 'prepare'; taskId: string }\n | { op: 'integrate'; taskId: string }\n | { op: 'release'; taskId: string; keep: boolean }\n | { op: 'handoff' | 'pruneOrphans' | 'reviewDiff' | 'mergeIntoCheckedOut' }\n | { op: 'discard'; keepIntegration: boolean };\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.\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 /** What `mergeIntoCheckedOut` answers. */\n mergeResult: IsolationMergeResult = { outcome: 'merged' };\n /** What `startRun` shares and `prepare` copies, to exercise their notices. */\n shared: string[] = [];\n sharedRepos: string[] = [];\n copied: string[] = [];\n /** Set to make `startRun` throw, as git does when no repo of the group can be isolated. */\n startRunError: Error | null = null;\n /** Per task id; a task not listed integrates as `merged`. */\n outcomes = new Map<string, IsolationOutcome>();\n calls: FakeIsolationCall[] = [];\n private holds = 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' | '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 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] };\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 if (outcome === 'merged') delete record.conflictRepo;\n else record.conflictRepo = this.stopsIn.get(task.id) ?? changed[0] ?? SELF_REPO;\n return outcome;\n }\n\n async release(run: IsolationRun, taskId: string, opts: { keep: boolean }): Promise<void> {\n this.log({ op: 'release', taskId, keep: opts.keep });\n const record = run.tasks[taskId];\n if (!opts.keep) delete run.tasks[taskId];\n else if (record?.status === 'active') record.status = 'kept';\n }\n\n async handoff(run: IsolationRun): Promise<IsolationHandoff> {\n this.log({ op: 'handoff' });\n return handoffOf(run);\n }\n\n async pruneOrphans(): Promise<void> { this.log({ op: 'pruneOrphans' }); }\n async reviewDiff(): Promise<string> { this.log({ op: 'reviewDiff' }); return ''; }\n async mergeIntoCheckedOut(): Promise<IsolationMergeResult> { this.log({ op: 'mergeIntoCheckedOut' }); return this.mergeResult; }\n async discard(_run: IsolationRun, opts: { keepIntegration: boolean }): Promise<void> {\n this.log({ op: 'discard', keepIntegration: opts.keepIntegration });\n }\n}\n","import * as path from 'path';\nimport type {\n IsolationHandoff,\n IsolationLandedTask,\n IsolationRepo,\n IsolationRun,\n IsolationTaskRecord,\n IsolationTaskStatus,\n PlanIsolation,\n RepoGroupLayout,\n TaskIsolation,\n} from '../interfaces/IWorktreeIsolation';\n\n/** The path of the repo in a workspace that is itself the repository. */\nexport const SELF_REPO = '.';\n\nexport function integrationBranchFor(runId: string): string {\n return `ordewell/${runId}/integration`;\n}\n\nexport function repoRootOf(workspaceRoot: string, repoPath: string): string {\n return path.join(workspaceRoot, repoPath);\n}\n\n/** The group a run isolates, as the planner is told it. */\nexport function layoutOf(run: IsolationRun): RepoGroupLayout {\n return { repos: run.repos.map((r) => r.path), shared: [...run.shared] };\n}\n\n/** The run's integration branch for a one-line notice: every repo's has the same name. */\nexport function integrationBranchNameOf(run: IsolationRun): string {\n return [...new Set(run.repos.map((r) => r.integrationBranch))].join(', ');\n}\n\nconst ISOLATION_STATE: Record<IsolationTaskStatus, Exclude<TaskIsolation['state'], 'none'>> = {\n active: 'active',\n merged: 'integrated',\n conflict: 'conflict',\n kept: 'kept',\n failed: 'kept',\n};\n\nexport function taskIsolationOf(record: IsolationTaskRecord): TaskIsolation {\n return {\n state: ISOLATION_STATE[record.status],\n branch: record.branch,\n worktree: record.workspace,\n repos: Object.entries(record.repos).filter(([, r]) => r.changed).map(([repoPath]) => repoPath),\n ...(record.conflictRepo ? { conflictRepo: record.conflictRepo } : {}),\n };\n}\n\n/** What a run hands over: each repo's integration branch and base, and what landed, in plan order. */\nexport function handoffOf(run: IsolationRun): IsolationHandoff {\n const merged = Object.values(run.tasks)\n .filter((r) => r.status === 'merged')\n .sort((a, b) => a.order - b.order);\n const entry = (r: IsolationTaskRecord): IsolationLandedTask => ({ taskId: r.taskId, order: r.order, title: r.title });\n return {\n repos: run.repos.map((repo) => ({\n path: repo.path,\n integrationBranch: repo.integrationBranch,\n baseRef: repo.baseRef,\n landed: merged.filter((r) => r.repos[repo.path]?.changed).map(entry),\n })),\n landed: merged.map(entry),\n };\n}\n\n/** A task record as ADR-0013 persisted it, for one repository. */\nexport interface Adr0013TaskRecord {\n taskId: string;\n order: number;\n title: string;\n branch: string;\n worktree: string;\n status: IsolationTaskStatus;\n linked: string[];\n}\n\n/** A run as ADR-0013 persisted it (0.4.23): one repository, its refs on the run itself. */\nexport interface Adr0013IsolationRun {\n id: string;\n workspaceRoot: string;\n baseRef: string;\n baseBranch?: string;\n integrationBranch: string;\n tasks: Record<string, Adr0013TaskRecord>;\n}\n\nexport interface Adr0013PlanIsolation {\n run: Adr0013IsolationRun;\n resolvers: Record<string, string>;\n}\n\nfunction isAdr0013(run: IsolationRun | Adr0013IsolationRun): run is Adr0013IsolationRun {\n return 'integrationBranch' in run && typeof run.integrationBranch === 'string';\n}\n\nfunction fromAdr0013Task(r: Adr0013TaskRecord): IsolationTaskRecord {\n return {\n taskId: r.taskId,\n order: r.order,\n title: r.title,\n branch: r.branch,\n workspace: r.worktree,\n status: r.status,\n // A merged task's branch was merged into the one repo there was; what it\n // changed was never recorded, and landing is what `changed` stands for.\n repos: { [SELF_REPO]: { worktree: r.worktree, linked: r.linked ?? [], ...(r.status === 'merged' ? { changed: true } : {}) } },\n ...(r.status === 'conflict' ? { conflictRepo: SELF_REPO } : {}),\n };\n}\n\n/**\n * A persisted plan isolation in today's shape. A run saved in the ADR-0013\n * format — recognised by its own `integrationBranch` — becomes a group of one\n * at `.`, keeping its branches, so a session saved by 0.4.23 resumes and hands\n * off exactly as it would have.\n */\nexport function migratePlanIsolation(state: PlanIsolation | Adr0013PlanIsolation): PlanIsolation {\n const { run, resolvers } = state;\n if (!isAdr0013(run)) return { run, resolvers: resolvers ?? {} };\n const repo: IsolationRepo = {\n path: SELF_REPO,\n root: repoRootOf(run.workspaceRoot, SELF_REPO),\n baseRef: run.baseRef,\n ...(run.baseBranch ? { baseBranch: run.baseBranch } : {}),\n integrationBranch: run.integrationBranch,\n };\n const tasks = Object.fromEntries(Object.entries(run.tasks ?? {}).map(([id, r]) => [id, fromAdr0013Task(r)]));\n return { run: { id: run.id, workspaceRoot: run.workspaceRoot, repos: [repo], shared: [], sharedRepos: [], tasks }, resolvers: resolvers ?? {} };\n}\n\n/** Bring a loaded plan's isolation record, if any, to today's shape in place. */\nexport function migratePlanStateIsolation(plan: { isolation?: PlanIsolation | Adr0013PlanIsolation }): void {\n if (plan.isolation?.run) plan.isolation = migratePlanIsolation(plan.isolation);\n}\n"],"mappings":"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","names":[]}
|
package/dist/testing.mjs
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SELF_REPO,
|
|
3
|
+
handoffOf,
|
|
4
|
+
integrationBranchFor
|
|
5
|
+
} from "./chunk-HQICT7HQ.mjs";
|
|
6
|
+
|
|
1
7
|
// src/testing.ts
|
|
2
8
|
function fakeConfig(overrides = {}) {
|
|
3
9
|
return {
|
|
@@ -20,6 +26,11 @@ function fakeConfig(overrides = {}) {
|
|
|
20
26
|
geminiModel: "",
|
|
21
27
|
planMapEnabled: true,
|
|
22
28
|
autonomousMode: true,
|
|
29
|
+
// Off so an orchestrator built without an injected isolation never runs
|
|
30
|
+
// real git against whatever repository the tests happen to run in.
|
|
31
|
+
worktreeIsolation: false,
|
|
32
|
+
workspaceRepos: [],
|
|
33
|
+
worktreeLinks: [],
|
|
23
34
|
approvalMode: "ask",
|
|
24
35
|
approvalPreApproved: [],
|
|
25
36
|
setProviderModelLists: () => {
|
|
@@ -78,8 +89,133 @@ var FakeTerminalSession = class {
|
|
|
78
89
|
for (const cb of this.exitCbs) cb(code);
|
|
79
90
|
}
|
|
80
91
|
};
|
|
92
|
+
var FakeWorktreeIsolation = class {
|
|
93
|
+
availability = { active: true };
|
|
94
|
+
/** The repo paths `startRun` groups: a group of one at `.` unless set. */
|
|
95
|
+
repos = [SELF_REPO];
|
|
96
|
+
/** Per task id, the repos it changes; every repo of the group when not listed. */
|
|
97
|
+
changes = /* @__PURE__ */ new Map();
|
|
98
|
+
/** Per task id, the repo its landing stops in when its outcome is not `merged`; its first changed repo when not listed. */
|
|
99
|
+
stopsIn = /* @__PURE__ */ new Map();
|
|
100
|
+
/** What `mergeIntoCheckedOut` answers. */
|
|
101
|
+
mergeResult = { outcome: "merged" };
|
|
102
|
+
/** What `startRun` shares and `prepare` copies, to exercise their notices. */
|
|
103
|
+
shared = [];
|
|
104
|
+
sharedRepos = [];
|
|
105
|
+
copied = [];
|
|
106
|
+
/** Set to make `startRun` throw, as git does when no repo of the group can be isolated. */
|
|
107
|
+
startRunError = null;
|
|
108
|
+
/** Per task id; a task not listed integrates as `merged`. */
|
|
109
|
+
outcomes = /* @__PURE__ */ new Map();
|
|
110
|
+
calls = [];
|
|
111
|
+
holds = /* @__PURE__ */ new Map();
|
|
112
|
+
runCount = 0;
|
|
113
|
+
log(call) {
|
|
114
|
+
this.calls.push(call);
|
|
115
|
+
}
|
|
116
|
+
/** Task ids in the order `op` was called for them. */
|
|
117
|
+
taskIdsFor(op) {
|
|
118
|
+
return this.calls.flatMap((c) => c.op === op && "taskId" in c ? [c.taskId] : []);
|
|
119
|
+
}
|
|
120
|
+
/** Make `integrate` for this task wait until the returned function is called. */
|
|
121
|
+
holdIntegration(taskId) {
|
|
122
|
+
let open;
|
|
123
|
+
this.holds.set(taskId, new Promise((resolve) => {
|
|
124
|
+
open = resolve;
|
|
125
|
+
}));
|
|
126
|
+
return open;
|
|
127
|
+
}
|
|
128
|
+
async isActive(workspaceRoot) {
|
|
129
|
+
this.log({ op: "isActive", workspaceRoot });
|
|
130
|
+
return this.availability;
|
|
131
|
+
}
|
|
132
|
+
/** Like git: once the tracked changes are stashed, the tree is no longer dirty. */
|
|
133
|
+
async stash(workspaceRoot) {
|
|
134
|
+
this.log({ op: "stash", workspaceRoot });
|
|
135
|
+
if (!this.availability.active && this.availability.reason === "dirty") this.availability = { active: true };
|
|
136
|
+
}
|
|
137
|
+
async startRun(workspaceRoot) {
|
|
138
|
+
this.log({ op: "startRun", workspaceRoot });
|
|
139
|
+
if (this.startRunError) throw this.startRunError;
|
|
140
|
+
const id = `run${++this.runCount}`;
|
|
141
|
+
return {
|
|
142
|
+
id,
|
|
143
|
+
workspaceRoot,
|
|
144
|
+
repos: this.repos.map((repo) => ({
|
|
145
|
+
path: repo,
|
|
146
|
+
root: `${workspaceRoot}/${repo}`.replace(/\/\.$/, ""),
|
|
147
|
+
baseRef: "base0000",
|
|
148
|
+
baseBranch: "main",
|
|
149
|
+
integrationBranch: integrationBranchFor(id)
|
|
150
|
+
})),
|
|
151
|
+
shared: [...this.shared],
|
|
152
|
+
sharedRepos: [...this.sharedRepos],
|
|
153
|
+
tasks: {}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async prepare(task, run) {
|
|
157
|
+
this.log({ op: "prepare", taskId: task.id });
|
|
158
|
+
const name = `${task.order}-${task.id}`;
|
|
159
|
+
const cwd = `/fake-worktrees/${run.id}/${name}`;
|
|
160
|
+
const branch = `ordewell/${run.id}/${name}`;
|
|
161
|
+
run.tasks[task.id] = {
|
|
162
|
+
taskId: task.id,
|
|
163
|
+
order: task.order,
|
|
164
|
+
title: task.title,
|
|
165
|
+
branch,
|
|
166
|
+
workspace: cwd,
|
|
167
|
+
status: "active",
|
|
168
|
+
repos: Object.fromEntries(run.repos.map((r) => [r.path, { worktree: r.path === SELF_REPO ? cwd : `${cwd}/${r.path}`, linked: [] }]))
|
|
169
|
+
};
|
|
170
|
+
return { cwd, branch, copied: [...this.copied] };
|
|
171
|
+
}
|
|
172
|
+
/** Like git: the landing is recorded and persisted before the (held) merge, and cleared once it settles. */
|
|
173
|
+
async integrate(task, run, persist = () => void 0) {
|
|
174
|
+
this.log({ op: "integrate", taskId: task.id });
|
|
175
|
+
const record = run.tasks[task.id];
|
|
176
|
+
if (!record) return "failed";
|
|
177
|
+
const changed = this.changes.get(task.id) ?? run.repos.map((r) => r.path);
|
|
178
|
+
for (const [repo, entry] of Object.entries(record.repos)) entry.changed = changed.includes(repo) || (entry.changed ?? false);
|
|
179
|
+
if (changed.length > 0) {
|
|
180
|
+
run.landing = { taskId: task.id, tips: Object.fromEntries(changed.map((repo) => [repo, `tip-${repo}`])) };
|
|
181
|
+
persist();
|
|
182
|
+
}
|
|
183
|
+
await this.holds.get(task.id);
|
|
184
|
+
delete run.landing;
|
|
185
|
+
const outcome = this.outcomes.get(task.id) ?? "merged";
|
|
186
|
+
record.status = outcome;
|
|
187
|
+
if (outcome === "merged") delete record.conflictRepo;
|
|
188
|
+
else record.conflictRepo = this.stopsIn.get(task.id) ?? changed[0] ?? SELF_REPO;
|
|
189
|
+
return outcome;
|
|
190
|
+
}
|
|
191
|
+
async release(run, taskId, opts) {
|
|
192
|
+
this.log({ op: "release", taskId, keep: opts.keep });
|
|
193
|
+
const record = run.tasks[taskId];
|
|
194
|
+
if (!opts.keep) delete run.tasks[taskId];
|
|
195
|
+
else if (record?.status === "active") record.status = "kept";
|
|
196
|
+
}
|
|
197
|
+
async handoff(run) {
|
|
198
|
+
this.log({ op: "handoff" });
|
|
199
|
+
return handoffOf(run);
|
|
200
|
+
}
|
|
201
|
+
async pruneOrphans() {
|
|
202
|
+
this.log({ op: "pruneOrphans" });
|
|
203
|
+
}
|
|
204
|
+
async reviewDiff() {
|
|
205
|
+
this.log({ op: "reviewDiff" });
|
|
206
|
+
return "";
|
|
207
|
+
}
|
|
208
|
+
async mergeIntoCheckedOut() {
|
|
209
|
+
this.log({ op: "mergeIntoCheckedOut" });
|
|
210
|
+
return this.mergeResult;
|
|
211
|
+
}
|
|
212
|
+
async discard(_run, opts) {
|
|
213
|
+
this.log({ op: "discard", keepIntegration: opts.keepIntegration });
|
|
214
|
+
}
|
|
215
|
+
};
|
|
81
216
|
export {
|
|
82
217
|
FakeTerminalSession,
|
|
218
|
+
FakeWorktreeIsolation,
|
|
83
219
|
fakeConfig,
|
|
84
220
|
fakeFileSystem
|
|
85
221
|
};
|
package/dist/testing.mjs.map
CHANGED
|
@@ -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 { ITerminalSession } from './interfaces/ITerminalRunner';\n\nexport function fakeConfig(overrides: Partial<IConfig> = {}): IConfig {\n return {\n aiProvider: 'openrouter',\n apiKey: 'sk-test',\n planningModel: 'test-model',\n enabledRunners: ['claude-code'],\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 approvalMode: 'ask',\n approvalPreApproved: [],\n setProviderModelLists: () => {},\n getProviderBaseUrl: () => '',\n getProviderApiKey: () => '',\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 FakeTerminalSession implements ITerminalSession {\n private outputCbs: Array<(text: string) => void> = [];\n private exitCbs: Array<(code: number) => void> = [];\n output = '';\n written: string[] = [];\n killed = false;\n\n constructor(public id = 's1', public taskId = 't1') {}\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 getOutput(): string { return this.output; }\n write(text: string): void { this.written.push(text); }\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 }\n}\n"],"mappings":";AAIO,SAAS,WAAW,YAA8B,CAAC,GAAY;AACpE,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,eAAe;AAAA,IACf,gBAAgB,CAAC,aAAa;AAAA,IAC9B,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,IAChB,cAAc;AAAA,IACd,qBAAqB,CAAC;AAAA,IACtB,uBAAuB,MAAM;AAAA,IAAC;AAAA,IAC9B,oBAAoB,MAAM;AAAA,IAC1B,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,sBAAN,MAAsD;AAAA,EAO3D,YAAmB,KAAK,MAAa,SAAS,MAAM;AAAjC;AAAkB;AAAA,EAAgB;AAAA,EAAlC;AAAA,EAAkB;AAAA,EAN7B,YAA2C,CAAC;AAAA,EAC5C,UAAyC,CAAC;AAAA,EAClD,SAAS;AAAA,EACT,UAAoB,CAAC;AAAA,EACrB,SAAS;AAAA,EAIT,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,YAAoB;AAAE,WAAO,KAAK;AAAA,EAAQ;AAAA,EAC1C,MAAM,MAAoB;AAAE,SAAK,QAAQ,KAAK,IAAI;AAAA,EAAG;AAAA,EAErD,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;AAAA,EACxC;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 { ITerminalSession } from './interfaces/ITerminalRunner';\nimport type {\n IsolationAvailability,\n IsolationHandoff,\n IsolationMergeResult,\n IsolationOutcome,\n IsolationRun,\n PreparedTask,\n IWorktreeIsolation,\n} from './interfaces/IWorktreeIsolation';\nimport type { Task } from './models/Task';\nimport { handoffOf, integrationBranchFor, SELF_REPO } from './services/isolationRecord';\n\nexport function fakeConfig(overrides: Partial<IConfig> = {}): IConfig {\n return {\n aiProvider: 'openrouter',\n apiKey: 'sk-test',\n planningModel: 'test-model',\n enabledRunners: ['claude-code'],\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 approvalMode: 'ask',\n approvalPreApproved: [],\n setProviderModelLists: () => {},\n getProviderBaseUrl: () => '',\n getProviderApiKey: () => '',\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 FakeTerminalSession implements ITerminalSession {\n private outputCbs: Array<(text: string) => void> = [];\n private exitCbs: Array<(code: number) => void> = [];\n output = '';\n written: string[] = [];\n killed = false;\n\n constructor(public id = 's1', public taskId = 't1') {}\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 getOutput(): string { return this.output; }\n write(text: string): void { this.written.push(text); }\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 }\n}\n\nexport type FakeIsolationCall =\n | { op: 'isActive'; workspaceRoot: string }\n | { op: 'stash'; workspaceRoot: string }\n | { op: 'startRun'; workspaceRoot: string }\n | { op: 'prepare'; taskId: string }\n | { op: 'integrate'; taskId: string }\n | { op: 'release'; taskId: string; keep: boolean }\n | { op: 'handoff' | 'pruneOrphans' | 'reviewDiff' | 'mergeIntoCheckedOut' }\n | { op: 'discard'; keepIntegration: boolean };\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.\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 /** What `mergeIntoCheckedOut` answers. */\n mergeResult: IsolationMergeResult = { outcome: 'merged' };\n /** What `startRun` shares and `prepare` copies, to exercise their notices. */\n shared: string[] = [];\n sharedRepos: string[] = [];\n copied: string[] = [];\n /** Set to make `startRun` throw, as git does when no repo of the group can be isolated. */\n startRunError: Error | null = null;\n /** Per task id; a task not listed integrates as `merged`. */\n outcomes = new Map<string, IsolationOutcome>();\n calls: FakeIsolationCall[] = [];\n private holds = 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' | '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 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] };\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 if (outcome === 'merged') delete record.conflictRepo;\n else record.conflictRepo = this.stopsIn.get(task.id) ?? changed[0] ?? SELF_REPO;\n return outcome;\n }\n\n async release(run: IsolationRun, taskId: string, opts: { keep: boolean }): Promise<void> {\n this.log({ op: 'release', taskId, keep: opts.keep });\n const record = run.tasks[taskId];\n if (!opts.keep) delete run.tasks[taskId];\n else if (record?.status === 'active') record.status = 'kept';\n }\n\n async handoff(run: IsolationRun): Promise<IsolationHandoff> {\n this.log({ op: 'handoff' });\n return handoffOf(run);\n }\n\n async pruneOrphans(): Promise<void> { this.log({ op: 'pruneOrphans' }); }\n async reviewDiff(): Promise<string> { this.log({ op: 'reviewDiff' }); return ''; }\n async mergeIntoCheckedOut(): Promise<IsolationMergeResult> { this.log({ op: 'mergeIntoCheckedOut' }); return this.mergeResult; }\n async discard(_run: IsolationRun, opts: { keepIntegration: boolean }): Promise<void> {\n this.log({ op: 'discard', keepIntegration: opts.keepIntegration });\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
package/dist/Task-4rDGr8Su.d.mts
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
interface UserStep {
|
|
2
|
-
order: number;
|
|
3
|
-
instruction: string;
|
|
4
|
-
completed: boolean;
|
|
5
|
-
}
|
|
6
|
-
/** One deterministic signal gathered while verifying a completed task. */
|
|
7
|
-
interface VerificationCheck {
|
|
8
|
-
name: 'exit_code' | 'completion_marker' | 'manual';
|
|
9
|
-
passed: boolean;
|
|
10
|
-
/** A check that did not apply. Skipped checks don't affect the verdict. */
|
|
11
|
-
skipped: boolean;
|
|
12
|
-
detail: string;
|
|
13
|
-
}
|
|
14
|
-
/** Evidence-based verdict for a completed task. Single end-to-end outcome produced by verification. */
|
|
15
|
-
interface Verdict {
|
|
16
|
-
outcome: 'pass' | 'fail';
|
|
17
|
-
reason: string;
|
|
18
|
-
checks: VerificationCheck[];
|
|
19
|
-
decidedAt: string;
|
|
20
|
-
}
|
|
21
|
-
interface TaskOutputSummary {
|
|
22
|
-
reviewReason: string;
|
|
23
|
-
logTail: string;
|
|
24
|
-
capturedAt: string;
|
|
25
|
-
}
|
|
26
|
-
type TaskType = 'ai' | 'user';
|
|
27
|
-
type TaskStatus = 'pending' | 'approved' | 'in_progress' | 'completed' | 'failed' | 'blocked' | 'awaiting_user';
|
|
28
|
-
type TaskMode = string;
|
|
29
|
-
interface TaskModelAssignment {
|
|
30
|
-
modelId: string;
|
|
31
|
-
modelLabel: string;
|
|
32
|
-
thinkingEffort?: string;
|
|
33
|
-
/**
|
|
34
|
-
* All variant ids the model offered when this assignment was made. Carried
|
|
35
|
-
* on the assignment because runners need it at spawn time (opencode's TUI
|
|
36
|
-
* only honors an assigned variant when the others are config-disabled) and
|
|
37
|
-
* the discovery catalog isn't available there.
|
|
38
|
-
*/
|
|
39
|
-
availableVariants?: string[];
|
|
40
|
-
}
|
|
41
|
-
type RunnerId = string;
|
|
42
|
-
interface Task {
|
|
43
|
-
id: string;
|
|
44
|
-
order: number;
|
|
45
|
-
title: string;
|
|
46
|
-
description: string;
|
|
47
|
-
type: TaskType;
|
|
48
|
-
status: TaskStatus;
|
|
49
|
-
dependencies: string[];
|
|
50
|
-
prompt?: string;
|
|
51
|
-
userSteps?: UserStep[];
|
|
52
|
-
subtasks: Task[];
|
|
53
|
-
verdict?: Verdict;
|
|
54
|
-
outputSummary?: TaskOutputSummary;
|
|
55
|
-
assignedModel?: TaskModelAssignment;
|
|
56
|
-
assignedRunner: RunnerId;
|
|
57
|
-
thinkingEffort?: string;
|
|
58
|
-
taskMode?: TaskMode;
|
|
59
|
-
completionMarker: string;
|
|
60
|
-
autonomy?: 'AFK' | 'HITL';
|
|
61
|
-
sliceType?: 'HITL' | 'AFK';
|
|
62
|
-
userStoriesCovered?: string[];
|
|
63
|
-
}
|
|
64
|
-
interface DiscoveredMode {
|
|
65
|
-
id: string;
|
|
66
|
-
label: string;
|
|
67
|
-
description: string;
|
|
68
|
-
}
|
|
69
|
-
type ResearchToolType = 'read_file' | 'read_files' | 'glob' | 'grep' | 'find_symbol' | 'list_dir' | 'bash' | 'fetch' | 'web_search' | 'spawn_research_agent'
|
|
70
|
-
/**
|
|
71
|
-
* A tool belonging to a harness planner's own toolbox (ADR-0009) that has no
|
|
72
|
-
* Ordewell equivalent — Edit, WebFetch, TodoWrite, whatever a coding agent
|
|
73
|
-
* ships next. The real name travels in `toolLabel` rather than being
|
|
74
|
-
* relabelled as a tool it is not; the union stays closed so the
|
|
75
|
-
* exhaustiveness checks in every surface's icon/label switch survive.
|
|
76
|
-
*/
|
|
77
|
-
| 'agent_tool';
|
|
78
|
-
/**
|
|
79
|
-
* What happened when a research tool call ran, for honest per-surface
|
|
80
|
-
* rendering. The broadcast seam carries this on every `research_step_done` so
|
|
81
|
-
* surfaces do not have to pattern-match refusal text to tell a refused `rm`
|
|
82
|
-
* from a successful `rm` — the old render path flipped a `✓` for both.
|
|
83
|
-
*/
|
|
84
|
-
type ResearchStepOutcome = 'success' | 'failure' | 'refused' | 'denied' | 'not_executed';
|
|
85
|
-
interface ResearchStep {
|
|
86
|
-
id: string;
|
|
87
|
-
tool: ResearchToolType;
|
|
88
|
-
/** The tool's own name when it came from a harness planner — always set for `agent_tool`. */
|
|
89
|
-
toolLabel?: string;
|
|
90
|
-
args: string;
|
|
91
|
-
result: string;
|
|
92
|
-
success: boolean;
|
|
93
|
-
outcome: ResearchStepOutcome;
|
|
94
|
-
/** The model's tool_call id, so a surface can match `tool_result` to the
|
|
95
|
-
* pending `tool_call` it announced — robust under parallel same-tool rounds. */
|
|
96
|
-
toolCallId?: string;
|
|
97
|
-
timestamp: string;
|
|
98
|
-
thinkingText?: string;
|
|
99
|
-
}
|
|
100
|
-
interface UserPromptEntry {
|
|
101
|
-
id: string;
|
|
102
|
-
type: 'user_prompt' | 'system';
|
|
103
|
-
content: string;
|
|
104
|
-
timestamp: string;
|
|
105
|
-
}
|
|
106
|
-
type ResearchLogEntry = ResearchStep | UserPromptEntry;
|
|
107
|
-
interface ResearchProgress {
|
|
108
|
-
type: 'thinking' | 'tool_call' | 'tool_result' | 'plan_token' | 'interrupted' | 'liveness';
|
|
109
|
-
text?: string;
|
|
110
|
-
tool?: string;
|
|
111
|
-
/** Harness planners (ADR-0009): the agent's own name for a tool Ordewell has no member for. */
|
|
112
|
-
toolLabel?: string;
|
|
113
|
-
toolArgs?: string;
|
|
114
|
-
toolResult?: string;
|
|
115
|
-
planToken?: string;
|
|
116
|
-
step?: ResearchStep;
|
|
117
|
-
/** The model's tool_call id, threaded on tool_call and tool_result so a
|
|
118
|
-
* surface can match the result to its pending call — robust under parallel
|
|
119
|
-
* same-tool rounds where LIFO-by-name matching mislabels summaries. */
|
|
120
|
-
toolCallId?: string;
|
|
121
|
-
/** Present when this event originates from (or reports on) one spawned research subagent (issue #34). */
|
|
122
|
-
subagentId?: string;
|
|
123
|
-
}
|
|
124
|
-
interface ThinkingBlock {
|
|
125
|
-
id: string;
|
|
126
|
-
text: string;
|
|
127
|
-
}
|
|
128
|
-
interface StreamThinkingEvent {
|
|
129
|
-
type: 'thinking';
|
|
130
|
-
block: ThinkingBlock;
|
|
131
|
-
}
|
|
132
|
-
interface StreamStepEvent {
|
|
133
|
-
type: 'step';
|
|
134
|
-
step: ResearchStep;
|
|
135
|
-
}
|
|
136
|
-
type StreamEvent = StreamThinkingEvent | StreamStepEvent;
|
|
137
|
-
interface DiscoveredModel {
|
|
138
|
-
modelId: string;
|
|
139
|
-
modelLabel: string;
|
|
140
|
-
runnerProvider?: string;
|
|
141
|
-
/**
|
|
142
|
-
* Human-facing provider name as the runner itself reports it (e.g.
|
|
143
|
-
* "OpenCode Zen" for `runnerProvider: 'opencode'`). Populated from the
|
|
144
|
-
* runner's own provider catalog when available; when absent the UI derives a
|
|
145
|
-
* label from `runnerProvider` by title-casing.
|
|
146
|
-
*/
|
|
147
|
-
runnerProviderLabel?: string;
|
|
148
|
-
/**
|
|
149
|
-
* The runner whose catalog listed this model. Stamped once, at the single
|
|
150
|
-
* `ModelDiscovery.discover` choke point, so a flat cross-runner list can
|
|
151
|
-
* still say where each entry came from — `runnerProvider` alone cannot:
|
|
152
|
-
* OpenCode reports most of its catalog as `openrouter`, which names the
|
|
153
|
-
* serving backend, not the agent Ordewell would spawn.
|
|
154
|
-
*/
|
|
155
|
-
runnerId?: string;
|
|
156
|
-
/** The runner's display name (`OpenCode`), from its manifest. */
|
|
157
|
-
runnerLabel?: string;
|
|
158
|
-
variants: {
|
|
159
|
-
id: string;
|
|
160
|
-
label: string;
|
|
161
|
-
}[];
|
|
162
|
-
}
|
|
163
|
-
type PlanStatus = 'draft' | 'approved' | 'rejected' | 'running' | 'completed';
|
|
164
|
-
/**
|
|
165
|
-
* One entry of the planner's persisted dialogue (ADR-0002). The single source
|
|
166
|
-
* of truth for both UI redisplay and conversational context. Tool-call results
|
|
167
|
-
* are NOT stored here — they live in the AI service's tool-use history;
|
|
168
|
-
* `researchLog` remains the persisted tool trace for the UI.
|
|
169
|
-
*/
|
|
170
|
-
interface ConversationMessage {
|
|
171
|
-
role: 'user' | 'assistant';
|
|
172
|
-
content: string;
|
|
173
|
-
timestamp: string;
|
|
174
|
-
/**
|
|
175
|
-
* Timeline marker: 'plan_generated' records the point in the dialogue where
|
|
176
|
-
* the plan was committed (the UI anchors the plan card there on restore);
|
|
177
|
-
* 'system' is a host-injected notice. Absent for ordinary chat turns, so
|
|
178
|
-
* sessions saved before markers existed degrade gracefully.
|
|
179
|
-
*/
|
|
180
|
-
kind?: 'plan_generated' | 'system';
|
|
181
|
-
}
|
|
182
|
-
interface QueuedMessage {
|
|
183
|
-
id: string;
|
|
184
|
-
text: string;
|
|
185
|
-
timestamp: string;
|
|
186
|
-
}
|
|
187
|
-
interface PlanModificationWarnings {
|
|
188
|
-
deletedCompleted: string[];
|
|
189
|
-
changedCompleted: string[];
|
|
190
|
-
deletedInProgress: string[];
|
|
191
|
-
modifiedInProgress: string[];
|
|
192
|
-
brokenDependencies: string[];
|
|
193
|
-
}
|
|
194
|
-
declare function emptyWarnings(): PlanModificationWarnings;
|
|
195
|
-
interface LegacyPlanState {
|
|
196
|
-
tasks: Task[];
|
|
197
|
-
generatedAt: string;
|
|
198
|
-
status: PlanStatus;
|
|
199
|
-
runners: RunnerId[];
|
|
200
|
-
lastUpdated: string;
|
|
201
|
-
researchLog?: ResearchLogEntry[];
|
|
202
|
-
/** The planner dialogue — user messages and assistant messages, in order (ADR-0002). */
|
|
203
|
-
conversationHistory?: ConversationMessage[];
|
|
204
|
-
/** Full markdown PRD once written by the planner (PRD mode), also saved to .scratch/<slug>/PRD.md. */
|
|
205
|
-
prdMarkdown?: string;
|
|
206
|
-
/** Follow-ups queued while tasks execute — applied as plan modifications between batches. */
|
|
207
|
-
queuedMessages?: QueuedMessage[];
|
|
208
|
-
}
|
|
209
|
-
interface Message {
|
|
210
|
-
id: string;
|
|
211
|
-
role: 'user' | 'planner' | 'system';
|
|
212
|
-
content: string;
|
|
213
|
-
timestamp: number;
|
|
214
|
-
}
|
|
215
|
-
interface TaskSnapshot extends Task {
|
|
216
|
-
completedAt: number;
|
|
217
|
-
verdict?: Verdict;
|
|
218
|
-
retryCount: number;
|
|
219
|
-
finalized: boolean;
|
|
220
|
-
}
|
|
221
|
-
type PlanState = {
|
|
222
|
-
phase: 'planning';
|
|
223
|
-
history: Message[];
|
|
224
|
-
message: string;
|
|
225
|
-
pendingTasks: Task[];
|
|
226
|
-
} | {
|
|
227
|
-
phase: 'executing';
|
|
228
|
-
history: Message[];
|
|
229
|
-
message: string;
|
|
230
|
-
executionLog: TaskSnapshot[];
|
|
231
|
-
pendingTasks: Task[];
|
|
232
|
-
goal: string;
|
|
233
|
-
runners: string[];
|
|
234
|
-
status: PlanStatus;
|
|
235
|
-
};
|
|
236
|
-
declare function migratePlanState(raw: unknown): PlanState;
|
|
237
|
-
declare function migrateLegacyPlan(legacy: LegacyPlanState): PlanState;
|
|
238
|
-
|
|
239
|
-
declare function createTask(overrides?: Partial<Task>): Task;
|
|
240
|
-
declare function createEmptyPlan(): LegacyPlanState;
|
|
241
|
-
declare function flattenTasks(tasks: Task[]): Task[];
|
|
242
|
-
/** A flattened task with the parent it hangs under, null for a top-level task. */
|
|
243
|
-
interface TaskWithParent {
|
|
244
|
-
task: Task;
|
|
245
|
-
parent: Task | null;
|
|
246
|
-
}
|
|
247
|
-
declare function flattenTasksWithParents(tasks: Task[]): TaskWithParent[];
|
|
248
|
-
declare function migrateTask(task: Record<string, unknown>): Task;
|
|
249
|
-
declare function addTaskToPlan(tasks: Task[], partial: Partial<Task>): Task[];
|
|
250
|
-
declare function removeTaskFromPlan(tasks: Task[], taskId: string): Task[];
|
|
251
|
-
declare function updateTaskInPlan(tasks: Task[], taskId: string, changes: Partial<Task>): Task[];
|
|
252
|
-
declare function renumberTasks(tasks: Task[]): Task[];
|
|
253
|
-
declare function validateModifiedPlan(original: Task[], modified: Task[]): PlanModificationWarnings;
|
|
254
|
-
interface ActiveTaskSession {
|
|
255
|
-
id: string;
|
|
256
|
-
taskId: string;
|
|
257
|
-
}
|
|
258
|
-
interface ValidationResult {
|
|
259
|
-
valid: boolean;
|
|
260
|
-
errors: string[];
|
|
261
|
-
}
|
|
262
|
-
interface ValidationContext {
|
|
263
|
-
executionLog: TaskSnapshot[];
|
|
264
|
-
oldPending: Task[];
|
|
265
|
-
newPending: Task[];
|
|
266
|
-
activeSessions: Map<string, ActiveTaskSession>;
|
|
267
|
-
}
|
|
268
|
-
type ValidationCheck = (ctx: ValidationContext) => ValidationResult;
|
|
269
|
-
declare function warningsText(w: PlanModificationWarnings): string | null;
|
|
270
|
-
|
|
271
|
-
export { type ActiveTaskSession as A, emptyWarnings as B, type ConversationMessage as C, type DiscoveredMode as D, flattenTasks as E, flattenTasksWithParents as F, migrateLegacyPlan as G, migratePlanState as H, migrateTask as I, removeTaskFromPlan as J, renumberTasks as K, type LegacyPlanState as L, type Message as M, updateTaskInPlan as N, validateModifiedPlan as O, type PlanModificationWarnings as P, type QueuedMessage as Q, type ResearchLogEntry as R, type StreamEvent as S, type Task as T, type UserPromptEntry as U, type ValidationCheck as V, warningsText as W, type DiscoveredModel as a, type PlanState as b, type PlanStatus as c, type ResearchProgress as d, type ResearchStep as e, type ResearchStepOutcome as f, type ResearchToolType as g, type RunnerId as h, type StreamStepEvent as i, type StreamThinkingEvent as j, type TaskMode as k, type TaskModelAssignment as l, type TaskOutputSummary as m, type TaskSnapshot as n, type TaskStatus as o, type TaskType as p, type TaskWithParent as q, type ThinkingBlock as r, type UserStep as s, type ValidationContext as t, type ValidationResult as u, type Verdict as v, type VerificationCheck as w, addTaskToPlan as x, createEmptyPlan as y, createTask as z };
|