@ordewell/core 0.10.12 → 0.11.1

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 +1488 -769
  19. package/dist/index.d.ts +1488 -769
  20. package/dist/index.js +32003 -20350
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +29380 -19553
  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
@@ -7,13 +7,13 @@ import {
7
7
  plannedTask,
8
8
  stripTrailingCommas,
9
9
  taskWhere
10
- } from "./chunk-24EL4Z2W.mjs";
10
+ } from "./chunk-YAFNYNFX.mjs";
11
11
  import {
12
12
  flattenTasksWithParents
13
- } from "./chunk-PFH7WFIB.mjs";
13
+ } from "./chunk-QTZ5XTBY.mjs";
14
14
 
15
15
  // src/services/PlanValidator.ts
16
- function parsePlanJson(raw, runners, runnerModes, autonomousDefault = true) {
16
+ function parsePlanJson(raw, runners, runnerModes, autonomousDefault = false) {
17
17
  const { matches, fallback, sawUnbalanced } = extractObjectsWithKey(raw, PLAN_ENVELOPE_KEY);
18
18
  const candidates = matches.length > 0 ? [...matches].reverse() : [fallback.json];
19
19
  let lastError = null;
@@ -34,7 +34,7 @@ function parsePlanJson(raw, runners, runnerModes, autonomousDefault = true) {
34
34
  }
35
35
  throw lastError ?? new PlanParseError("Invalid plan: missing tasks array", raw);
36
36
  }
37
- function parsePlanObject(json, raw, runners, runnerModes, autonomousDefault = true) {
37
+ function parsePlanObject(json, raw, runners, runnerModes, autonomousDefault = false) {
38
38
  const cleaned = stripTrailingCommas(escapeControlCharsInStrings(json));
39
39
  let parsed;
40
40
  try {
@@ -48,7 +48,7 @@ function parsePlanObject(json, raw, runners, runnerModes, autonomousDefault = tr
48
48
  const [first] = result.errors;
49
49
  throw new PlanParseError(first.message, raw, { semantic: first.taskId !== void 0 });
50
50
  }
51
- function validatePlanTasks(obj, runners, runnerModes, autonomousDefault = true) {
51
+ function validatePlanTasks(obj, runners, runnerModes, autonomousDefault = false) {
52
52
  const rawTasks = isRecord(obj) ? obj.tasks : void 0;
53
53
  if (!Array.isArray(rawTasks)) {
54
54
  return { ok: false, errors: [{ field: "tasks", message: "Invalid plan: missing tasks array" }] };
@@ -94,4 +94,4 @@ export {
94
94
  validatePlanTasks,
95
95
  looksLikePlanAttempt
96
96
  };
97
- //# sourceMappingURL=chunk-KWS3VEER.mjs.map
97
+ //# sourceMappingURL=chunk-MYXMIVWA.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/PlanValidator.ts"],"sourcesContent":["import { Task, RunnerId, flattenTasksWithParents } from '../models/Task';\nimport { extractObjectsWithKey, stripTrailingCommas, escapeControlCharsInStrings, PlanParseError, PLAN_ENVELOPE_KEY } from './JsonExtractor';\nimport type { RunnerModeInfo } from './ModeResolver';\nimport { plannedTask, checkPlanInvariants, taskWhere, type PlanTaskError } from './PlannedTask';\n\nexport function parsePlanJson(\n raw: string,\n runners: RunnerId[],\n runnerModes?: Record<RunnerId, RunnerModeInfo[]>,\n autonomousDefault = false,\n): Task[] {\n const { matches, fallback, sawUnbalanced } = extractObjectsWithKey(raw, PLAN_ENVELOPE_KEY);\n\n // A reply can carry several tasks-keyed objects — a schema echo from the\n // prompt, an inline draft, then the real plan. The model's LAST emission is\n // its final answer, so candidates are tried back to front and the first one\n // that fully parses and validates wins.\n const candidates = matches.length > 0 ? [...matches].reverse() : [fallback.json];\n\n let lastError: PlanParseError | null = null;\n for (const json of candidates) {\n try {\n return parsePlanObject(json, raw, runners, runnerModes, autonomousDefault);\n } catch (err) {\n if (!(err instanceof PlanParseError)) throw err;\n // Prefer surfacing the error from the most plan-like candidate: the\n // last one carrying a tasks key (tried first).\n if (!lastError) lastError = err;\n }\n }\n\n // No usable tasks object anywhere, but an object ran off the end of the\n // text: the plan was almost certainly cut off mid-stream.\n if (matches.length === 0 && sawUnbalanced) {\n throw new PlanParseError(\n 'Plan response appears truncated: the model likely hit its output length limit before finishing the JSON.',\n raw,\n { truncated: true },\n );\n }\n throw lastError ?? new PlanParseError('Invalid plan: missing tasks array', raw);\n}\n\nfunction parsePlanObject(\n json: string,\n raw: string,\n runners: RunnerId[],\n runnerModes?: Record<RunnerId, RunnerModeInfo[]>,\n autonomousDefault = false,\n): Task[] {\n const cleaned = stripTrailingCommas(escapeControlCharsInStrings(json));\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(cleaned);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new PlanParseError(`Plan response was not valid JSON: ${detail}`, raw);\n }\n\n const result = validatePlanTasks(parsed, runners, runnerModes, autonomousDefault);\n if (result.ok) return result.tasks;\n const [first] = result.errors;\n // An envelope problem (no tasks array, an empty one) keeps the generic\n // re-emit; only a rule broken by a task is worth naming in the corrective.\n throw new PlanParseError(first.message, raw, { semantic: first.taskId !== undefined });\n}\n\nexport type { PlanTaskError } from './PlannedTask';\nexport { requestedSkills } from './PlannedTask';\n\n/** `warnings` name what was accepted but not used, for a channel that can tell the planner. */\nexport type PlanTasksResult = { ok: true; tasks: Task[]; warnings?: string[] } | { ok: false; errors: PlanTaskError[] };\n\n/**\n * Every check a plan's tasks must pass, on an already-parsed `{ tasks }`\n * object — what a plan is held to whichever channel it arrived on. All\n * problems are reported, the slice classification first, so the first is the\n * one the text path's corrective names.\n */\nexport function validatePlanTasks(\n obj: unknown,\n runners: RunnerId[],\n runnerModes?: Record<RunnerId, RunnerModeInfo[]>,\n autonomousDefault = false,\n): PlanTasksResult {\n const rawTasks = isRecord(obj) ? obj.tasks : undefined;\n if (!Array.isArray(rawTasks)) {\n return { ok: false, errors: [{ field: 'tasks', message: 'Invalid plan: missing tasks array' }] };\n }\n if (rawTasks.length === 0) {\n return { ok: false, errors: [{ field: 'tasks', message: 'Plan contained no tasks' }] };\n }\n\n const planned = rawTasks.map((t) => plannedTask(t, { runners, runnerModes, autonomousDefault }));\n const tasks = planned.map((p) => p.task);\n const errors = [\n ...sliceRequired(tasks),\n ...planned.flatMap((p) => p.errors),\n ...checkPlanInvariants(tasks, runners, runnerModes),\n ];\n\n if (errors.length > 0) return { ok: false, errors };\n const ignored = planned.flatMap((p) => p.ignored);\n const warnings = [\n ...(ignored.some((i) => i.field === 'taskMode')\n ? ['\"taskMode\" is not yours to set and was ignored: each task runs in its runner\\'s mode for the user\\'s autonomy level, or its read-only mode when you set \"readOnly\": true.']\n : []),\n ...ignored.filter((i) => i.field === 'readOnly').map((i) => `${i.message}: it was ignored.`),\n ];\n return warnings.length > 0 ? { ok: true, tasks, warnings } : { ok: true, tasks };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Whether a reply that failed {@link parsePlanJson} actually LOOKED like a\n * plan attempt — a balanced tasks-keyed object, or JSON that got cut off.\n * Distinguishes \"the model tried to emit a plan and botched it\" (worth a\n * corrective retry) from prose that merely mentions `\"tasks\"` (leave alone).\n */\nexport function looksLikePlanAttempt(raw: string): boolean {\n const { matches, sawUnbalanced } = extractObjectsWithKey(raw, PLAN_ENVELOPE_KEY);\n if (matches.length > 0) return true;\n return sawUnbalanced && raw.includes(`\"${PLAN_ENVELOPE_KEY}\"`);\n}\n\n/**\n * A whole plan says how each task is sliced: an edit may leave it to the\n * task's parent or the tasks it was made from, a submitted plan may not.\n */\nfunction sliceRequired(tasks: readonly Task[]): PlanTaskError[] {\n return flattenTasksWithParents(tasks).flatMap(({ task: t, parent }) => {\n const where = taskWhere(t.title, parent?.title);\n const errors: PlanTaskError[] = [];\n if (!t.sliceType) errors.push({ taskId: t.id, field: 'sliceType', message: `${where} is missing sliceType. Every task must specify sliceType (\"AFK\" or \"HITL\").` });\n if (t.type === 'ai' && !t.autonomy) errors.push({ taskId: t.id, field: 'autonomy', message: `AI ${where} is missing autonomy. Every AI task must specify autonomy (\"AFK\" or \"HITL\").` });\n return errors;\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;AAKO,SAAS,cACd,KACA,SACA,aACA,oBAAoB,OACZ;AACR,QAAM,EAAE,SAAS,UAAU,cAAc,IAAI,sBAAsB,KAAK,iBAAiB;AAMzF,QAAM,aAAa,QAAQ,SAAS,IAAI,CAAC,GAAG,OAAO,EAAE,QAAQ,IAAI,CAAC,SAAS,IAAI;AAE/E,MAAI,YAAmC;AACvC,aAAW,QAAQ,YAAY;AAC7B,QAAI;AACF,aAAO,gBAAgB,MAAM,KAAK,SAAS,aAAa,iBAAiB;AAAA,IAC3E,SAAS,KAAK;AACZ,UAAI,EAAE,eAAe,gBAAiB,OAAM;AAG5C,UAAI,CAAC,UAAW,aAAY;AAAA,IAC9B;AAAA,EACF;AAIA,MAAI,QAAQ,WAAW,KAAK,eAAe;AACzC,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,EAAE,WAAW,KAAK;AAAA,IACpB;AAAA,EACF;AACA,QAAM,aAAa,IAAI,eAAe,qCAAqC,GAAG;AAChF;AAEA,SAAS,gBACP,MACA,KACA,SACA,aACA,oBAAoB,OACZ;AACR,QAAM,UAAU,oBAAoB,4BAA4B,IAAI,CAAC;AAErE,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,OAAO;AAAA,EAC7B,SAAS,KAAK;AACZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,UAAM,IAAI,eAAe,qCAAqC,MAAM,IAAI,GAAG;AAAA,EAC7E;AAEA,QAAM,SAAS,kBAAkB,QAAQ,SAAS,aAAa,iBAAiB;AAChF,MAAI,OAAO,GAAI,QAAO,OAAO;AAC7B,QAAM,CAAC,KAAK,IAAI,OAAO;AAGvB,QAAM,IAAI,eAAe,MAAM,SAAS,KAAK,EAAE,UAAU,MAAM,WAAW,OAAU,CAAC;AACvF;AAcO,SAAS,kBACd,KACA,SACA,aACA,oBAAoB,OACH;AACjB,QAAM,WAAW,SAAS,GAAG,IAAI,IAAI,QAAQ;AAC7C,MAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG;AAC5B,WAAO,EAAE,IAAI,OAAO,QAAQ,CAAC,EAAE,OAAO,SAAS,SAAS,oCAAoC,CAAC,EAAE;AAAA,EACjG;AACA,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO,EAAE,IAAI,OAAO,QAAQ,CAAC,EAAE,OAAO,SAAS,SAAS,0BAA0B,CAAC,EAAE;AAAA,EACvF;AAEA,QAAM,UAAU,SAAS,IAAI,CAAC,MAAM,YAAY,GAAG,EAAE,SAAS,aAAa,kBAAkB,CAAC,CAAC;AAC/F,QAAM,QAAQ,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI;AACvC,QAAM,SAAS;AAAA,IACb,GAAG,cAAc,KAAK;AAAA,IACtB,GAAG,QAAQ,QAAQ,CAAC,MAAM,EAAE,MAAM;AAAA,IAClC,GAAG,oBAAoB,OAAO,SAAS,WAAW;AAAA,EACpD;AAEA,MAAI,OAAO,SAAS,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO;AAClD,QAAM,UAAU,QAAQ,QAAQ,CAAC,MAAM,EAAE,OAAO;AAChD,QAAM,WAAW;AAAA,IACf,GAAI,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,UAAU,IAC1C,CAAC,yKAA2K,IAC5K,CAAC;AAAA,IACL,GAAG,QAAQ,OAAO,CAAC,MAAM,EAAE,UAAU,UAAU,EAAE,IAAI,CAAC,MAAM,GAAG,EAAE,OAAO,mBAAmB;AAAA,EAC7F;AACA,SAAO,SAAS,SAAS,IAAI,EAAE,IAAI,MAAM,OAAO,SAAS,IAAI,EAAE,IAAI,MAAM,MAAM;AACjF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAQO,SAAS,qBAAqB,KAAsB;AACzD,QAAM,EAAE,SAAS,cAAc,IAAI,sBAAsB,KAAK,iBAAiB;AAC/E,MAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,SAAO,iBAAiB,IAAI,SAAS,IAAI,iBAAiB,GAAG;AAC/D;AAMA,SAAS,cAAc,OAAyC;AAC9D,SAAO,wBAAwB,KAAK,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,MAAM;AACrE,UAAM,QAAQ,UAAU,EAAE,OAAO,QAAQ,KAAK;AAC9C,UAAM,SAA0B,CAAC;AACjC,QAAI,CAAC,EAAE,UAAW,QAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,OAAO,aAAa,SAAS,GAAG,KAAK,8EAA8E,CAAC;AAClK,QAAI,EAAE,SAAS,QAAQ,CAAC,EAAE,SAAU,QAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,OAAO,YAAY,SAAS,MAAM,KAAK,+EAA+E,CAAC;AACvL,WAAO;AAAA,EACT,CAAC;AACH;","names":[]}
@@ -68,18 +68,18 @@ function dependencyTargetErrors(tasks, taskId, dependencies) {
68
68
  }
69
69
  function findDependencyCycle(tasks) {
70
70
  const byId = new Map([...walkPlan(tasks)].map(({ task }) => [task.id, task]));
71
- const path = [];
71
+ const path2 = [];
72
72
  const done = /* @__PURE__ */ new Set();
73
73
  const visit = (id) => {
74
74
  if (done.has(id)) return null;
75
- const at = path.indexOf(id);
76
- if (at >= 0) return path.slice(at);
77
- path.push(id);
75
+ const at = path2.indexOf(id);
76
+ if (at >= 0) return path2.slice(at);
77
+ path2.push(id);
78
78
  for (const dep of byId.get(id)?.dependencies ?? []) {
79
79
  const hit = visit(dep);
80
80
  if (hit) return hit;
81
81
  }
82
- path.pop();
82
+ path2.pop();
83
83
  done.add(id);
84
84
  return null;
85
85
  };
@@ -101,6 +101,138 @@ function canSetDependencies(tasks, taskId, dependencies) {
101
101
  return cycle ? { ok: false, error: `Dependency cycle detected: ${[...cycle, cycle[0]].map((t) => `"${t.title}"`).join(" \u2192 ")}` } : { ok: true };
102
102
  }
103
103
 
104
+ // src/services/repoPath.ts
105
+ var SELF_REPO = ".";
106
+
107
+ // src/services/isolationRecord.ts
108
+ import * as path from "path";
109
+ function integrationBranchFor(runId) {
110
+ return `ordewell/${runId}/integration`;
111
+ }
112
+ function repoRootOf(workspaceRoot, repoPath) {
113
+ return path.join(workspaceRoot, repoPath);
114
+ }
115
+ function noRemoval() {
116
+ return { preserved: [], refused: [] };
117
+ }
118
+ var PrepareFailedError = class extends Error {
119
+ constructor(failure, preserved) {
120
+ super(failure instanceof Error ? failure.message : String(failure));
121
+ this.failure = failure;
122
+ this.preserved = preserved;
123
+ this.name = failure instanceof Error ? failure.name : "Error";
124
+ this.cause = failure;
125
+ }
126
+ failure;
127
+ preserved;
128
+ };
129
+ function absorbRemoval(into, from) {
130
+ into.preserved.push(...from.preserved);
131
+ into.refused.push(...from.refused);
132
+ return into;
133
+ }
134
+ function layoutOf(run) {
135
+ return { repos: run.repos.map((r) => r.path), shared: [...run.shared] };
136
+ }
137
+ var ISOLATION_STATE = {
138
+ active: "active",
139
+ merged: "integrated",
140
+ conflict: "conflict",
141
+ repairing: "repairing",
142
+ kept: "kept",
143
+ failed: "kept"
144
+ };
145
+ var isKnownStatus = (status) => Object.prototype.hasOwnProperty.call(ISOLATION_STATE, status);
146
+ function changedReposOf(record) {
147
+ return Object.entries(record.repos).filter(([, r]) => r.changed).map(([repoPath]) => repoPath);
148
+ }
149
+ function taskIsolationOf(record, repairLimit) {
150
+ return {
151
+ state: ISOLATION_STATE[record.status],
152
+ branch: record.branch,
153
+ worktree: record.workspace,
154
+ repos: changedReposOf(record),
155
+ ...record.conflictRepo ? { conflictRepo: record.conflictRepo } : {},
156
+ ...record.conflictFiles?.length ? { conflictFiles: record.conflictFiles } : {},
157
+ ...record.repairs && repairLimit !== void 0 ? { repair: { attempt: record.repairs, limit: repairLimit } } : {},
158
+ ...record.repairedFiles?.length ? { repairedFiles: record.repairedFiles } : {}
159
+ };
160
+ }
161
+ function handoffOf(run) {
162
+ const merged = Object.values(run.tasks).filter((r) => r.status === "merged" && !r.inHead).sort((a, b) => a.order - b.order);
163
+ const entry = (r) => ({
164
+ taskId: r.taskId,
165
+ order: r.order,
166
+ title: r.title,
167
+ ...r.repairedFiles?.length ? { repairedFiles: r.repairedFiles } : {}
168
+ });
169
+ return {
170
+ repos: run.repos.map((repo) => ({
171
+ path: repo.path,
172
+ integrationBranch: repo.integrationBranch,
173
+ baseRef: repo.baseRef,
174
+ ...repo.baseBranch ? { baseBranch: repo.baseBranch } : {},
175
+ ...repo.startedDetached ? { startedDetached: true } : {},
176
+ landed: merged.filter((r) => r.repos[repo.path]?.changed).map(entry)
177
+ })),
178
+ landed: merged.map(entry)
179
+ };
180
+ }
181
+ function isAdr0013(run) {
182
+ return "integrationBranch" in run && typeof run.integrationBranch === "string";
183
+ }
184
+ function fromAdr0013Task(r) {
185
+ return {
186
+ taskId: r.taskId,
187
+ order: r.order,
188
+ title: r.title,
189
+ branch: r.branch,
190
+ workspace: r.worktree,
191
+ status: r.status,
192
+ // A merged task's branch was merged into the one repo there was; what it
193
+ // changed was never recorded, and landing is what `changed` stands for.
194
+ repos: { [SELF_REPO]: { worktree: r.worktree, linked: r.linked ?? [], ...r.status === "merged" ? { changed: true } : {} } },
195
+ ...r.status === "conflict" ? { conflictRepo: SELF_REPO } : {}
196
+ };
197
+ }
198
+ function migratePlanIsolation(state) {
199
+ const { run, resolvers } = state;
200
+ if (!isAdr0013(run)) return { run, resolvers: resolvers ?? {} };
201
+ const repo = {
202
+ path: SELF_REPO,
203
+ root: repoRootOf(run.workspaceRoot, SELF_REPO),
204
+ baseRef: run.baseRef,
205
+ ...run.baseBranch ? { baseBranch: run.baseBranch } : {},
206
+ integrationBranch: run.integrationBranch
207
+ };
208
+ const tasks = Object.fromEntries(Object.entries(run.tasks ?? {}).map(([id, r]) => [id, fromAdr0013Task(r)]));
209
+ return { run: { id: run.id, workspaceRoot: run.workspaceRoot, repos: [repo], shared: [], sharedRepos: [], tasks }, resolvers: resolvers ?? {} };
210
+ }
211
+ function migratePlanStateIsolation(plan) {
212
+ if (plan.isolation?.run) plan.isolation = migratePlanIsolation(plan.isolation);
213
+ }
214
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
215
+ var isArray = (value) => Array.isArray(value);
216
+ function carriesIsolation(plan) {
217
+ if (!isRecord(plan) || !isRecord(plan.isolation)) return false;
218
+ const { run } = plan.isolation;
219
+ if (!isRecord(run) || !isRecord(run.tasks)) return false;
220
+ if (isArray(run.repos)) {
221
+ return run.repos.some((repo) => isRecord(repo) && typeof repo.path === "string" && typeof repo.integrationBranch === "string" && typeof repo.baseRef === "string");
222
+ }
223
+ return typeof run.integrationBranch === "string" && typeof run.baseRef === "string";
224
+ }
225
+ function isolationViewOfPlan(plan, repairLimit) {
226
+ if (!carriesIsolation(plan)) return null;
227
+ const { run } = migratePlanIsolation(plan.isolation);
228
+ const tasks = {};
229
+ for (const [taskId, record] of Object.entries(run.tasks)) {
230
+ if (!isKnownStatus(record.status)) continue;
231
+ tasks[taskId] = taskIsolationOf(record, repairLimit);
232
+ }
233
+ return { tasks, handoff: handoffOf(run) };
234
+ }
235
+
104
236
  // src/services/TaskRefs.ts
105
237
  function taskOrderLabel(task, parent) {
106
238
  return parent ? `${parent.label ?? parent.order}.${task.order}` : String(task.order);
@@ -169,6 +301,40 @@ function migrateLoadedTasks(tasks) {
169
301
  migrateLoadedTasks(task.subtasks ?? []);
170
302
  }
171
303
  }
304
+ function dropRetiredPlanFields(plan) {
305
+ const raw = plan;
306
+ delete raw.researchBudget;
307
+ const settings = plan.runSettings;
308
+ if (settings) {
309
+ delete settings.researchMaxParallel;
310
+ delete settings.researchMaxPerMessage;
311
+ }
312
+ for (const turn of plan.pendingTurns ?? []) delete turn.budgetStarted;
313
+ for (const task of flattenTasks(plan.tasks)) delete task.researchWait;
314
+ }
315
+ function carryOverQueuedEdits(plan) {
316
+ const queued = plan.queuedMessages ?? [];
317
+ delete plan.queuedMessages;
318
+ if (queued.length === 0) return;
319
+ const carried = queued.map(({ id, text, timestamp, skills }) => ({ id, source: "user", text, timestamp, ...skills ? { skills } : {} }));
320
+ plan.pendingTurns = [...plan.pendingTurns ?? [], ...carried];
321
+ }
322
+ function migrateLoadedPlan(plan, { adopting = true } = {}) {
323
+ plan.approved ??= plan.status !== "draft" && plan.status !== "rejected";
324
+ if (!plan.approvedTasks && plan.approved) {
325
+ plan.approvedTasks = flattenTasks(plan.tasks).map((t) => t.id);
326
+ }
327
+ if (adopting) {
328
+ for (const task of flattenTasks(plan.tasks)) delete task.resumeAt;
329
+ }
330
+ migratePlanStateIsolation(plan);
331
+ delete plan.runnerTransport;
332
+ for (const key of ["supervisorLevel", "mergeAtGatesAutomatically", "supervisorRetryLimit", "wakeUpReadBudget"]) {
333
+ delete plan[key];
334
+ }
335
+ dropRetiredPlanFields(plan);
336
+ carryOverQueuedEdits(plan);
337
+ }
172
338
  function isUserMessage(entry) {
173
339
  return entry.role === "user" && entry.kind !== "skill_load" && entry.kind !== "wake_up";
174
340
  }
@@ -254,12 +420,13 @@ function migratePlanState(raw) {
254
420
  }
255
421
  function createTask(overrides = {}) {
256
422
  const skills = skillNames(overrides.skills);
423
+ const type = overrides.type ?? "ai";
257
424
  return {
258
425
  id: overrides.id ?? uuidv4(),
259
426
  order: overrides.order ?? 0,
260
427
  title: overrides.title ?? "",
261
428
  description: overrides.description ?? "",
262
- type: overrides.type ?? "ai",
429
+ type,
263
430
  status: overrides.status ?? "pending",
264
431
  dependencies: overrides.dependencies ?? [],
265
432
  prompt: overrides.prompt,
@@ -267,16 +434,25 @@ function createTask(overrides = {}) {
267
434
  subtasks: overrides.subtasks ?? [],
268
435
  verdict: overrides.verdict,
269
436
  outputSummary: overrides.outputSummary,
437
+ ...overrides.handoff ? { handoff: structuredClone(overrides.handoff) } : {},
438
+ ...overrides.attempted ? { attempted: true } : {},
270
439
  ...overrides.attemptUsage ? { attemptUsage: structuredClone(overrides.attemptUsage) } : {},
271
440
  assignedModel: overrides.assignedModel,
272
441
  assignedRunner: overrides.assignedRunner ?? DEFAULT_RUNNERS[0],
273
- taskMode: overrides.taskMode ?? "build",
442
+ // A script task runs in no mode (ADR-0035): one given it would read as a mode it runs in.
443
+ taskMode: overrides.taskMode ?? (type === "script" ? void 0 : "build"),
274
444
  autonomy: overrides.autonomy,
275
445
  sliceType: overrides.sliceType,
276
446
  userStoriesCovered: overrides.userStoriesCovered,
277
447
  ...opsFlag(overrides.ops, overrides.type) ? { ops: true } : {},
278
448
  ...skills ? { skills } : {},
279
- ...overrides.addedBy === "supervisor" ? { addedBy: "supervisor" } : {}
449
+ ...overrides.addedBy === "supervisor" ? { addedBy: "supervisor" } : {},
450
+ ...overrides.commands !== void 0 ? { commands: overrides.commands.map((argv) => [...argv]) } : {},
451
+ ...overrides.script !== void 0 ? { script: overrides.script } : {},
452
+ ...overrides.timeoutMs !== void 0 ? { timeoutMs: overrides.timeoutMs } : {},
453
+ ...overrides.loop ? { loop: { body: [...overrides.loop.body], maxRounds: overrides.loop.maxRounds } } : {},
454
+ ...overrides.loopRound !== void 0 ? { loopRound: overrides.loopRound } : {},
455
+ ...overrides.research !== void 0 ? { research: overrides.research } : {}
280
456
  };
281
457
  }
282
458
  var SKILL_NAME = new RegExp(`^${SKILL_NAME_PATTERN}$`);
@@ -295,6 +471,9 @@ function inheritedSkills(chosen, inherited) {
295
471
  function opsFlag(value, type = "ai") {
296
472
  return value === true && type === "ai" ? true : void 0;
297
473
  }
474
+ function runsAsOps(task) {
475
+ return task.type === "script" || opsFlag(task.ops, task.type) === true;
476
+ }
298
477
  function inheritedOps(from, chosen) {
299
478
  return chosen ?? from.every((t) => opsFlag(t.ops) === true);
300
479
  }
@@ -342,11 +521,19 @@ function addTaskToPlan(tasks, partial) {
342
521
  }
343
522
  function removeTaskFromPlan(tasks, taskId) {
344
523
  const removed = subtreeIds(tasks, taskId);
345
- const prune = (level) => level.filter((t) => !removed.has(t.id)).map((t) => ({
346
- ...t,
347
- dependencies: t.dependencies.filter((depId) => !removed.has(depId)),
348
- subtasks: prune(t.subtasks)
349
- }));
524
+ const prune = (level) => level.filter((t) => !removed.has(t.id)).map((t) => {
525
+ const pruned = {
526
+ ...t,
527
+ dependencies: t.dependencies.filter((depId) => !removed.has(depId)),
528
+ subtasks: prune(t.subtasks)
529
+ };
530
+ if (!t.loop?.body.some((id) => removed.has(id))) return pruned;
531
+ const body = t.loop.body.filter((id) => !removed.has(id));
532
+ if (body.length > 0) return { ...pruned, loop: { ...t.loop, body } };
533
+ delete pruned.loop;
534
+ delete pruned.loopRound;
535
+ return pruned;
536
+ });
350
537
  return renumberTasks(prune(tasks));
351
538
  }
352
539
  function updateTaskInPlan(tasks, taskId, changes) {
@@ -364,6 +551,9 @@ function renumberTasks(tasks) {
364
551
  subtasks: renumberTasks(t.subtasks)
365
552
  }));
366
553
  }
554
+ function waitsMidAttempt(task) {
555
+ return task.status === "awaiting_user" && (task.awaitingReason === "checkpoint" || task.awaitingReason === "input");
556
+ }
367
557
  function pausedOnLimit(task) {
368
558
  return task.status === "awaiting_user" && task.awaitingReason === "usage-limit";
369
559
  }
@@ -377,7 +567,7 @@ function approvalRecordOf(plan) {
377
567
  };
378
568
  }
379
569
  function waitsForApproval(task, record) {
380
- return record.approved === true && task.type === "ai" && (task.status === "pending" || task.status === "approved") && !(record.approvedTasks ?? []).includes(task.id);
570
+ return record.approved === true && task.type !== "user" && (task.status === "pending" || task.status === "approved") && !(record.approvedTasks ?? []).includes(task.id);
381
571
  }
382
572
  function stampOrigin(before, after, bySupervisor) {
383
573
  const prior = new Map(flattenTasks([...before]).map((t) => [t.id, t]));
@@ -404,20 +594,26 @@ function keepExecutionState(current, rewrite) {
404
594
  const result = tasks.map((t) => {
405
595
  const prior = existing.get(t.id);
406
596
  if (prior && SETTLED_STATUSES.has(prior.status)) return { ...prior };
597
+ if (prior?.status === "failed" && prior.type === "script" !== (t.type === "script")) return { ...prior };
407
598
  return {
408
599
  ...t,
409
600
  status: prior?.status ?? "pending",
410
601
  skipReason: prior?.skipReason,
411
602
  verdict: prior?.verdict,
412
603
  outputSummary: prior?.outputSummary,
604
+ handoff: prior?.handoff,
413
605
  runnerSessionId: prior?.runnerSessionId,
414
606
  runnerSessionOwner: prior?.runnerSessionOwner,
415
607
  carriedFrom: prior?.carriedFrom,
416
608
  attemptSkills: prior?.attemptSkills,
609
+ attempted: prior?.attempted,
417
610
  attemptUsage: prior?.attemptUsage,
611
+ loopRound: prior?.loopRound,
418
612
  // Where a task that has run ran is fixed, like its runner session (ADR-0020).
419
613
  ...prior?.status === "failed" ? { ops: prior.ops } : {},
420
614
  forcedPastGate: prior?.forcedPastGate,
615
+ reportTo: prior?.reportTo,
616
+ researchOrigin: prior?.researchOrigin,
421
617
  subtasks: overlay(t.subtasks ?? [], prior?.subtasks ?? [])
422
618
  };
423
619
  });
@@ -443,6 +639,19 @@ export {
443
639
  dependencyTargetErrors,
444
640
  findDependencyCycle,
445
641
  canSetDependencies,
642
+ SELF_REPO,
643
+ integrationBranchFor,
644
+ repoRootOf,
645
+ noRemoval,
646
+ PrepareFailedError,
647
+ absorbRemoval,
648
+ layoutOf,
649
+ changedReposOf,
650
+ taskIsolationOf,
651
+ handoffOf,
652
+ migratePlanIsolation,
653
+ migratePlanStateIsolation,
654
+ isolationViewOfPlan,
446
655
  taskOrderLabel,
447
656
  taskRef,
448
657
  titledTaskRef,
@@ -452,6 +661,8 @@ export {
452
661
  resolveUserTaskRef,
453
662
  isAwaitingReason,
454
663
  migrateLoadedTasks,
664
+ dropRetiredPlanFields,
665
+ migrateLoadedPlan,
455
666
  isUserMessage,
456
667
  migratePlanState,
457
668
  createTask,
@@ -459,6 +670,7 @@ export {
459
670
  skillNames,
460
671
  inheritedSkills,
461
672
  opsFlag,
673
+ runsAsOps,
462
674
  inheritedOps,
463
675
  DEFAULT_RUNNERS,
464
676
  createEmptyPlan,
@@ -469,6 +681,7 @@ export {
469
681
  removeTaskFromPlan,
470
682
  updateTaskInPlan,
471
683
  renumberTasks,
684
+ waitsMidAttempt,
472
685
  pausedOnLimit,
473
686
  waitsForReset,
474
687
  approvalRecordOf,
@@ -476,4 +689,4 @@ export {
476
689
  stampOrigin,
477
690
  keepExecutionState
478
691
  };
479
- //# sourceMappingURL=chunk-PFH7WFIB.mjs.map
692
+ //# sourceMappingURL=chunk-QTZ5XTBY.mjs.map