@delete101020/aidlc 4.0.39 → 4.0.42

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/bundle.js CHANGED
@@ -19700,6 +19700,7 @@ var require_FollowUpHookSchema = __commonJS({
19700
19700
  return typeof p === "string" && path17.posix.basename(p.replace(/\\/g, "/")) === exports2.FOLLOW_UPS_FILE;
19701
19701
  });
19702
19702
  }
19703
+ var STEP_KEYS_NOT_HOOKS = ["on_reject"];
19703
19704
  function collectFollowUpHookIssues(raw) {
19704
19705
  const issues = [];
19705
19706
  const pipelines = raw?.pipelines;
@@ -19720,7 +19721,7 @@ var require_FollowUpHookSchema = __commonJS({
19720
19721
  const label = typeof step.name === "string" ? step.name : typeof step.agent === "string" ? step.agent : String(si);
19721
19722
  const at = `pipelines.${pid}.steps.${label}`;
19722
19723
  for (const key of Object.keys(step)) {
19723
- if (!key.startsWith("on_")) {
19724
+ if (!key.startsWith("on_") || STEP_KEYS_NOT_HOOKS.includes(key)) {
19724
19725
  continue;
19725
19726
  }
19726
19727
  if (!isHookKey(key)) {
@@ -19763,15 +19764,18 @@ var require_WorkspaceSchema = __commonJS({
19763
19764
  "../core/dist/schema/WorkspaceSchema.js"(exports2) {
19764
19765
  "use strict";
19765
19766
  Object.defineProperty(exports2, "__esModule", { value: true });
19766
- exports2.WorkspaceValidationError = exports2.WorkspaceSchema = void 0;
19767
- exports2.normalizeStep = normalizeStep4;
19767
+ exports2.WorkspaceValidationError = exports2.WorkspaceSchema = exports2.AMBIGUOUS_STEP_INDEX = exports2.DEFAULT_MAX_BATCHES = exports2.DEFAULT_MAX_SEND_BACKS = void 0;
19768
+ exports2.normalizeStep = normalizeStep5;
19768
19769
  exports2.stepSkillAlternatives = stepSkillAlternatives;
19769
19770
  exports2.resolveStepSkills = resolveStepSkills;
19770
19771
  exports2.producesEntries = producesEntries;
19771
19772
  exports2.requiresEntries = requiresEntries;
19772
19773
  exports2.stepAgentId = stepAgentId3;
19773
19774
  exports2.stepDagId = stepDagId;
19775
+ exports2.findStepIndex = findStepIndex2;
19776
+ exports2.sendBackIssues = sendBackIssues;
19774
19777
  exports2.collectWorkspaceRefIssues = collectWorkspaceRefIssues3;
19778
+ exports2.unsafeRunnerArg = unsafeRunnerArg;
19775
19779
  exports2.validateWorkspace = validateWorkspace6;
19776
19780
  var zod_1 = require_zod();
19777
19781
  var FollowUpHookSchema_1 = require_FollowUpHookSchema();
@@ -19947,6 +19951,46 @@ var require_WorkspaceSchema = __commonJS({
19947
19951
  * (infinite loop, network stall). Defaults to 120_000 when omitted.
19948
19952
  */
19949
19953
  auto_review_timeout_ms: zod_1.z.number().int().positive().optional(),
19954
+ /**
19955
+ * Where an `auto_review` reject sends the work. Without it the step is
19956
+ * rejected in place and the run stops — right for a check on the step's
19957
+ * own output, wrong for verify/review, where running the check again on
19958
+ * the same code gives the same answer and it is `implement` that has to
19959
+ * change. The target reopens with the reason as its feedback, the steps
19960
+ * after it reset, and the autopilot loop carries on from there.
19961
+ *
19962
+ * The target is a step name (or agent id) before this one, without
19963
+ * `human_review` — work a human gate approved goes back through a human.
19964
+ * A validator can name the same target in its verdict (`sendBackTo`), or
19965
+ * name this step / `null` to stop here instead. Bounded by the
19966
+ * pipeline's `max_send_backs`.
19967
+ */
19968
+ on_reject: zod_1.z.object({ send_back_to: zod_1.z.string().min(1) }).optional(),
19969
+ /**
19970
+ * Minutes the step's agent may run under `aidlc run exec` / Run to
19971
+ * completion before it is stopped (the whole process tree: SIGTERM, then
19972
+ * SIGKILL). A timed-out step fails the run — it is not sent back and does
19973
+ * not count toward `max_send_backs` — and stays `awaiting_work`. Unset
19974
+ * means no limit.
19975
+ */
19976
+ timeout_minutes: zod_1.z.number().positive().optional(),
19977
+ /**
19978
+ * Run the step as a series of agent sessions ("batches") instead of one.
19979
+ * Each session does a slice of the work and ends its first `produces`
19980
+ * file with `Status: continue` or `Status: done`; on `continue` the exec
19981
+ * loop starts a fresh session on the same revision. A missing or
19982
+ * unreadable `Status:` line fails the step. `timeout_minutes` applies to
19983
+ * each batch.
19984
+ */
19985
+ batches: zod_1.z.boolean().optional(),
19986
+ /**
19987
+ * Batches one `aidlc run exec` may run on this step before it stops for a
19988
+ * person ({@link DEFAULT_MAX_BATCHES} when unset). Running exec again
19989
+ * allows as many more.
19990
+ */
19991
+ max_batches: zod_1.z.number().int().positive().optional(),
19992
+ /** Stop the loop after every `continue` batch, for a person to look before the next. */
19993
+ pause_between_batches: zod_1.z.boolean().optional(),
19950
19994
  /** When true, the runner pauses for human approval before advancing. */
19951
19995
  human_review: zod_1.z.boolean().default(false),
19952
19996
  /**
@@ -20008,12 +20052,42 @@ var require_WorkspaceSchema = __commonJS({
20008
20052
  recipe: zod_1.z.string().min(1).optional(),
20009
20053
  steps: zod_1.z.array(PipelineStepSchema).min(1),
20010
20054
  on_failure: zod_1.z.enum(["stop", "continue"]).default("stop"),
20011
- budget: PipelineBudgetSchema.optional()
20055
+ budget: PipelineBudgetSchema.optional(),
20056
+ /**
20057
+ * How many times the autopilot loop may send work back (`on_reject`)
20058
+ * before it stops for a human. Counted from the last time a human acted on
20059
+ * the run — approve, reject, rerun, request update — so a run that was
20060
+ * steered by hand does not start out at the ceiling. Defaults to
20061
+ * {@link DEFAULT_MAX_SEND_BACKS}; 0 stops at the first reject.
20062
+ */
20063
+ max_send_backs: zod_1.z.number().int().min(0).optional(),
20064
+ /**
20065
+ * The run is meant to go unattended. `aidlc run exec` / Run to completion
20066
+ * then refuses to start unless it is safe to leave alone — on a branch
20067
+ * other than main, a clean working tree, the approval-gate hook registered
20068
+ * for Claude steps — and the hook blocks on its own errors instead of
20069
+ * allowing. See `runs/autopilotPreflight.ts`.
20070
+ */
20071
+ autopilot: zod_1.z.boolean().optional()
20072
+ }).superRefine((p, ctx) => {
20073
+ for (const issue of sendBackIssues(p.steps)) {
20074
+ ctx.addIssue({ code: "custom", path: ["steps", issue.stepIdx, "on_reject", "send_back_to"], message: issue.message });
20075
+ }
20012
20076
  });
20077
+ exports2.DEFAULT_MAX_SEND_BACKS = 3;
20078
+ exports2.DEFAULT_MAX_BATCHES = 10;
20013
20079
  var RecipeGateSchema = zod_1.z.object({
20014
20080
  human_review: zod_1.z.boolean().optional(),
20015
20081
  auto_review: zod_1.z.boolean().optional(),
20016
- auto_review_runner: zod_1.z.string().min(1).optional()
20082
+ auto_review_runner: zod_1.z.string().min(1).optional(),
20083
+ /** Where this step's auto-review reject sends the work — see the step's `on_reject`. */
20084
+ on_reject: zod_1.z.object({ send_back_to: zod_1.z.string().min(1) }).optional(),
20085
+ /** Run-time limit for this step — see the step's `timeout_minutes`. */
20086
+ timeout_minutes: zod_1.z.number().positive().optional(),
20087
+ /** Batched sessions for this step — see the step's `batches`. */
20088
+ batches: zod_1.z.boolean().optional(),
20089
+ max_batches: zod_1.z.number().int().positive().optional(),
20090
+ pause_between_batches: zod_1.z.boolean().optional()
20017
20091
  });
20018
20092
  var RecipeSchema = zod_1.z.object({
20019
20093
  id: zod_1.z.string().min(1),
@@ -20038,9 +20112,13 @@ var require_WorkspaceSchema = __commonJS({
20038
20112
  * plan) and none after, a risky one wants them everywhere. Only the fields
20039
20113
  * present override; the rest inherit.
20040
20114
  */
20041
- gates: zod_1.z.record(zod_1.z.string(), RecipeGateSchema).optional()
20115
+ gates: zod_1.z.record(zod_1.z.string(), RecipeGateSchema).optional(),
20116
+ /** Sets the assembled pipeline's `autopilot` — see the pipeline's. */
20117
+ autopilot: zod_1.z.boolean().optional(),
20118
+ /** Sets the assembled pipeline's `max_send_backs`, over the source pipeline's. */
20119
+ max_send_backs: zod_1.z.number().int().min(0).optional()
20042
20120
  });
20043
- function normalizeStep4(step) {
20121
+ function normalizeStep5(step) {
20044
20122
  if (typeof step === "string") {
20045
20123
  return {
20046
20124
  agent: step,
@@ -20086,6 +20164,14 @@ var require_WorkspaceSchema = __commonJS({
20086
20164
  auto_review: obj.auto_review === true,
20087
20165
  auto_review_runner: typeof obj.auto_review_runner === "string" ? obj.auto_review_runner : void 0,
20088
20166
  auto_review_timeout_ms: typeof obj.auto_review_timeout_ms === "number" && obj.auto_review_timeout_ms > 0 ? obj.auto_review_timeout_ms : void 0,
20167
+ ...isOnReject(obj.on_reject) ? { on_reject: { send_back_to: obj.on_reject.send_back_to } } : {},
20168
+ ...typeof obj.timeout_minutes === "number" && obj.timeout_minutes > 0 ? { timeout_minutes: obj.timeout_minutes } : {},
20169
+ ...obj.batches === true ? {
20170
+ batches: {
20171
+ max: typeof obj.max_batches === "number" && obj.max_batches >= 1 ? Math.floor(obj.max_batches) : exports2.DEFAULT_MAX_BATCHES,
20172
+ pause: obj.pause_between_batches === true
20173
+ }
20174
+ } : {},
20089
20175
  human_review: obj.human_review === true,
20090
20176
  ...git && { git }
20091
20177
  };
@@ -20136,9 +20222,57 @@ var require_WorkspaceSchema = __commonJS({
20136
20222
  return "";
20137
20223
  }
20138
20224
  function stepDagId(step) {
20139
- const norm = normalizeStep4(step);
20225
+ const norm = normalizeStep5(step);
20140
20226
  return norm.name ?? norm.agent;
20141
20227
  }
20228
+ exports2.AMBIGUOUS_STEP_INDEX = -2;
20229
+ function findStepIndex2(steps, ref) {
20230
+ const asInt = parseInt(ref, 10);
20231
+ if (!isNaN(asInt) && String(asInt) === ref) {
20232
+ return asInt >= 0 && asInt < steps.length ? asInt : -1;
20233
+ }
20234
+ const byName = steps.findIndex((s) => s.name === ref);
20235
+ if (byName >= 0) {
20236
+ return byName;
20237
+ }
20238
+ const byAgent = steps.map((s, i) => s.agent === ref ? i : -1).filter((i) => i >= 0);
20239
+ if (byAgent.length > 1) {
20240
+ return exports2.AMBIGUOUS_STEP_INDEX;
20241
+ }
20242
+ return byAgent.length === 1 ? byAgent[0] : -1;
20243
+ }
20244
+ function isOnReject(v) {
20245
+ return !!v && typeof v === "object" && typeof v.send_back_to === "string" && v.send_back_to.length > 0;
20246
+ }
20247
+ function sendBackIssues(steps) {
20248
+ const norms = steps.map((s) => normalizeStep5(s));
20249
+ const issues = [];
20250
+ norms.forEach((n, i) => {
20251
+ const target = n.on_reject?.send_back_to;
20252
+ if (!target) {
20253
+ return;
20254
+ }
20255
+ const id = n.name ?? n.agent;
20256
+ const add = (message) => {
20257
+ issues.push({ stepIdx: i, message: `Step "${id}": ${message}` });
20258
+ };
20259
+ if (!n.auto_review) {
20260
+ add("`on_reject` routes an auto-review reject, and this step has `auto_review` off.");
20261
+ return;
20262
+ }
20263
+ const t = findStepIndex2(norms, target);
20264
+ if (t === exports2.AMBIGUOUS_STEP_INDEX) {
20265
+ add(`\`on_reject.send_back_to\` "${target}" is an agent with more than one step; name the step.`);
20266
+ } else if (t < 0) {
20267
+ add(`\`on_reject.send_back_to\` "${target}" is not a step of this pipeline.`);
20268
+ } else if (t >= i) {
20269
+ add(`\`on_reject.send_back_to\` "${target}" must be a step before this one.`);
20270
+ } else if (norms[t].human_review) {
20271
+ add(`\`on_reject.send_back_to\` "${target}" has \`human_review: true\`. Work a human approved goes back through a human \u2014 turn that gate off or drop \`on_reject\`.`);
20272
+ }
20273
+ });
20274
+ return issues;
20275
+ }
20142
20276
  function collectWorkspaceRefIssues3(config) {
20143
20277
  const issues = [];
20144
20278
  const agentIds = new Set(config.agents.map((a) => a.id));
@@ -20156,7 +20290,7 @@ var require_WorkspaceSchema = __commonJS({
20156
20290
  }
20157
20291
  for (const pipeline of config.pipelines) {
20158
20292
  for (const step of pipeline.steps) {
20159
- const norm = normalizeStep4(step);
20293
+ const norm = normalizeStep5(step);
20160
20294
  const id = norm.name ?? norm.agent;
20161
20295
  if (!agentIds.has(norm.agent)) {
20162
20296
  issues.push({
@@ -20282,8 +20416,60 @@ var require_WorkspaceSchema = __commonJS({
20282
20416
  /** Claude tier alias (or any `model:` value) → concrete model id, lowercased keys. */
20283
20417
  model_aliases: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).default({}),
20284
20418
  /** Model id → rate. The key `*` applies to every model of this provider. */
20285
- rates: zod_1.z.record(zod_1.z.string(), ProviderRateSchema).default({})
20419
+ rates: zod_1.z.record(zod_1.z.string(), ProviderRateSchema).default({}),
20420
+ /**
20421
+ * Extra command-line arguments for this runner's CLI, added to every step it
20422
+ * runs — chiefly what a headless agent may do without asking:
20423
+ *
20424
+ * ```yaml
20425
+ * providers:
20426
+ * default:
20427
+ * args: [--permission-mode, acceptEdits, --allowedTools, "Bash(npm test:*)"]
20428
+ * ```
20429
+ *
20430
+ * Committed with the workspace, so the whole team runs with the same
20431
+ * permissions instead of each clone's own `.claude/settings.json`. Flags
20432
+ * that switch permission checks off altogether are refused — see
20433
+ * {@link unsafeRunnerArg}.
20434
+ */
20435
+ args: zod_1.z.array(zod_1.z.string()).default([])
20436
+ }).superRefine((p, ctx) => {
20437
+ const bad = unsafeRunnerArg(p.args);
20438
+ if (bad) {
20439
+ ctx.addIssue({
20440
+ code: "custom",
20441
+ path: ["args"],
20442
+ message: `\`${bad}\` switches the agent's permission checks off, and \`args\` is committed for everyone who runs this workspace \u2014 unattended runs included. Allow the tools it needs instead (\`--allowedTools\` for Claude), or run it in a sandbox of your own with \`runner: custom\`.`
20443
+ });
20444
+ }
20286
20445
  });
20446
+ function unsafeRunnerArg(args) {
20447
+ const flags = /* @__PURE__ */ new Set([
20448
+ "--dangerously-skip-permissions",
20449
+ "--allow-dangerously-skip-permissions",
20450
+ "--dangerously-bypass-approvals-and-sandbox",
20451
+ "--yolo",
20452
+ "--allow-all",
20453
+ "--allow-all-paths"
20454
+ ]);
20455
+ const values = {
20456
+ "--permission-mode": "bypassPermissions",
20457
+ "--sandbox": "danger-full-access",
20458
+ "-s": "danger-full-access"
20459
+ };
20460
+ for (let i = 0; i < args.length; i++) {
20461
+ const [flag, inline] = args[i].split(/=(.*)/s, 2);
20462
+ if (flags.has(flag)) {
20463
+ return args[i];
20464
+ }
20465
+ const unsafe = values[flag];
20466
+ const value = inline ?? args[i + 1];
20467
+ if (unsafe && value === unsafe) {
20468
+ return `${flag} ${unsafe}`;
20469
+ }
20470
+ }
20471
+ return null;
20472
+ }
20287
20473
  exports2.WorkspaceSchema = zod_1.z.object({
20288
20474
  /** Schema version — bump on breaking changes. Currently always "1.0". */
20289
20475
  version: zod_1.z.string().min(1),
@@ -20394,7 +20580,7 @@ var require_PipelineAssembler = __commonJS({
20394
20580
  "use strict";
20395
20581
  Object.defineProperty(exports2, "__esModule", { value: true });
20396
20582
  exports2.PipelineAssembleError = void 0;
20397
- exports2.assemblePipeline = assemblePipeline4;
20583
+ exports2.assemblePipeline = assemblePipeline5;
20398
20584
  exports2.recipePipelineId = recipePipelineId4;
20399
20585
  exports2.pipelineRecipeLabel = pipelineRecipeLabel;
20400
20586
  var WorkspaceSchema_1 = require_WorkspaceSchema();
@@ -20405,7 +20591,7 @@ var require_PipelineAssembler = __commonJS({
20405
20591
  }
20406
20592
  };
20407
20593
  exports2.PipelineAssembleError = PipelineAssembleError4;
20408
- function assemblePipeline4(config, opts) {
20594
+ function assemblePipeline5(config, opts) {
20409
20595
  const recipe = config.recipes.find((r) => r.id === opts.recipeId);
20410
20596
  if (!recipe) {
20411
20597
  const available = config.recipes.map((r) => r.id).join(", ") || "(none defined)";
@@ -20482,14 +20668,40 @@ var require_PipelineAssembler = __commonJS({
20482
20668
  if (auto_review_runner) {
20483
20669
  step.auto_review_runner = auto_review_runner;
20484
20670
  }
20671
+ const on_reject = gate.on_reject ?? norm.on_reject;
20672
+ if (on_reject) {
20673
+ step.on_reject = { send_back_to: on_reject.send_back_to };
20674
+ }
20675
+ const timeout_minutes = gate.timeout_minutes ?? norm.timeout_minutes;
20676
+ if (timeout_minutes !== void 0) {
20677
+ step.timeout_minutes = timeout_minutes;
20678
+ }
20679
+ const batched = gate.batches ?? norm.batches !== void 0;
20680
+ if (batched) {
20681
+ step.batches = true;
20682
+ const max = gate.max_batches ?? norm.batches?.max;
20683
+ if (max !== void 0) {
20684
+ step.max_batches = max;
20685
+ }
20686
+ if (gate.pause_between_batches ?? norm.batches?.pause) {
20687
+ step.pause_between_batches = true;
20688
+ }
20689
+ }
20485
20690
  return step;
20486
20691
  });
20692
+ const sendBackProblems = (0, WorkspaceSchema_1.sendBackIssues)(steps);
20693
+ if (sendBackProblems.length > 0) {
20694
+ throw new PipelineAssembleError4(`Recipe "${recipe.id}" assembled a pipeline whose \`on_reject\` cannot work:
20695
+ ` + sendBackProblems.map((i) => ` - ${i.message}`).join("\n"));
20696
+ }
20487
20697
  const assembled = {
20488
20698
  id: opts.pipelineId ?? recipe.id,
20489
20699
  derived_from: source.id,
20490
20700
  recipe: recipe.id,
20491
20701
  steps,
20492
- on_failure: source.on_failure
20702
+ on_failure: source.on_failure,
20703
+ ...(recipe.max_send_backs ?? source.max_send_backs) !== void 0 ? { max_send_backs: recipe.max_send_backs ?? source.max_send_backs } : {},
20704
+ ...(recipe.autopilot ?? source.autopilot) !== void 0 ? { autopilot: recipe.autopilot ?? source.autopilot } : {}
20493
20705
  };
20494
20706
  const refIssues = (0, WorkspaceSchema_1.collectWorkspaceRefIssues)({
20495
20707
  ...config,
@@ -21495,6 +21707,7 @@ var require_PipelineRunner = __commonJS({
21495
21707
  const step = state.steps[idx];
21496
21708
  const now = (/* @__PURE__ */ new Date()).toISOString();
21497
21709
  const next = clone(state);
21710
+ delete next.sendBacks;
21498
21711
  if (step.status === "approved") {
21499
21712
  for (const i of followersOf(pipeline, idx)) {
21500
21713
  const follower = next.steps[i];
@@ -21552,13 +21765,34 @@ var require_PipelineRunner = __commonJS({
21552
21765
  runner: verdict.runner
21553
21766
  });
21554
21767
  if (verdict.decision === "reject") {
21768
+ const route = autoRejectRoute(next, pipeline, idx, norm, verdict);
21769
+ if ("targetIdx" in route) {
21770
+ const count = next.sendBacks ?? 0;
21771
+ const max = pipeline.max_send_backs ?? WorkspaceSchema_1.DEFAULT_MAX_SEND_BACKS;
21772
+ if (count < max) {
21773
+ sendBack(next, {
21774
+ idx,
21775
+ targetIdx: route.targetIdx,
21776
+ reason: verdict.reason,
21777
+ at: verdict.at,
21778
+ pipeline,
21779
+ source: route.source
21780
+ });
21781
+ next.sendBacks = count + 1;
21782
+ return next;
21783
+ }
21784
+ route.note = `Sent back ${count} time${count === 1 ? "" : "s"} already (max_send_backs: ${max}) \u2014 stopping for a human instead of going round again.`;
21785
+ }
21786
+ const reason = "note" in route && route.note ? `${verdict.reason}
21787
+
21788
+ ${route.note}` : verdict.reason;
21555
21789
  nextStep.status = "rejected";
21556
- nextStep.rejectReason = verdict.reason;
21790
+ nextStep.rejectReason = reason;
21557
21791
  nextStep.history = pushHistory(nextStep.history, {
21558
21792
  kind: "reject",
21559
21793
  at: verdict.at,
21560
21794
  revision: nextStep.revision,
21561
- reason: verdict.reason,
21795
+ reason,
21562
21796
  sentBackToIdx: idx,
21563
21797
  ...nextStep.skill ? { skill: nextStep.skill } : {},
21564
21798
  ...nextStep.chosenModel ? { model: nextStep.chosenModel } : {}
@@ -21574,6 +21808,35 @@ var require_PipelineRunner = __commonJS({
21574
21808
  }
21575
21809
  return advance(next, idx, pipeline);
21576
21810
  }
21811
+ function autoRejectRoute(state, pipeline, idx, norm, verdict) {
21812
+ const label = (i) => `"${(0, RunState_1.stepIdentity)(state.steps[i])}" (step ${i + 1})`;
21813
+ const configured = norm.on_reject?.send_back_to;
21814
+ const cfgIdx = configured ? (0, WorkspaceSchema_1.findStepIndex)(pipeline.steps.map(WorkspaceSchema_1.normalizeStep), configured) : -1;
21815
+ if (configured && !(cfgIdx >= 0 && cfgIdx < idx)) {
21816
+ return { note: `\`on_reject.send_back_to\` "${configured}" is not a step before this one \u2014 fix the pipeline.` };
21817
+ }
21818
+ if (verdict.sendBackTo === void 0) {
21819
+ return cfgIdx >= 0 ? { targetIdx: cfgIdx, source: "config" } : {};
21820
+ }
21821
+ if (verdict.sendBackTo === null) {
21822
+ return {};
21823
+ }
21824
+ const asked = String(verdict.sendBackTo);
21825
+ const v = (0, WorkspaceSchema_1.findStepIndex)(state.steps, asked);
21826
+ if (v === idx) {
21827
+ return {};
21828
+ }
21829
+ if (v < 0 || v > idx) {
21830
+ return { note: `The validator asked to send this back to "${asked}", which is not a step before this one.` };
21831
+ }
21832
+ if (v !== cfgIdx) {
21833
+ const allowed = cfgIdx >= 0 ? `only ${label(cfgIdx)} is` : "no step is (see `on_reject.send_back_to`)";
21834
+ return {
21835
+ note: `The validator suggests sending this back to ${label(v)}; ${allowed} sent back automatically. Send it back by hand if you agree.`
21836
+ };
21837
+ }
21838
+ return { targetIdx: v, source: "verdict" };
21839
+ }
21577
21840
  function retryAutoReview(args) {
21578
21841
  const { pipeline } = args;
21579
21842
  const state = alignedOrThrow(args.state, pipeline);
@@ -21596,6 +21859,7 @@ var require_PipelineRunner = __commonJS({
21596
21859
  throw new PipelineRunError3(`Step "${step.agent}" no longer has auto_review enabled in pipeline "${pipeline.id}".`);
21597
21860
  }
21598
21861
  const next = clone(state);
21862
+ delete next.sendBacks;
21599
21863
  next.steps[idx] = {
21600
21864
  ...step,
21601
21865
  status: "awaiting_auto_review",
@@ -21616,7 +21880,9 @@ var require_PipelineRunner = __commonJS({
21616
21880
  if (step.status !== "awaiting_review") {
21617
21881
  throw new PipelineRunError3(`Cannot approve step "${step.agent}": status is "${step.status}", expected "awaiting_review"`);
21618
21882
  }
21619
- return advance(clone(state), idx, pipeline);
21883
+ const next = clone(state);
21884
+ delete next.sendBacks;
21885
+ return advance(next, idx, pipeline);
21620
21886
  }
21621
21887
  function rejectStep3(args) {
21622
21888
  const { state, reason, targetIdx, pipeline } = args;
@@ -21625,75 +21891,19 @@ var require_PipelineRunner = __commonJS({
21625
21891
  if (!step) {
21626
21892
  throw new PipelineRunError3(`No step at index ${idx}`);
21627
21893
  }
21628
- if (step.status !== "awaiting_review") {
21629
- throw new PipelineRunError3(`Cannot reject step "${step.agent}": status is "${step.status}", expected "awaiting_review"`);
21630
- }
21631
21894
  const now = (/* @__PURE__ */ new Date()).toISOString();
21632
21895
  const isCascade = typeof targetIdx === "number" && targetIdx >= 0 && targetIdx < idx;
21896
+ if (step.status !== "awaiting_review" && !(isCascade && step.status === "rejected")) {
21897
+ throw new PipelineRunError3(`Cannot reject step "${step.agent}": status is "${step.status}", expected "awaiting_review"`);
21898
+ }
21633
21899
  if (isCascade) {
21634
21900
  const next2 = clone(state);
21635
- const blame = `Rejected at step ${idx + 1} (${step.agent})${reason ? `: ${reason}` : ""}`;
21636
- const rejectedHistory = pushHistory(next2.steps[idx].history, {
21637
- kind: "reject",
21638
- at: now,
21639
- revision: next2.steps[idx].revision,
21640
- reason,
21641
- sentBackToIdx: targetIdx,
21642
- ...next2.steps[idx].skill ? { skill: next2.steps[idx].skill } : {},
21643
- ...next2.steps[idx].chosenModel ? { model: next2.steps[idx].chosenModel } : {}
21644
- });
21645
- const usesDag = pipeline ? pipeline.steps.map(WorkspaceSchema_1.normalizeStep).some((s) => s.depends_on.length > 0) : false;
21646
- const targetIdxN = targetIdx;
21647
- const resetIndices = usesDag && pipeline ? collectDagResetSet(pipeline, state, targetIdxN, idx) : sequentialRange(targetIdxN, idx);
21648
- for (const i of resetIndices) {
21649
- const s = next2.steps[i];
21650
- if (i === targetIdxN) {
21651
- const newRev = s.revision + 1;
21652
- next2.steps[i] = {
21653
- ...s,
21654
- status: "awaiting_work",
21655
- revision: newRev,
21656
- feedback: blame,
21657
- rejectReason: void 0,
21658
- autoReviewVerdict: void 0,
21659
- artifactsProduced: [],
21660
- finishedAt: void 0,
21661
- startedAt: now,
21662
- history: pushHistory(s.history, {
21663
- kind: "rerun",
21664
- at: now,
21665
- revision: newRev,
21666
- feedback: blame
21667
- })
21668
- };
21669
- } else if (i === idx) {
21670
- next2.steps[i] = {
21671
- ...s,
21672
- status: "pending",
21673
- rejectReason: void 0,
21674
- autoReviewVerdict: void 0,
21675
- artifactsProduced: [],
21676
- startedAt: void 0,
21677
- finishedAt: void 0,
21678
- history: rejectedHistory
21679
- };
21680
- } else {
21681
- next2.steps[i] = {
21682
- ...s,
21683
- status: "pending",
21684
- rejectReason: void 0,
21685
- autoReviewVerdict: void 0,
21686
- artifactsProduced: [],
21687
- startedAt: void 0,
21688
- finishedAt: void 0
21689
- };
21690
- }
21691
- }
21692
- next2.currentStepIdx = targetIdx;
21693
- next2.status = "running";
21901
+ sendBack(next2, { idx, targetIdx, reason, at: now, pipeline });
21902
+ delete next2.sendBacks;
21694
21903
  return next2;
21695
21904
  }
21696
21905
  const next = clone(state);
21906
+ delete next.sendBacks;
21697
21907
  next.steps[idx] = {
21698
21908
  ...step,
21699
21909
  status: "rejected",
@@ -21726,6 +21936,7 @@ var require_PipelineRunner = __commonJS({
21726
21936
  }
21727
21937
  const now = (/* @__PURE__ */ new Date()).toISOString();
21728
21938
  const next = clone(state);
21939
+ delete next.sendBacks;
21729
21940
  const newRev = step.revision + 1;
21730
21941
  const carriedFeedback = feedback ?? step.feedback;
21731
21942
  next.steps[idx] = {
@@ -21788,6 +21999,7 @@ var require_PipelineRunner = __commonJS({
21788
21999
  }
21789
22000
  const now = (/* @__PURE__ */ new Date()).toISOString();
21790
22001
  const next = clone(state);
22002
+ delete next.sendBacks;
21791
22003
  const normalized = pipeline.steps.map(WorkspaceSchema_1.normalizeStep);
21792
22004
  const usesDag = normalized.some((s) => s.depends_on.length > 0);
21793
22005
  const upper = state.status === "completed" ? pipeline.steps.length - 1 : state.currentStepIdx;
@@ -21931,6 +22143,7 @@ var require_PipelineRunner = __commonJS({
21931
22143
  const step = state.steps[idx];
21932
22144
  const now = (/* @__PURE__ */ new Date()).toISOString();
21933
22145
  const next = clone(state);
22146
+ delete next.sendBacks;
21934
22147
  const newRev = step.revision + 1;
21935
22148
  const carriedFeedback = feedback ?? step.feedback;
21936
22149
  next.steps[idx] = {
@@ -22077,6 +22290,70 @@ var require_PipelineRunner = __commonJS({
22077
22290
  next.status = "completed";
22078
22291
  }
22079
22292
  }
22293
+ function sendBack(next, args) {
22294
+ const { idx, targetIdx, reason, at: now, pipeline, source } = args;
22295
+ const before = clone(next);
22296
+ const step = next.steps[idx];
22297
+ const blame = `Rejected at step ${idx + 1} (${step.agent})${reason ? `: ${reason}` : ""}`;
22298
+ const rejectedHistory = pushHistory(step.history, {
22299
+ kind: "reject",
22300
+ at: now,
22301
+ revision: step.revision,
22302
+ reason,
22303
+ sentBackToIdx: targetIdx,
22304
+ ...source ? { source } : {},
22305
+ ...step.skill ? { skill: step.skill } : {},
22306
+ ...step.chosenModel ? { model: step.chosenModel } : {}
22307
+ });
22308
+ const usesDag = pipeline ? pipeline.steps.map(WorkspaceSchema_1.normalizeStep).some((s) => s.depends_on.length > 0) : false;
22309
+ const resetIndices = usesDag && pipeline ? collectDagResetSet(pipeline, before, targetIdx, idx) : sequentialRange(targetIdx, idx);
22310
+ for (const i of resetIndices) {
22311
+ const s = next.steps[i];
22312
+ if (i === targetIdx) {
22313
+ const newRev = s.revision + 1;
22314
+ next.steps[i] = {
22315
+ ...s,
22316
+ status: "awaiting_work",
22317
+ revision: newRev,
22318
+ feedback: blame,
22319
+ rejectReason: void 0,
22320
+ autoReviewVerdict: void 0,
22321
+ artifactsProduced: [],
22322
+ finishedAt: void 0,
22323
+ startedAt: now,
22324
+ history: pushHistory(s.history, {
22325
+ kind: "rerun",
22326
+ at: now,
22327
+ revision: newRev,
22328
+ feedback: blame
22329
+ })
22330
+ };
22331
+ } else if (i === idx) {
22332
+ next.steps[i] = {
22333
+ ...s,
22334
+ status: "pending",
22335
+ rejectReason: void 0,
22336
+ autoReviewVerdict: void 0,
22337
+ artifactsProduced: [],
22338
+ startedAt: void 0,
22339
+ finishedAt: void 0,
22340
+ history: rejectedHistory
22341
+ };
22342
+ } else {
22343
+ next.steps[i] = {
22344
+ ...s,
22345
+ status: "pending",
22346
+ rejectReason: void 0,
22347
+ autoReviewVerdict: void 0,
22348
+ artifactsProduced: [],
22349
+ startedAt: void 0,
22350
+ finishedAt: void 0
22351
+ };
22352
+ }
22353
+ }
22354
+ next.currentStepIdx = targetIdx;
22355
+ next.status = "running";
22356
+ }
22080
22357
  function clone(value) {
22081
22358
  return JSON.parse(JSON.stringify(value));
22082
22359
  }
@@ -25489,7 +25766,7 @@ var require_PlanGenerator = __commonJS({
25489
25766
  Object.defineProperty(exports2, "__esModule", { value: true });
25490
25767
  exports2.generatePlan = generatePlan;
25491
25768
  exports2.renderPlanMarkdown = renderPlanMarkdown;
25492
- function normalizeStep4(step) {
25769
+ function normalizeStep5(step) {
25493
25770
  if (typeof step === "string") {
25494
25771
  return { name: step };
25495
25772
  }
@@ -25605,7 +25882,7 @@ var require_PlanGenerator = __commonJS({
25605
25882
  }
25606
25883
  function generatePlan(epicId, context, pipeline, workspace) {
25607
25884
  const complexity = inferScopeComplexity(context.scope);
25608
- const steps = (pipeline.steps || []).map(normalizeStep4);
25885
+ const steps = (pipeline.steps || []).map(normalizeStep5);
25609
25886
  const agentCountPerPhase = allocateAgentCount(complexity, steps.length);
25610
25887
  const phaseAllocations = steps.map((step) => {
25611
25888
  const agentId = step.agent || "unknown";
@@ -26727,6 +27004,11 @@ var require_EpicStepEdit = __commonJS({
26727
27004
  }
26728
27005
  function commitEpicStepEdit2(args) {
26729
27006
  const { workspaceRoot, doc, plan } = args;
27007
+ const sendBack = (0, WorkspaceSchema_1.sendBackIssues)(plan.pipeline.steps);
27008
+ if (sendBack.length > 0) {
27009
+ throw new EpicStepEditError2(`This edit would leave \`on_reject\` pointing at a step it cannot send work to:
27010
+ ` + sendBack.map((i) => ` - ${i.message}`).join("\n"));
27011
+ }
26730
27012
  args.writeWorkspace(plan.pipeline);
26731
27013
  try {
26732
27014
  RunStateStore_1.RunStateStore.save(workspaceRoot, plan.runState);
@@ -26773,6 +27055,7 @@ var require_EpicStepEdit = __commonJS({
26773
27055
  delete next.auto_review;
26774
27056
  delete next.auto_review_runner;
26775
27057
  delete next.auto_review_timeout_ms;
27058
+ delete next.on_reject;
26776
27059
  }
26777
27060
  const nextRaw = [...raw];
26778
27061
  nextRaw[idx] = next;
@@ -28681,6 +28964,69 @@ var require_resolveCommand = __commonJS({
28681
28964
  }
28682
28965
  });
28683
28966
 
28967
+ // ../core/dist/runner/processTree.js
28968
+ var require_processTree = __commonJS({
28969
+ "../core/dist/runner/processTree.js"(exports2) {
28970
+ "use strict";
28971
+ Object.defineProperty(exports2, "__esModule", { value: true });
28972
+ exports2.STOP_GRACE_MS = void 0;
28973
+ exports2.processGroupSpawnOptions = processGroupSpawnOptions;
28974
+ exports2.stopOnAbort = stopOnAbort;
28975
+ exports2.stopProcessTree = stopProcessTree;
28976
+ var child_process_1 = require("child_process");
28977
+ exports2.STOP_GRACE_MS = 1e4;
28978
+ function processGroupSpawnOptions() {
28979
+ return process.platform === "win32" ? {} : { detached: true };
28980
+ }
28981
+ function stopOnAbort(proc, signal, graceMs = exports2.STOP_GRACE_MS) {
28982
+ if (!signal) {
28983
+ return () => {
28984
+ };
28985
+ }
28986
+ let timer;
28987
+ const onAbort = () => {
28988
+ stopProcessTree(proc, "SIGTERM");
28989
+ timer = setTimeout(() => stopProcessTree(proc, "SIGKILL"), graceMs);
28990
+ timer.unref?.();
28991
+ };
28992
+ if (signal.aborted) {
28993
+ onAbort();
28994
+ } else {
28995
+ signal.addEventListener("abort", onAbort, { once: true });
28996
+ }
28997
+ return () => {
28998
+ signal.removeEventListener("abort", onAbort);
28999
+ if (timer) {
29000
+ clearTimeout(timer);
29001
+ stopProcessTree(proc, "SIGKILL");
29002
+ }
29003
+ };
29004
+ }
29005
+ function stopProcessTree(proc, sig) {
29006
+ const pid = proc.pid;
29007
+ if (!pid) {
29008
+ return;
29009
+ }
29010
+ if (process.platform === "win32") {
29011
+ try {
29012
+ (0, child_process_1.spawn)("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true }).on("error", () => {
29013
+ });
29014
+ } catch {
29015
+ }
29016
+ return;
29017
+ }
29018
+ try {
29019
+ process.kill(-pid, sig);
29020
+ } catch {
29021
+ try {
29022
+ proc.kill(sig);
29023
+ } catch {
29024
+ }
29025
+ }
29026
+ }
29027
+ }
29028
+ });
29029
+
28684
29030
  // ../core/dist/runner/DefaultRunner.js
28685
29031
  var require_DefaultRunner = __commonJS({
28686
29032
  "../core/dist/runner/DefaultRunner.js"(exports2) {
@@ -28693,6 +29039,7 @@ var require_DefaultRunner = __commonJS({
28693
29039
  var mcp_1 = require_mcp();
28694
29040
  var ndjson_1 = require_ndjson();
28695
29041
  var resolveCommand_1 = require_resolveCommand();
29042
+ var processTree_1 = require_processTree();
28696
29043
  var DefaultRunner3 = class {
28697
29044
  opts;
28698
29045
  /**
@@ -28731,6 +29078,10 @@ var require_DefaultRunner = __commonJS({
28731
29078
  ctx.skill,
28732
29079
  ...model ? ["--model", model] : [],
28733
29080
  ...this.opts.extraArgs ?? [],
29081
+ ...ctx.extraArgs ?? [],
29082
+ // `--allowedTools` and friends take any number of values, and would take
29083
+ // the prompt as one more; `--` ends the options.
29084
+ "--",
28734
29085
  userMessage
28735
29086
  ];
28736
29087
  const cmd = (0, resolveCommand_1.resolveCommand)(bin);
@@ -28740,8 +29091,10 @@ var require_DefaultRunner = __commonJS({
28740
29091
  // inside a Claude Code session — a fresh `claude` rejects it as invalid.
28741
29092
  // ctx.env (workspace-configured) is layered last so explicit keys win.
28742
29093
  env: (0, claudeEnv_1.buildClaudeSpawnEnv)(ctx.env),
28743
- stdio: ["ignore", "pipe", "pipe"]
29094
+ stdio: ["ignore", "pipe", "pipe"],
29095
+ ...(0, processTree_1.processGroupSpawnOptions)()
28744
29096
  });
29097
+ const detach = (0, processTree_1.stopOnAbort)(proc, ctx.signal);
28745
29098
  let finalText = "";
28746
29099
  let costUsd;
28747
29100
  const handleEvent = (evt) => {
@@ -28767,11 +29120,13 @@ var require_DefaultRunner = __commonJS({
28767
29120
  });
28768
29121
  return new Promise((resolve5) => {
28769
29122
  proc.on("error", (err) => {
29123
+ detach();
28770
29124
  ctx.onError(`Failed to spawn ${bin}: ${err.message}
28771
29125
  `);
28772
29126
  resolve5({ success: false, output: finalText, costUsd });
28773
29127
  });
28774
29128
  proc.on("close", (code) => {
29129
+ detach();
28775
29130
  sink.flush();
28776
29131
  resolve5({ success: code === 0, output: finalText, costUsd });
28777
29132
  });
@@ -28842,6 +29197,7 @@ var require_CodexRunner = __commonJS({
28842
29197
  var ndjson_1 = require_ndjson();
28843
29198
  var models_1 = require_models();
28844
29199
  var resolveCommand_1 = require_resolveCommand();
29200
+ var processTree_1 = require_processTree();
28845
29201
  var CodexRunner2 = class {
28846
29202
  opts;
28847
29203
  /**
@@ -28878,14 +29234,17 @@ var require_CodexRunner = __commonJS({
28878
29234
  this.opts.sandbox ?? "workspace-write",
28879
29235
  ...model ? ["--model", model] : [],
28880
29236
  ...this.opts.extraArgs ?? [],
29237
+ ...ctx.extraArgs ?? [],
28881
29238
  buildPrompt(ctx)
28882
29239
  ];
28883
29240
  const cmd = (0, resolveCommand_1.resolveCommand)(bin);
28884
29241
  const proc = (0, child_process_1.spawn)(cmd.command, [...cmd.args, ...args], {
28885
29242
  cwd: ctx.workspaceRoot,
28886
29243
  env: { ...process.env, ...ctx.env },
28887
- stdio: ["ignore", "pipe", "pipe"]
29244
+ stdio: ["ignore", "pipe", "pipe"],
29245
+ ...(0, processTree_1.processGroupSpawnOptions)()
28888
29246
  });
29247
+ const detach = (0, processTree_1.stopOnAbort)(proc, ctx.signal);
28889
29248
  let streamed = "";
28890
29249
  let lastMessage = "";
28891
29250
  let sawDelta = false;
@@ -28937,11 +29296,13 @@ var require_CodexRunner = __commonJS({
28937
29296
  proc.stderr.on("data", (d) => ctx.onError(d.toString("utf8")));
28938
29297
  return new Promise((resolve5) => {
28939
29298
  proc.on("error", (err) => {
29299
+ detach();
28940
29300
  ctx.onError(`Failed to spawn ${bin}: ${err.message}
28941
29301
  `);
28942
29302
  resolve5({ success: false, output: "", usage });
28943
29303
  });
28944
29304
  proc.on("close", (code) => {
29305
+ detach();
28945
29306
  sink.flush();
28946
29307
  resolve5({
28947
29308
  success: code === 0,
@@ -29014,6 +29375,7 @@ var require_CopilotRunner = __commonJS({
29014
29375
  var ndjson_1 = require_ndjson();
29015
29376
  var models_1 = require_models();
29016
29377
  var resolveCommand_1 = require_resolveCommand();
29378
+ var processTree_1 = require_processTree();
29017
29379
  var GRAPH_SERVERS2 = ["ast-graph", "codegraph"];
29018
29380
  var CopilotRunner2 = class {
29019
29381
  opts;
@@ -29051,14 +29413,17 @@ var require_CopilotRunner = __commonJS({
29051
29413
  ...this.opts.keepBuiltinMcps ? [] : ["--disable-builtin-mcps"],
29052
29414
  ...graph ? ["--additional-mcp-config", copilotMcpConfig(graph)] : [],
29053
29415
  ...model ? ["--model", model] : [],
29054
- ...this.opts.extraArgs ?? []
29416
+ ...this.opts.extraArgs ?? [],
29417
+ ...ctx.extraArgs ?? []
29055
29418
  ];
29056
29419
  const cmd = (0, resolveCommand_1.resolveCommand)(bin);
29057
29420
  const proc = (0, child_process_1.spawn)(cmd.command, [...cmd.args, ...args], {
29058
29421
  cwd: ctx.workspaceRoot,
29059
29422
  env: { ...process.env, ...ctx.env },
29060
- stdio: ["pipe", "pipe", "pipe"]
29423
+ stdio: ["pipe", "pipe", "pipe"],
29424
+ ...(0, processTree_1.processGroupSpawnOptions)()
29061
29425
  });
29426
+ const detach = (0, processTree_1.stopOnAbort)(proc, ctx.signal);
29062
29427
  let streamed = "";
29063
29428
  let lastMessage = "";
29064
29429
  const deltaSeen = /* @__PURE__ */ new Set();
@@ -29120,11 +29485,13 @@ var require_CopilotRunner = __commonJS({
29120
29485
  proc.stdin.end((0, CodexRunner_1.buildPrompt)(ctx));
29121
29486
  return new Promise((resolve5) => {
29122
29487
  proc.on("error", (err) => {
29488
+ detach();
29123
29489
  ctx.onError(`Failed to spawn ${bin}: ${err.message}
29124
29490
  `);
29125
29491
  resolve5({ success: false, output: "" });
29126
29492
  });
29127
29493
  proc.on("close", (code) => {
29494
+ detach();
29128
29495
  sink.flush();
29129
29496
  resolve5({
29130
29497
  success: code === 0,
@@ -30374,7 +30741,10 @@ var require_promptComposer = __commonJS({
30374
30741
  const inlineInstructions = !!instructions && !harness.projectInstructions;
30375
30742
  const language = artifactLanguage?.trim() || null;
30376
30743
  const depth = (0, strictMode_1.strictModeSection)(input.strictMode === false ? false : true);
30377
- if (!inlinePersona && !inlineInstructions && !language && !depth) {
30744
+ const history = input.runHistory ? `## Prior revisions
30745
+
30746
+ Earlier rejections and feedback for this step: \`${input.runHistory.file}\` \u2192 \`steps[${input.runHistory.stepIdx}].history\`. Do not bring back what they rejected.` : null;
30747
+ if (!inlinePersona && !inlineInstructions && !language && !depth && !history) {
30378
30748
  return {
30379
30749
  text: skills,
30380
30750
  included: { persona: false, instructions: false, language: false, depth: false }
@@ -30399,6 +30769,9 @@ var require_promptComposer = __commonJS({
30399
30769
  if (depth) {
30400
30770
  parts.push(depth);
30401
30771
  }
30772
+ if (history) {
30773
+ parts.push(history);
30774
+ }
30402
30775
  const skillText = inlinePersona ? stripPersonaDirectives(skills) : skills;
30403
30776
  parts.push(["## Phase Behavior", "", skillText.trim()].join("\n"));
30404
30777
  return {
@@ -30750,6 +31123,235 @@ var require_commandBodyFreshness = __commonJS({
30750
31123
  }
30751
31124
  });
30752
31125
 
31126
+ // ../core/dist/runs/autopilotPreflight.js
31127
+ var require_autopilotPreflight = __commonJS({
31128
+ "../core/dist/runs/autopilotPreflight.js"(exports2) {
31129
+ "use strict";
31130
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m, k, k2) {
31131
+ if (k2 === void 0) k2 = k;
31132
+ var desc = Object.getOwnPropertyDescriptor(m, k);
31133
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
31134
+ desc = { enumerable: true, get: function() {
31135
+ return m[k];
31136
+ } };
31137
+ }
31138
+ Object.defineProperty(o, k2, desc);
31139
+ } : function(o, m, k, k2) {
31140
+ if (k2 === void 0) k2 = k;
31141
+ o[k2] = m[k];
31142
+ });
31143
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? function(o, v) {
31144
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31145
+ } : function(o, v) {
31146
+ o["default"] = v;
31147
+ });
31148
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ function() {
31149
+ var ownKeys = function(o) {
31150
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
31151
+ var ar = [];
31152
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
31153
+ return ar;
31154
+ };
31155
+ return ownKeys(o);
31156
+ };
31157
+ return function(mod) {
31158
+ if (mod && mod.__esModule) return mod;
31159
+ var result = {};
31160
+ if (mod != null) {
31161
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31162
+ }
31163
+ __setModuleDefault(result, mod);
31164
+ return result;
31165
+ };
31166
+ }();
31167
+ Object.defineProperty(exports2, "__esModule", { value: true });
31168
+ exports2.AUTOPILOT_ENV = exports2.APPROVAL_GATE_SCRIPT = void 0;
31169
+ exports2.findApprovalGate = findApprovalGate2;
31170
+ exports2.autopilotPreflight = autopilotPreflight;
31171
+ exports2.hasEmptyCiChecks = hasEmptyCiChecks2;
31172
+ exports2.emptyCiChecksMessage = emptyCiChecksMessage2;
31173
+ var child_process_1 = require("child_process");
31174
+ var fs14 = __importStar(require("fs"));
31175
+ var os3 = __importStar(require("os"));
31176
+ var path17 = __importStar(require("path"));
31177
+ var claudeHome_1 = require_claudeHome();
31178
+ var EpicScaffold_1 = require_EpicScaffold();
31179
+ var WorkspaceSchema_1 = require_WorkspaceSchema();
31180
+ exports2.APPROVAL_GATE_SCRIPT = "aidlc-approval-gate";
31181
+ exports2.AUTOPILOT_ENV = "AIDLC_AUTOPILOT";
31182
+ var PROTECTED_BRANCHES = ["main", "master"];
31183
+ function findApprovalGate2(root, opts = {}) {
31184
+ const files = [
31185
+ path17.join(root, ".claude", "settings.json"),
31186
+ path17.join(root, ".claude", "settings.local.json"),
31187
+ path17.join(opts.userConfigDir ?? (0, claudeHome_1.claudeConfigDir)(), "settings.json")
31188
+ ];
31189
+ let brokenReason = null;
31190
+ for (const file of files) {
31191
+ let settings = null;
31192
+ try {
31193
+ settings = JSON.parse(fs14.readFileSync(file, "utf8"));
31194
+ } catch {
31195
+ continue;
31196
+ }
31197
+ const groups = settings?.hooks?.PreToolUse;
31198
+ if (!Array.isArray(groups)) {
31199
+ continue;
31200
+ }
31201
+ for (const g of groups) {
31202
+ if (!Array.isArray(g?.hooks) || !matcherCoversBash(g.matcher)) {
31203
+ continue;
31204
+ }
31205
+ for (const h of g.hooks) {
31206
+ if (typeof h?.command !== "string" || !h.command.includes(exports2.APPROVAL_GATE_SCRIPT)) {
31207
+ continue;
31208
+ }
31209
+ const script = hookScriptPath(h.command, root);
31210
+ if (!script) {
31211
+ continue;
31212
+ }
31213
+ if (!fs14.existsSync(script)) {
31214
+ brokenReason = `${rel(root, file)} runs ${script}, which does not exist.`;
31215
+ continue;
31216
+ }
31217
+ if (runsScriptDirectly(h.command) && process.platform !== "win32" && !isExecutable(script)) {
31218
+ brokenReason = `${rel(root, script)} is registered in ${rel(root, file)} but is not executable (chmod +x it).`;
31219
+ continue;
31220
+ }
31221
+ return { found: true, settingsFile: file, script };
31222
+ }
31223
+ }
31224
+ }
31225
+ return {
31226
+ found: false,
31227
+ reason: brokenReason ?? 'no PreToolUse hook running .claude/hooks/aidlc-approval-gate.py on Bash in .claude/settings.json, .claude/settings.local.json or your user settings \u2014 see ONBOARDING.md, "Turn on the approval gate".'
31228
+ };
31229
+ }
31230
+ function autopilotPreflight(args) {
31231
+ const { root, state, config } = args;
31232
+ const problems = [];
31233
+ const git = (gitArgs) => {
31234
+ try {
31235
+ return (0, child_process_1.execFileSync)("git", gitArgs, {
31236
+ cwd: root,
31237
+ encoding: "utf8",
31238
+ stdio: ["ignore", "pipe", "ignore"],
31239
+ maxBuffer: 64 * 1024 * 1024
31240
+ });
31241
+ } catch {
31242
+ return null;
31243
+ }
31244
+ };
31245
+ if (git(["rev-parse", "--is-inside-work-tree"])?.trim() !== "true") {
31246
+ problems.push("This is not a git checkout. Autopilot works on a branch of its own, so it needs git.");
31247
+ } else {
31248
+ const branch = git(["symbolic-ref", "--short", "-q", "HEAD"])?.trim();
31249
+ const remoteDefault = git(["symbolic-ref", "--short", "-q", "refs/remotes/origin/HEAD"])?.trim().replace(/^origin\//, "");
31250
+ const protectedBranches = /* @__PURE__ */ new Set([...PROTECTED_BRANCHES, ...remoteDefault ? [remoteDefault] : []]);
31251
+ if (!branch) {
31252
+ problems.push("HEAD is detached. Check out a branch for this epic (git switch -c <branch>) so the agent's commits land on it.");
31253
+ } else if (protectedBranches.has(branch)) {
31254
+ problems.push(`You are on "${branch}". Autopilot does not commit to it \u2014 switch to a branch for this epic (git switch -c <branch>).`);
31255
+ }
31256
+ const skip = [
31257
+ toPosix(path17.relative(root, path17.join((0, EpicScaffold_1.epicsRoot)(root, config), state.runId))),
31258
+ ".aidlc"
31259
+ ];
31260
+ const dirty = dirtyPaths(git(["status", "--porcelain=v1", "-z", "--untracked-files=all"]) ?? "").filter((p) => !skip.some((s) => p === s || p.startsWith(`${s}/`)));
31261
+ if (dirty.length > 0) {
31262
+ const shown = dirty.slice(0, 5).join(", ") + (dirty.length > 5 ? `, and ${dirty.length - 5} more` : "");
31263
+ problems.push(`The working tree has uncommitted changes (${shown}). Commit or stash them first: the agent would build on them, and its own changes would be mixed in with them.`);
31264
+ }
31265
+ }
31266
+ const claudeSteps = new Set(state.steps.filter((s) => s.status !== "approved").map((s) => config.agents.find((a) => a.id === s.agent)).filter((a) => a?.runner === "default").map((a) => a.id));
31267
+ if (claudeSteps.size > 0) {
31268
+ const gate = findApprovalGate2(root, { userConfigDir: args.userConfigDir });
31269
+ if (!gate.found) {
31270
+ problems.push(`The approval gate is not on: ${gate.reason}`);
31271
+ }
31272
+ }
31273
+ args.pipeline.steps.forEach((s, i) => {
31274
+ if (state.steps[i]?.status === "approved") {
31275
+ return;
31276
+ }
31277
+ const norm = (0, WorkspaceSchema_1.normalizeStep)(s);
31278
+ const runner = norm.auto_review ? norm.auto_review_runner : void 0;
31279
+ if (runner && hasEmptyCiChecks2(root, runner)) {
31280
+ problems.push(emptyCiChecksMessage2(runner, norm.name ?? norm.agent));
31281
+ }
31282
+ });
31283
+ return problems;
31284
+ }
31285
+ var EMPTY_CHECKS = /\bCHECKS\s*=\s*\[\s*\]/;
31286
+ function hasEmptyCiChecks2(root, runner) {
31287
+ let text;
31288
+ try {
31289
+ text = fs14.readFileSync(path17.isAbsolute(runner) ? runner : path17.join(root, runner), "utf8");
31290
+ } catch {
31291
+ return false;
31292
+ }
31293
+ const code = text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
31294
+ return EMPTY_CHECKS.test(code);
31295
+ }
31296
+ function emptyCiChecksMessage2(runner, step) {
31297
+ return `${runner} has no commands in CHECKS, so it passes whatever "${step}" builds. Add your lint, typecheck and test commands to it before leaving a run alone.`;
31298
+ }
31299
+ function dirtyPaths(out2) {
31300
+ const parts = out2.split("\0");
31301
+ const paths = [];
31302
+ for (let i = 0; i < parts.length; i++) {
31303
+ const entry = parts[i];
31304
+ if (entry.length < 4) {
31305
+ continue;
31306
+ }
31307
+ paths.push(entry.slice(3));
31308
+ if (entry[0] === "R" || entry[0] === "C") {
31309
+ i++;
31310
+ }
31311
+ }
31312
+ return paths;
31313
+ }
31314
+ function matcherCoversBash(matcher) {
31315
+ if (!matcher || matcher === "*") {
31316
+ return true;
31317
+ }
31318
+ try {
31319
+ return new RegExp(`^(?:${matcher})$`).test("Bash");
31320
+ } catch {
31321
+ return matcher.split("|").map((m) => m.trim()).includes("Bash");
31322
+ }
31323
+ }
31324
+ function hookScriptPath(command, root) {
31325
+ const expanded = command.replace(/\$\{?CLAUDE_PROJECT_DIR\}?/g, root).replace(/["']/g, "");
31326
+ const token = expanded.split(/\s+/).find((t) => t.includes(exports2.APPROVAL_GATE_SCRIPT));
31327
+ if (!token) {
31328
+ return null;
31329
+ }
31330
+ const p = token.startsWith("~/") ? path17.join(os3.homedir(), token.slice(2)) : token;
31331
+ return path17.resolve(root, p);
31332
+ }
31333
+ function runsScriptDirectly(command) {
31334
+ const first = command.replace(/["']/g, "").trim().split(/\s+/)[0] ?? "";
31335
+ return first.includes(exports2.APPROVAL_GATE_SCRIPT);
31336
+ }
31337
+ function isExecutable(file) {
31338
+ try {
31339
+ fs14.accessSync(file, fs14.constants.X_OK);
31340
+ return true;
31341
+ } catch {
31342
+ return false;
31343
+ }
31344
+ }
31345
+ function rel(root, p) {
31346
+ const r = path17.relative(root, p);
31347
+ return r.startsWith("..") ? p : toPosix(r);
31348
+ }
31349
+ function toPosix(p) {
31350
+ return p.split(path17.sep).join("/");
31351
+ }
31352
+ }
31353
+ });
31354
+
30753
31355
  // ../core/dist/runs/GitRunStateStore.js
30754
31356
  var require_GitRunStateStore = __commonJS({
30755
31357
  "../core/dist/runs/GitRunStateStore.js"(exports2) {
@@ -31374,20 +31976,323 @@ Expected a JS/TS module at "${scriptPath}" with a default-exported function retu
31374
31976
  at,
31375
31977
  runner
31376
31978
  };
31377
- } finally {
31378
- if (timer) {
31379
- clearTimeout(timer);
31979
+ } finally {
31980
+ if (timer) {
31981
+ clearTimeout(timer);
31982
+ }
31983
+ }
31984
+ if (!raw || raw.decision !== "pass" && raw.decision !== "reject" || typeof raw.reason !== "string") {
31985
+ return {
31986
+ decision: "reject",
31987
+ reason: `Auto-reviewer returned malformed verdict: ${JSON.stringify(raw)}`,
31988
+ at,
31989
+ runner
31990
+ };
31991
+ }
31992
+ const verdict = { decision: raw.decision, reason: raw.reason, at, runner };
31993
+ const sendBackTo = raw.sendBackTo;
31994
+ if (sendBackTo !== void 0) {
31995
+ verdict.sendBackTo = sendBackTo === null || typeof sendBackTo === "string" || typeof sendBackTo === "number" ? sendBackTo : JSON.stringify(sendBackTo);
31996
+ }
31997
+ return verdict;
31998
+ }
31999
+ }
32000
+ });
32001
+
32002
+ // ../core/dist/runs/revisionFeedback.js
32003
+ var require_revisionFeedback = __commonJS({
32004
+ "../core/dist/runs/revisionFeedback.js"(exports2) {
32005
+ "use strict";
32006
+ Object.defineProperty(exports2, "__esModule", { value: true });
32007
+ exports2.revisionFeedback = revisionFeedback;
32008
+ function revisionFeedback(state, stepIdx) {
32009
+ const step = state.steps[stepIdx];
32010
+ if (!step || step.revision <= 1) {
32011
+ return null;
32012
+ }
32013
+ const history = step.history ?? [];
32014
+ const prev = step.revision - 1;
32015
+ const reject = findLast(history, (h) => h.kind === "reject" && h.revision === prev && h.sentBackToIdx === stepIdx);
32016
+ const reason = reject?.kind === "reject" ? reject.reason?.trim() : void 0;
32017
+ const carried = findLast(history, (h) => h.kind === "rerun" && h.revision === prev);
32018
+ const fbRaw = step.feedback?.trim();
32019
+ const fb = fbRaw && !(carried?.kind === "rerun" && carried.feedback?.trim() === fbRaw) ? fbRaw : void 0;
32020
+ const lines = [];
32021
+ if (reason && !fb?.includes(reason)) {
32022
+ lines.push(`Rejected at step ${stepIdx + 1} (${step.agent}): ${reason}`);
32023
+ }
32024
+ if (fb && !reason?.includes(fb)) {
32025
+ lines.push(fb);
32026
+ }
32027
+ if (lines.length === 0) {
32028
+ return null;
32029
+ }
32030
+ return [`Revision ${step.revision}. Address before anything else:`, ...lines.map((l) => `- ${l}`)].join("\n");
32031
+ }
32032
+ function findLast(entries, pred) {
32033
+ for (let i = entries.length - 1; i >= 0; i--) {
32034
+ if (pred(entries[i])) {
32035
+ return entries[i];
32036
+ }
32037
+ }
32038
+ return void 0;
32039
+ }
32040
+ }
32041
+ });
32042
+
32043
+ // ../core/dist/runs/runLock.js
32044
+ var require_runLock = __commonJS({
32045
+ "../core/dist/runs/runLock.js"(exports2) {
32046
+ "use strict";
32047
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m, k, k2) {
32048
+ if (k2 === void 0) k2 = k;
32049
+ var desc = Object.getOwnPropertyDescriptor(m, k);
32050
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
32051
+ desc = { enumerable: true, get: function() {
32052
+ return m[k];
32053
+ } };
32054
+ }
32055
+ Object.defineProperty(o, k2, desc);
32056
+ } : function(o, m, k, k2) {
32057
+ if (k2 === void 0) k2 = k;
32058
+ o[k2] = m[k];
32059
+ });
32060
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? function(o, v) {
32061
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32062
+ } : function(o, v) {
32063
+ o["default"] = v;
32064
+ });
32065
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ function() {
32066
+ var ownKeys = function(o) {
32067
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
32068
+ var ar = [];
32069
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
32070
+ return ar;
32071
+ };
32072
+ return ownKeys(o);
32073
+ };
32074
+ return function(mod) {
32075
+ if (mod && mod.__esModule) return mod;
32076
+ var result = {};
32077
+ if (mod != null) {
32078
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32079
+ }
32080
+ __setModuleDefault(result, mod);
32081
+ return result;
32082
+ };
32083
+ }();
32084
+ Object.defineProperty(exports2, "__esModule", { value: true });
32085
+ exports2.runLockFile = runLockFile;
32086
+ exports2.acquireRunLock = acquireRunLock;
32087
+ var fs14 = __importStar(require("fs"));
32088
+ var path17 = __importStar(require("path"));
32089
+ var RunStateStore_1 = require_RunStateStore();
32090
+ function runLockFile(root, runId) {
32091
+ return RunStateStore_1.RunStateStore.file(root, runId).replace(/\.json$/, ".lock");
32092
+ }
32093
+ function acquireRunLock(root, runId) {
32094
+ const file = runLockFile(root, runId);
32095
+ fs14.mkdirSync(path17.dirname(file), { recursive: true });
32096
+ for (let attempt = 0; attempt < 2; attempt++) {
32097
+ try {
32098
+ fs14.writeFileSync(file, JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }), { flag: "wx" });
32099
+ return {
32100
+ release: () => {
32101
+ if (lockPid(file) === process.pid) {
32102
+ fs14.rmSync(file, { force: true });
32103
+ }
32104
+ }
32105
+ };
32106
+ } catch (err) {
32107
+ if (err.code !== "EEXIST") {
32108
+ throw err;
32109
+ }
32110
+ }
32111
+ const pid = lockPid(file);
32112
+ if (pid !== null && isAlive(pid)) {
32113
+ return { heldBy: pid };
32114
+ }
32115
+ fs14.rmSync(file, { force: true });
32116
+ }
32117
+ return { heldBy: lockPid(file) ?? -1 };
32118
+ }
32119
+ function lockPid(file) {
32120
+ try {
32121
+ const pid = JSON.parse(fs14.readFileSync(file, "utf8")).pid;
32122
+ return typeof pid === "number" && Number.isInteger(pid) && pid > 0 ? pid : null;
32123
+ } catch {
32124
+ return null;
32125
+ }
32126
+ }
32127
+ function isAlive(pid) {
32128
+ try {
32129
+ process.kill(pid, 0);
32130
+ return true;
32131
+ } catch (err) {
32132
+ return err.code === "EPERM";
32133
+ }
32134
+ }
32135
+ }
32136
+ });
32137
+
32138
+ // ../core/dist/runs/codeFingerprint.js
32139
+ var require_codeFingerprint = __commonJS({
32140
+ "../core/dist/runs/codeFingerprint.js"(exports2) {
32141
+ "use strict";
32142
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m, k, k2) {
32143
+ if (k2 === void 0) k2 = k;
32144
+ var desc = Object.getOwnPropertyDescriptor(m, k);
32145
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
32146
+ desc = { enumerable: true, get: function() {
32147
+ return m[k];
32148
+ } };
32149
+ }
32150
+ Object.defineProperty(o, k2, desc);
32151
+ } : function(o, m, k, k2) {
32152
+ if (k2 === void 0) k2 = k;
32153
+ o[k2] = m[k];
32154
+ });
32155
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? function(o, v) {
32156
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32157
+ } : function(o, v) {
32158
+ o["default"] = v;
32159
+ });
32160
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ function() {
32161
+ var ownKeys = function(o) {
32162
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
32163
+ var ar = [];
32164
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
32165
+ return ar;
32166
+ };
32167
+ return ownKeys(o);
32168
+ };
32169
+ return function(mod) {
32170
+ if (mod && mod.__esModule) return mod;
32171
+ var result = {};
32172
+ if (mod != null) {
32173
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32174
+ }
32175
+ __setModuleDefault(result, mod);
32176
+ return result;
32177
+ };
32178
+ }();
32179
+ Object.defineProperty(exports2, "__esModule", { value: true });
32180
+ exports2.codeFingerprint = codeFingerprint;
32181
+ var child_process_1 = require("child_process");
32182
+ var fs14 = __importStar(require("fs"));
32183
+ var os3 = __importStar(require("os"));
32184
+ var path17 = __importStar(require("path"));
32185
+ function codeFingerprint(root, exclude) {
32186
+ const git = (args, env) => (0, child_process_1.execFileSync)("git", args, {
32187
+ cwd: root,
32188
+ encoding: "utf8",
32189
+ env: env ? { ...process.env, ...env } : process.env,
32190
+ stdio: ["ignore", "pipe", "ignore"],
32191
+ maxBuffer: 64 * 1024 * 1024
32192
+ }).trim();
32193
+ let realIndex;
32194
+ try {
32195
+ realIndex = path17.resolve(root, git(["rev-parse", "--git-path", "index"]));
32196
+ } catch {
32197
+ return null;
32198
+ }
32199
+ const dir = fs14.mkdtempSync(path17.join(os3.tmpdir(), "aidlc-fp-"));
32200
+ const index = path17.join(dir, "index");
32201
+ try {
32202
+ if (fs14.existsSync(realIndex)) {
32203
+ fs14.copyFileSync(realIndex, index);
32204
+ }
32205
+ const env = { GIT_INDEX_FILE: index };
32206
+ git(["add", "-A", "--", "."], env);
32207
+ const skip = exclude.filter((p) => p && !p.startsWith(".."));
32208
+ if (skip.length > 0) {
32209
+ git(["rm", "-r", "-q", "--cached", "--ignore-unmatch", "--", ...skip], env);
32210
+ }
32211
+ return git(["write-tree"], env);
32212
+ } catch {
32213
+ return null;
32214
+ } finally {
32215
+ fs14.rmSync(dir, { recursive: true, force: true });
32216
+ }
32217
+ }
32218
+ }
32219
+ });
32220
+
32221
+ // ../core/dist/runs/batchStatus.js
32222
+ var require_batchStatus = __commonJS({
32223
+ "../core/dist/runs/batchStatus.js"(exports2) {
32224
+ "use strict";
32225
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m, k, k2) {
32226
+ if (k2 === void 0) k2 = k;
32227
+ var desc = Object.getOwnPropertyDescriptor(m, k);
32228
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
32229
+ desc = { enumerable: true, get: function() {
32230
+ return m[k];
32231
+ } };
32232
+ }
32233
+ Object.defineProperty(o, k2, desc);
32234
+ } : function(o, m, k, k2) {
32235
+ if (k2 === void 0) k2 = k;
32236
+ o[k2] = m[k];
32237
+ });
32238
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? function(o, v) {
32239
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32240
+ } : function(o, v) {
32241
+ o["default"] = v;
32242
+ });
32243
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ function() {
32244
+ var ownKeys = function(o) {
32245
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
32246
+ var ar = [];
32247
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
32248
+ return ar;
32249
+ };
32250
+ return ownKeys(o);
32251
+ };
32252
+ return function(mod) {
32253
+ if (mod && mod.__esModule) return mod;
32254
+ var result = {};
32255
+ if (mod != null) {
32256
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32257
+ }
32258
+ __setModuleDefault(result, mod);
32259
+ return result;
32260
+ };
32261
+ }();
32262
+ Object.defineProperty(exports2, "__esModule", { value: true });
32263
+ exports2.readBatchStatus = readBatchStatus;
32264
+ exports2.readBatchStatusFile = readBatchStatusFile;
32265
+ var fs14 = __importStar(require("fs"));
32266
+ var path17 = __importStar(require("path"));
32267
+ var LINE = /^[\s>#*_-]*status[\s*_]*:[\s*_`]*([a-z]+)[*_`]*(.*)$/i;
32268
+ var TAIL = /^\s*(?:$|[—–-]\s|[(.,;])/;
32269
+ function readBatchStatus(text, file = "the report") {
32270
+ let last = null;
32271
+ for (const line of text.split(/\r?\n/)) {
32272
+ const m = LINE.exec(line);
32273
+ if (m) {
32274
+ last = { word: m[1].toLowerCase(), tail: m[2] };
31380
32275
  }
31381
32276
  }
31382
- if (!raw || raw.decision !== "pass" && raw.decision !== "reject" || typeof raw.reason !== "string") {
31383
- return {
31384
- decision: "reject",
31385
- reason: `Auto-reviewer returned malformed verdict: ${JSON.stringify(raw)}`,
31386
- at,
31387
- runner
31388
- };
32277
+ if (!last) {
32278
+ return { error: `${file} has no \`Status: continue\` or \`Status: done\` line.` };
32279
+ }
32280
+ if ((last.word === "continue" || last.word === "done") && TAIL.test(last.tail)) {
32281
+ return { status: last.word };
32282
+ }
32283
+ return {
32284
+ error: `The last Status line in ${file} reads "${last.word}${last.tail}", not \`Status: continue\` or \`Status: done\`.`
32285
+ };
32286
+ }
32287
+ function readBatchStatusFile(root, rel) {
32288
+ const abs = path17.isAbsolute(rel) ? rel : path17.join(root, rel);
32289
+ let text;
32290
+ try {
32291
+ text = fs14.readFileSync(abs, "utf8");
32292
+ } catch {
32293
+ return { error: `${rel} does not exist, so the batch did not say whether it is done.` };
31389
32294
  }
31390
- return { decision: raw.decision, reason: raw.reason, at, runner };
32295
+ return readBatchStatus(text, rel);
31391
32296
  }
31392
32297
  }
31393
32298
  });
@@ -31445,9 +32350,16 @@ var require_execEngine = __commonJS({
31445
32350
  var runProgress_1 = require_runProgress();
31446
32351
  var budget_1 = require_budget();
31447
32352
  var producesSnapshot_1 = require_producesSnapshot();
32353
+ var revisionFeedback_1 = require_revisionFeedback();
32354
+ var runLock_1 = require_runLock();
32355
+ var codeFingerprint_1 = require_codeFingerprint();
32356
+ var batchStatus_1 = require_batchStatus();
32357
+ var autopilotPreflight_1 = require_autopilotPreflight();
32358
+ var processTree_1 = require_processTree();
31448
32359
  var pricing_1 = require_pricing();
31449
32360
  var models_1 = require_models();
31450
32361
  var WorkspaceSchema_1 = require_WorkspaceSchema();
32362
+ var RunState_1 = require_RunState();
31451
32363
  var artifactLanguage_1 = require_artifactLanguage();
31452
32364
  var strictMode_1 = require_strictMode();
31453
32365
  var inheritedContext_1 = require_inheritedContext();
@@ -31463,6 +32375,21 @@ var require_execEngine = __commonJS({
31463
32375
  }
31464
32376
  }
31465
32377
  async function runExecLoop2(root, runId, opts, hooks = {}) {
32378
+ if (opts.dryRun) {
32379
+ return execLoop2(root, runId, opts, hooks);
32380
+ }
32381
+ const lock = (0, runLock_1.acquireRunLock)(root, runId);
32382
+ if ("heldBy" in lock) {
32383
+ hooks.onRunFailed?.(`Run "${runId}" is already being executed (pid ${lock.heldBy}). Stop that one first, or wait for it to finish.`);
32384
+ return { kind: "error" };
32385
+ }
32386
+ try {
32387
+ return await execLoop2(root, runId, opts, hooks);
32388
+ } finally {
32389
+ lock.release();
32390
+ }
32391
+ }
32392
+ async function execLoop2(root, runId, opts, hooks) {
31466
32393
  const untilIdx = opts.untilIdx ?? -1;
31467
32394
  const initialState = RunStateStore_1.RunStateStore.load(root, runId);
31468
32395
  if (!initialState) {
@@ -31475,8 +32402,23 @@ var require_execEngine = __commonJS({
31475
32402
  return { kind: "error" };
31476
32403
  }
31477
32404
  const budget = initialPipeline.budget;
32405
+ const batchesRun = /* @__PURE__ */ new Map();
32406
+ if (initialPipeline.autopilot && !opts.dryRun) {
32407
+ let problems;
32408
+ try {
32409
+ const ws = WorkspaceLoader_1.WorkspaceLoader.load(root);
32410
+ problems = (0, autopilotPreflight_1.autopilotPreflight)({ root, state: initialState, pipeline: initialPipeline, config: ws.config });
32411
+ } catch (err) {
32412
+ problems = [`Could not check the workspace: ${errMsg(err)}`];
32413
+ }
32414
+ if (problems.length > 0) {
32415
+ hooks.onRunFailed?.(`Pipeline "${initialPipeline.id}" is an autopilot pipeline, and it is not safe to leave this run alone yet:
32416
+ ` + problems.map((p) => ` - ${p}`).join("\n"));
32417
+ return { kind: "error" };
32418
+ }
32419
+ }
31478
32420
  while (true) {
31479
- if (opts.shouldCancel?.()) {
32421
+ if (opts.shouldCancel?.() || opts.signal?.aborted) {
31480
32422
  hooks.onCancelled?.();
31481
32423
  return { kind: "cancelled" };
31482
32424
  }
@@ -31542,47 +32484,56 @@ var require_execEngine = __commonJS({
31542
32484
  hooks.onRunFailed?.(`Unexpected step status "${step.status}" \u2014 cannot exec.`);
31543
32485
  return { kind: "error" };
31544
32486
  }
31545
- const success = await execStep(root, state, runId, opts, hooks);
31546
- if (!success) {
32487
+ const ran = await execStep(root, state, runId, opts, hooks);
32488
+ if (ran === "failed") {
31547
32489
  return { kind: "error" };
31548
32490
  }
31549
- if (opts.dryRun) {
31550
- return { kind: "dry_run" };
32491
+ if (ran === "stalled") {
32492
+ return { kind: "stalled" };
32493
+ }
32494
+ if (ran === "stopped") {
32495
+ return { kind: "cancelled" };
31551
32496
  }
31552
- if (budget) {
32497
+ if (ran === "batch") {
32498
+ const overBudget2 = checkRunBudget(root, runId, state.currentStepIdx, budget, hooks);
32499
+ if (overBudget2) {
32500
+ return overBudget2;
32501
+ }
32502
+ const idx = state.currentStepIdx;
32503
+ const cfg = initialPipeline.steps[idx] ? (0, WorkspaceSchema_1.normalizeStep)(initialPipeline.steps[idx]).batches : void 0;
32504
+ const key = `${idx}:${step.revision}`;
32505
+ const n = (batchesRun.get(key) ?? 0) + 1;
32506
+ batchesRun.set(key, n);
31553
32507
  const after = RunStateStore_1.RunStateStore.load(root, runId);
31554
- const stepCosts = after ? after.steps.map((s) => s.costUsd) : [];
31555
- const stepAccounting = after ? after.steps.map((s) => {
31556
- if (typeof s.costUsd === "number") {
31557
- return s.costEstimated ? "estimated" : "measured";
31558
- }
31559
- return s.startedAt ? "blind" : void 0;
31560
- }) : [];
31561
- const lastStepCost = after?.steps[state.currentStepIdx]?.costUsd;
31562
- const verdict = (0, budget_1.checkBudget)({ stepCosts, stepAccounting, budget, lastStepCost });
31563
- if (!verdict.ok) {
31564
- hooks.onBudget?.({
31565
- spent: verdict.spent,
31566
- limit: verdict.limit,
31567
- ok: false,
31568
- exceeded: verdict.exceeded,
31569
- onExceed: budget.on_exceed,
32508
+ const batchNo = after ? batchesOf(after.steps[idx]).length : n;
32509
+ if (cfg && n >= cfg.max) {
32510
+ hooks.onStalled?.({
32511
+ stepIdx: idx,
32512
+ agent: step.agent,
32513
+ revision: step.revision,
31570
32514
  runId,
31571
- measured: verdict.measured,
31572
- estimated: verdict.estimated,
31573
- blindSteps: verdict.blindSteps
32515
+ reason: `"${step.agent}" ran ${n} batch${n === 1 ? "" : "es"} (\`max_batches: ${cfg.max}\`) and still says \`Status: continue\`. Read its progress before letting it carry on: running exec again allows ${cfg.max} more.`
31574
32516
  });
31575
- return budget.on_exceed === "fail" ? { kind: "error" } : { kind: "budget_pause" };
31576
- }
31577
- hooks.onBudget?.({
31578
- spent: verdict.spent,
31579
- limit: budget.max_usd,
31580
- ok: true,
31581
- runId,
31582
- measured: verdict.measured,
31583
- estimated: verdict.estimated,
31584
- blindSteps: verdict.blindSteps
31585
- });
32517
+ return { kind: "stalled" };
32518
+ }
32519
+ if (cfg?.pause) {
32520
+ hooks.onBatchPause?.({
32521
+ stepIdx: idx,
32522
+ agent: step.agent,
32523
+ batch: batchNo,
32524
+ runId,
32525
+ report: batchReport(initialPipeline, after ?? state, idx) ?? ""
32526
+ });
32527
+ return { kind: "batch_pause" };
32528
+ }
32529
+ continue;
32530
+ }
32531
+ if (opts.dryRun) {
32532
+ return { kind: "dry_run" };
32533
+ }
32534
+ const overBudget = checkRunBudget(root, runId, state.currentStepIdx, budget, hooks);
32535
+ if (overBudget) {
32536
+ return overBudget;
31586
32537
  }
31587
32538
  if (untilIdx >= 0 && state.currentStepIdx >= untilIdx) {
31588
32539
  hooks.onUntilStop?.({ untilIdx });
@@ -31590,10 +32541,49 @@ var require_execEngine = __commonJS({
31590
32541
  }
31591
32542
  }
31592
32543
  }
32544
+ function checkRunBudget(root, runId, stepIdx, budget, hooks) {
32545
+ if (!budget) {
32546
+ return null;
32547
+ }
32548
+ const after = RunStateStore_1.RunStateStore.load(root, runId);
32549
+ const stepCosts = after ? after.steps.map((s) => s.costUsd) : [];
32550
+ const stepAccounting = after ? after.steps.map((s) => {
32551
+ if (typeof s.costUsd === "number") {
32552
+ return s.costEstimated ? "estimated" : "measured";
32553
+ }
32554
+ return s.startedAt ? "blind" : void 0;
32555
+ }) : [];
32556
+ const lastStepCost = after?.steps[stepIdx]?.costUsd;
32557
+ const verdict = (0, budget_1.checkBudget)({ stepCosts, stepAccounting, budget, lastStepCost });
32558
+ if (!verdict.ok) {
32559
+ hooks.onBudget?.({
32560
+ spent: verdict.spent,
32561
+ limit: verdict.limit,
32562
+ ok: false,
32563
+ exceeded: verdict.exceeded,
32564
+ onExceed: budget.on_exceed,
32565
+ runId,
32566
+ measured: verdict.measured,
32567
+ estimated: verdict.estimated,
32568
+ blindSteps: verdict.blindSteps
32569
+ });
32570
+ return budget.on_exceed === "fail" ? { kind: "error" } : { kind: "budget_pause" };
32571
+ }
32572
+ hooks.onBudget?.({
32573
+ spent: verdict.spent,
32574
+ limit: budget.max_usd,
32575
+ ok: true,
32576
+ runId,
32577
+ measured: verdict.measured,
32578
+ estimated: verdict.estimated,
32579
+ blindSteps: verdict.blindSteps
32580
+ });
32581
+ return null;
32582
+ }
31593
32583
  function loadSkills(ws, skillIds) {
31594
32584
  return skillIds.map((id) => ws.skills.load(id)).join("\n\n---\n\n");
31595
32585
  }
31596
- function buildStepPrompt(ws, agent, skillIds, runner, root, epicId) {
32586
+ function buildStepPrompt(ws, agent, skillIds, runner, root, epicId, stepRec, stepIdx) {
31597
32587
  const harness = (0, types_1.harnessCapabilities)(runner);
31598
32588
  return (0, promptComposer_1.composeAgentPrompt)({
31599
32589
  skills: loadSkills(ws, skillIds),
@@ -31603,7 +32593,8 @@ var require_execEngine = __commonJS({
31603
32593
  artifactLanguage: (0, artifactLanguage_1.resolveArtifactLanguage)(ws.config),
31604
32594
  // Per epic, not per workspace: how deep a phase goes is a property of the
31605
32595
  // work item. An epic that never set it reads as strict.
31606
- strictMode: (0, strictMode_1.resolveEpicStrictMode)((0, EpicScaffold_1.epicsRoot)(root, ws.config), epicId)
32596
+ strictMode: (0, strictMode_1.resolveEpicStrictMode)((0, EpicScaffold_1.epicsRoot)(root, ws.config), epicId),
32597
+ runHistory: stepRec.revision > 1 ? { file: path17.relative(root, RunStateStore_1.RunStateStore.file(root, epicId)).split(path17.sep).join("/"), stepIdx } : null
31607
32598
  });
31608
32599
  }
31609
32600
  async function execStep(root, state, runId, opts, hooks) {
@@ -31615,17 +32606,17 @@ var require_execEngine = __commonJS({
31615
32606
  ws = WorkspaceLoader_1.WorkspaceLoader.load(root);
31616
32607
  } catch (err) {
31617
32608
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Failed to load workspace: ${errMsg(err)}` });
31618
- return false;
32609
+ return "failed";
31619
32610
  }
31620
32611
  const pipeline = ws.config.pipelines.find((p) => p.id === state.pipelineId);
31621
32612
  if (!pipeline) {
31622
32613
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Pipeline "${state.pipelineId}" not found in workspace.yaml.` });
31623
- return false;
32614
+ return "failed";
31624
32615
  }
31625
32616
  const agent = ws.config.agents.find((a) => a.id === agentId);
31626
32617
  if (!agent) {
31627
32618
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Agent "${agentId}" not found in workspace.yaml.` });
31628
- return false;
32619
+ return "failed";
31629
32620
  }
31630
32621
  const stepCfg = pipeline.steps[stepIdx];
31631
32622
  const norm = stepCfg ? (0, WorkspaceSchema_1.normalizeStep)(stepCfg) : void 0;
@@ -31640,22 +32631,38 @@ var require_execEngine = __commonJS({
31640
32631
  runner = ws.runners.resolve(agent);
31641
32632
  } catch (err) {
31642
32633
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Failed to resolve runner for agent "${agentId}": ${errMsg(err)}` });
31643
- return false;
32634
+ return "failed";
31644
32635
  }
31645
32636
  try {
31646
- prompt2 = buildStepPrompt(ws, agent, skillIds, runner, root, state.runId);
32637
+ prompt2 = buildStepPrompt(ws, agent, skillIds, runner, root, state.runId, stepRec, stepIdx);
31647
32638
  } catch (err) {
31648
32639
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Failed to load skills for agent "${agentId}": ${errMsg(err)}` });
31649
- return false;
32640
+ return "failed";
31650
32641
  }
31651
32642
  const skillText = prompt2.text;
31652
32643
  const env = ws.envResolver.resolveLayered(ws.config.environment ?? {}, agent.env ?? {});
32644
+ if (pipeline.autopilot) {
32645
+ env[autopilotPreflight_1.AUTOPILOT_ENV] = "1";
32646
+ }
31653
32647
  const contextStr = Object.entries(state.context).map(([k, v]) => `${k}=${v}`).join(" ");
31654
32648
  const epicRoot = path17.relative(root, (0, EpicScaffold_1.epicsRoot)(root, ws.config)).split(path17.sep).join("/");
31655
32649
  const inherited = state.context.from_epic ? `
31656
32650
 
31657
32651
  About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` : "";
31658
- const userMessage = opts.message ?? (contextStr ? contextStr + inherited : `Execute step: ${agentId}`);
32652
+ const baseMessage = opts.message ?? (contextStr ? contextStr + inherited : `Execute step: ${agentId}`);
32653
+ const feedback = (0, revisionFeedback_1.revisionFeedback)(state, stepIdx);
32654
+ const report = norm?.batches ? batchReport(pipeline, state, stepIdx) : null;
32655
+ if (norm?.batches && !report) {
32656
+ hooks.onStepFailed?.({
32657
+ stepIdx,
32658
+ agent: agentId,
32659
+ message: `Step "${agentId}" has \`batches: true\` but no \`produces\` file to write its \`Status:\` line in.`
32660
+ });
32661
+ return "failed";
32662
+ }
32663
+ const batchNo = report ? batchesOf(stepRec).length + 1 : 0;
32664
+ const batchNote = report ? batchInstructions(report, batchNo, stepRec.revision) : null;
32665
+ const userMessage = [baseMessage, feedback, batchNote].filter(Boolean).join("\n\n");
31659
32666
  if (opts.dryRun) {
31660
32667
  hooks.onDryRunPreview?.({
31661
32668
  skills: skillIds.join(", "),
@@ -31664,7 +32671,7 @@ About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` :
31664
32671
  env,
31665
32672
  inlined: prompt2.included
31666
32673
  });
31667
- return true;
32674
+ return "done";
31668
32675
  }
31669
32676
  hooks.onStepStart?.({
31670
32677
  stepIdx,
@@ -31678,20 +32685,69 @@ About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` :
31678
32685
  const aliases = (0, pricing_1.providerAliases)(ws.config.providers, agent.runner);
31679
32686
  const resolvedModel = (0, models_1.resolveProviderModel)(agent.runner, agentModel, aliases);
31680
32687
  const producedBefore = (0, producesSnapshot_1.snapshotProduces)({ state, pipeline, workspaceRoot: root, stepIdx });
31681
- const result = await runner.run({
31682
- skill: skillText,
31683
- env,
31684
- args: userMessage ? [userMessage] : [],
31685
- workspaceRoot: root,
31686
- model: agentModel,
31687
- modelAliases: aliases,
31688
- onOutput: (chunk) => hooks.onOutput?.(chunk),
31689
- onError: (chunk) => hooks.onErrorOutput?.(chunk),
31690
- claude: null
31691
- });
31692
- if (!result.success) {
32688
+ const codeExclude = [
32689
+ path17.relative(root, path17.join((0, EpicScaffold_1.epicsRoot)(root, ws.config), state.runId)),
32690
+ ".aidlc"
32691
+ ];
32692
+ const sentBackHere = isAutoSendBackTarget(state, stepIdx);
32693
+ const codeBefore = sentBackHere || report ? (0, codeFingerprint_1.codeFingerprint)(root, codeExclude) : null;
32694
+ const stop = new AbortController();
32695
+ const minutes = norm?.timeout_minutes;
32696
+ let timedOut = false;
32697
+ const timer = minutes ? setTimeout(() => {
32698
+ timedOut = true;
32699
+ stop.abort();
32700
+ }, minutes * 6e4) : void 0;
32701
+ const onStopNow = () => stop.abort();
32702
+ opts.signal?.addEventListener("abort", onStopNow, { once: true });
32703
+ let result;
32704
+ try {
32705
+ result = await settledOrAbandoned(runner.run({
32706
+ skill: skillText,
32707
+ env,
32708
+ args: userMessage ? [userMessage] : [],
32709
+ workspaceRoot: root,
32710
+ model: agentModel,
32711
+ modelAliases: aliases,
32712
+ extraArgs: ws.config.providers[agent.runner]?.args,
32713
+ signal: stop.signal,
32714
+ onOutput: (chunk) => hooks.onOutput?.(chunk),
32715
+ onError: (chunk) => hooks.onErrorOutput?.(chunk),
32716
+ claude: null
32717
+ }), stop.signal);
32718
+ } finally {
32719
+ if (timer) {
32720
+ clearTimeout(timer);
32721
+ }
32722
+ opts.signal?.removeEventListener("abort", onStopNow);
32723
+ }
32724
+ if (stop.signal.aborted) {
32725
+ recordInterrupted(root, runId, stepIdx, timedOut ? { why: "timeout", minutes } : { why: "stopped" });
32726
+ if (timedOut) {
32727
+ hooks.onStepFailed?.({
32728
+ stepIdx,
32729
+ agent: agentId,
32730
+ message: `Step "${agentId}" ran past its ${minutes}-minute limit (\`timeout_minutes\`) and was stopped. It is still awaiting work on revision ${stepRec.revision}; nothing was sent back. Whatever it half-wrote is still in the working tree.`
32731
+ });
32732
+ return "failed";
32733
+ }
32734
+ hooks.onStepStopped?.({ stepIdx, agent: agentId, revision: stepRec.revision, runId });
32735
+ return "stopped";
32736
+ }
32737
+ if (!result || !result.success) {
31693
32738
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Step "${agentId}" failed (non-zero exit).` });
31694
- return false;
32739
+ return "failed";
32740
+ }
32741
+ const codeUnchanged = codeBefore !== null && (0, codeFingerprint_1.codeFingerprint)(root, codeExclude) === codeBefore;
32742
+ if (sentBackHere && codeUnchanged && batchNo <= 1) {
32743
+ hooks.onStalled?.({
32744
+ stepIdx,
32745
+ agent: agentId,
32746
+ revision: stepRec.revision,
32747
+ runId,
32748
+ reason: `Revision ${stepRec.revision} of "${agentId}" changed no code \u2014 the working tree is what it was before it ran (its report aside). Sent back ${state.sendBacks} time${state.sendBacks === 1 ? "" : "s"}; stopping rather than have the same code reviewed again.`
32749
+ });
32750
+ return "stalled";
31695
32751
  }
31696
32752
  const stale = (0, producesSnapshot_1.unchangedProduces)(producedBefore, root);
31697
32753
  if (stale.length > 0) {
@@ -31700,37 +32756,64 @@ About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` :
31700
32756
  agent: agentId,
31701
32757
  message: `Step "${agentId}" reported success but left its artifacts unchanged: ${stale.join(", ")}. The agent may have failed to write them. Check its output, then rerun the step.`
31702
32758
  });
31703
- return false;
32759
+ return "failed";
32760
+ }
32761
+ if (report) {
32762
+ const read = (0, batchStatus_1.readBatchStatusFile)(root, report);
32763
+ if ("error" in read) {
32764
+ hooks.onStepFailed?.({
32765
+ stepIdx,
32766
+ agent: agentId,
32767
+ message: `Step "${agentId}" runs in batches and its report cannot say whether it is done: ${read.error} Add the line, then run exec again.`
32768
+ });
32769
+ return "failed";
32770
+ }
32771
+ if (read.status === "continue") {
32772
+ if (codeUnchanged) {
32773
+ hooks.onStalled?.({
32774
+ stepIdx,
32775
+ agent: agentId,
32776
+ revision: stepRec.revision,
32777
+ runId,
32778
+ reason: `Batch ${batchNo} of "${agentId}" asked for another session but changed no code \u2014 the working tree is what it was before it ran (its report aside). Stopping rather than start the same batch again.`
32779
+ });
32780
+ return "stalled";
32781
+ }
32782
+ const fresh = RunStateStore_1.RunStateStore.load(root, runId);
32783
+ const rec = fresh?.steps[stepIdx];
32784
+ if (!fresh || !rec) {
32785
+ hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Run "${runId}" disappeared.` });
32786
+ return "failed";
32787
+ }
32788
+ recordProvenance(rec, { agent, resolvedModel, agentModel, skillIds, alternatives, modelOpts });
32789
+ const cost = addBatchCost(rec, batchNo, result, batchCost(result, ws, agent.runner, resolvedModel ?? agentModel));
32790
+ rec.history = [...rec.history ?? [], {
32791
+ kind: "batch",
32792
+ at: (/* @__PURE__ */ new Date()).toISOString(),
32793
+ revision: rec.revision,
32794
+ batch: batchNo,
32795
+ ...cost !== void 0 ? { costUsd: cost } : {}
32796
+ }];
32797
+ persist(root, fresh);
32798
+ hooks.onBatchDone?.({
32799
+ stepIdx,
32800
+ agent: agentId,
32801
+ revision: rec.revision,
32802
+ batch: batchNo,
32803
+ costUsd: cost,
32804
+ costEstimated: rec.costEstimated,
32805
+ runId
32806
+ });
32807
+ return "batch";
32808
+ }
31704
32809
  }
31705
32810
  let next;
31706
32811
  let freshState;
31707
32812
  try {
31708
32813
  freshState = RunStateStore_1.RunStateStore.load(root, runId);
31709
32814
  const rec = freshState.steps[stepIdx];
31710
- rec.runner = agent.runner;
31711
- rec.model = resolvedModel;
31712
- if (alternatives) {
31713
- rec.skill = skillIds[0];
31714
- }
31715
- if (modelOpts.models) {
31716
- rec.chosenModel = agentModel;
31717
- }
31718
- rec.usage = result.usage;
31719
- if (typeof result.costUsd === "number") {
31720
- rec.costUsd = result.costUsd;
31721
- rec.costEstimated = void 0;
31722
- } else {
31723
- const est = (0, pricing_1.estimateCostUsd)({
31724
- table: (0, pricing_1.ratesFromConfig)(ws.config.providers),
31725
- provider: agent.runner,
31726
- model: resolvedModel ?? agentModel,
31727
- usage: result.usage
31728
- });
31729
- if (est) {
31730
- rec.costUsd = est.usd;
31731
- rec.costEstimated = true;
31732
- }
31733
- }
32815
+ recordProvenance(rec, { agent, resolvedModel, agentModel, skillIds, alternatives, modelOpts });
32816
+ addBatchCost(rec, batchNo, result, batchCost(result, ws, agent.runner, resolvedModel ?? agentModel));
31734
32817
  next = (0, PipelineRunner_1.markStepDone)({ state: freshState, pipeline, workspaceRoot: root });
31735
32818
  } catch (err) {
31736
32819
  if (err instanceof PipelineRunner_1.PipelineRunError && err.missing?.length) {
@@ -31738,7 +32821,7 @@ About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` :
31738
32821
  } else {
31739
32822
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: errMsg(err) });
31740
32823
  }
31741
- return false;
32824
+ return "failed";
31742
32825
  }
31743
32826
  persist(root, next, freshState);
31744
32827
  const doneStep = next.steps[stepIdx];
@@ -31751,7 +32834,111 @@ About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` :
31751
32834
  runner: agent.runner,
31752
32835
  model: resolvedModel
31753
32836
  });
31754
- return true;
32837
+ return "done";
32838
+ }
32839
+ function batchesOf(rec) {
32840
+ if (!rec) {
32841
+ return [];
32842
+ }
32843
+ return (rec.history ?? []).flatMap((h) => h.kind === "batch" && h.revision === rec.revision ? [h] : []);
32844
+ }
32845
+ function batchReport(pipeline, state, stepIdx) {
32846
+ const cfg = pipeline.steps[stepIdx];
32847
+ if (!cfg) {
32848
+ return null;
32849
+ }
32850
+ const norm = (0, WorkspaceSchema_1.normalizeStep)(cfg);
32851
+ const first = norm.produces.find((p) => !norm.produces_optional.includes(p));
32852
+ return first ? (0, RunState_1.resolvePath)(first, state.context) : null;
32853
+ }
32854
+ function batchInstructions(report, batchNo, revision) {
32855
+ const lines = [
32856
+ `This step runs in batches: this is batch ${batchNo} of revision ${revision}, in a fresh session.`,
32857
+ batchNo === 1 && revision > 1 ? `Make this batch the fixes the feedback above asks for. Keep the progress table ${report} already has from the earlier revision, so you know what is done, and add the fixes to it.` : batchNo === 1 ? "Start with the first batch in the plan." : `${report} records what earlier batches did. Read it first, then do the next batch in the plan.`,
32858
+ `Do that one batch only, then update ${report}: its progress table, and as its last line \`Status: continue\` if any of the step's work is left, or \`Status: done\` when all of it is finished. A batch may be split smaller if it does not fit; never do two in one session.`
32859
+ ];
32860
+ return lines.join(" ");
32861
+ }
32862
+ function recordProvenance(rec, p) {
32863
+ rec.runner = p.agent.runner;
32864
+ rec.model = p.resolvedModel;
32865
+ if (p.alternatives) {
32866
+ rec.skill = p.skillIds[0];
32867
+ }
32868
+ if (p.modelOpts.models) {
32869
+ rec.chosenModel = p.agentModel;
32870
+ }
32871
+ }
32872
+ function batchCost(result, ws, provider, model) {
32873
+ if (typeof result.costUsd === "number") {
32874
+ return { usd: result.costUsd, estimated: false };
32875
+ }
32876
+ const est = (0, pricing_1.estimateCostUsd)({
32877
+ table: (0, pricing_1.ratesFromConfig)(ws.config.providers),
32878
+ provider,
32879
+ model,
32880
+ usage: result.usage
32881
+ });
32882
+ return est ? { usd: est.usd, estimated: true } : void 0;
32883
+ }
32884
+ function addBatchCost(rec, batchNo, result, cost) {
32885
+ const carry = batchNo > 1;
32886
+ rec.usage = carry ? addUsage(rec.usage, result.usage) : result.usage;
32887
+ if (!cost) {
32888
+ return void 0;
32889
+ }
32890
+ if (carry && typeof rec.costUsd === "number") {
32891
+ rec.costUsd += cost.usd;
32892
+ rec.costEstimated = rec.costEstimated || cost.estimated || void 0;
32893
+ } else {
32894
+ rec.costUsd = cost.usd;
32895
+ rec.costEstimated = cost.estimated || void 0;
32896
+ }
32897
+ return cost.usd;
32898
+ }
32899
+ function addUsage(a, b) {
32900
+ if (!a) {
32901
+ return b;
32902
+ }
32903
+ if (!b) {
32904
+ return a;
32905
+ }
32906
+ const sum = (x, y) => x === void 0 && y === void 0 ? void 0 : (x ?? 0) + (y ?? 0);
32907
+ return { inputTokens: sum(a.inputTokens, b.inputTokens), outputTokens: sum(a.outputTokens, b.outputTokens) };
32908
+ }
32909
+ async function settledOrAbandoned(run, signal) {
32910
+ let timer;
32911
+ const abandoned = new Promise((resolve5) => {
32912
+ const giveUp = () => {
32913
+ timer = setTimeout(() => resolve5(null), processTree_1.STOP_GRACE_MS + 5e3);
32914
+ };
32915
+ if (signal.aborted) {
32916
+ giveUp();
32917
+ } else {
32918
+ signal.addEventListener("abort", giveUp, { once: true });
32919
+ }
32920
+ });
32921
+ try {
32922
+ return await Promise.race([run, abandoned]);
32923
+ } finally {
32924
+ if (timer) {
32925
+ clearTimeout(timer);
32926
+ }
32927
+ }
32928
+ }
32929
+ function recordInterrupted(root, runId, stepIdx, what) {
32930
+ const fresh = RunStateStore_1.RunStateStore.load(root, runId);
32931
+ const rec = fresh?.steps[stepIdx];
32932
+ if (!fresh || !rec) {
32933
+ return;
32934
+ }
32935
+ rec.history = [...rec.history ?? [], {
32936
+ kind: "interrupted",
32937
+ at: (/* @__PURE__ */ new Date()).toISOString(),
32938
+ revision: rec.revision,
32939
+ ...what
32940
+ }];
32941
+ RunStateStore_1.RunStateStore.save(root, fresh);
31755
32942
  }
31756
32943
  async function runAutoReviewStep(root, runId, hooks) {
31757
32944
  const state = RunStateStore_1.RunStateStore.load(root, runId);
@@ -31781,18 +32968,43 @@ About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` :
31781
32968
  return false;
31782
32969
  }
31783
32970
  persist(root, next, state);
32971
+ const idx = state.currentStepIdx;
32972
+ const sentBack = verdict.decision === "reject" && next.currentStepIdx !== idx && next.steps[idx].status === "pending";
31784
32973
  hooks.onAutoReviewResult?.({
31785
32974
  agent: step.agent,
31786
32975
  decision: verdict.decision,
31787
- reason: verdict.reason,
31788
- runId
32976
+ reason: next.steps[idx].rejectReason ?? verdict.reason,
32977
+ runId,
32978
+ ...sentBack ? {
32979
+ sentBackTo: {
32980
+ stepIdx: next.currentStepIdx,
32981
+ agent: next.steps[next.currentStepIdx].agent,
32982
+ revision: next.steps[next.currentStepIdx].revision,
32983
+ count: next.sendBacks ?? 0,
32984
+ max: pipeline.max_send_backs ?? WorkspaceSchema_1.DEFAULT_MAX_SEND_BACKS
32985
+ }
32986
+ } : {}
31789
32987
  });
31790
32988
  return true;
31791
32989
  }
32990
+ function isAutoSendBackTarget(state, stepIdx) {
32991
+ if (!state.sendBacks) {
32992
+ return false;
32993
+ }
32994
+ let latest = null;
32995
+ for (const s of state.steps) {
32996
+ for (const h of s.history ?? []) {
32997
+ if (h.kind === "reject" && h.source && (!latest || h.at >= latest.at)) {
32998
+ latest = { at: h.at, to: h.sentBackToIdx };
32999
+ }
33000
+ }
33001
+ }
33002
+ return latest?.to === stepIdx;
33003
+ }
31792
33004
  async function autoApproveStep(root, state, hooks) {
31793
33005
  const ws = WorkspaceLoader_1.WorkspaceLoader.load(root);
31794
33006
  const pipeline = ws.config.pipelines.find((p) => p.id === state.pipelineId);
31795
- const next = (0, PipelineRunner_1.approveStep)({ state, pipeline });
33007
+ const next = { ...(0, PipelineRunner_1.approveStep)({ state, pipeline }), sendBacks: state.sendBacks };
31796
33008
  persist(root, next, state);
31797
33009
  hooks.onAutoApproved?.({ agent: state.steps[state.currentStepIdx].agent });
31798
33010
  }
@@ -31967,6 +33179,10 @@ var require_runReport = __commonJS({
31967
33179
  return ` - \u2714 approved (rev ${h.revision}${h.skill ? `, ${h.skill}` : ""}${h.model ? `, ${h.model}` : ""})`;
31968
33180
  case "undo":
31969
33181
  return ` - \u21B6 mark-done undone (rev ${h.revision}, was ${h.from})`;
33182
+ case "interrupted":
33183
+ return h.why === "timeout" ? ` - \u23F1 stopped after ${h.minutes ?? "?"} min (timeout_minutes, rev ${h.revision})` : ` - \u23F9 stopped mid-step (rev ${h.revision})`;
33184
+ case "batch":
33185
+ return ` - \u21BB batch ${h.batch} done, more to come (rev ${h.revision}${typeof h.costUsd === "number" ? `, $${h.costUsd.toFixed(4)}` : ""})`;
31970
33186
  default:
31971
33187
  return " - (unknown event)";
31972
33188
  }
@@ -32612,6 +33828,8 @@ into working code on a feature branch.
32612
33828
  dependsOn: ["review"]
32613
33829
  }
32614
33830
  ];
33831
+ var VERDICT_VALIDATOR = ".aidlc/validators/verdict.mjs";
33832
+ var CI_VALIDATOR = ".aidlc/validators/ci.mjs";
32615
33833
  var AINATIVE_RECIPES = [
32616
33834
  {
32617
33835
  id: "native-quick",
@@ -32620,7 +33838,7 @@ into working code on a feature branch.
32620
33838
  },
32621
33839
  {
32622
33840
  id: "native-lite",
32623
- description: "Small change with a precedent already in the codebase \u2014 no spec, no separate verify. The human gates sit up front, on intent and build-plan, where a wrong direction is cheapest to correct; implement and review then run to the end unattended. Trade-off: review is the only quality gate left, and nobody reads it before the branch is finished \u2014 prefer native-fix when the change has no precedent to follow.",
33841
+ description: "Small change with a precedent already in the codebase \u2014 no spec, no separate verify. The human gates sit up front, on intent and build-plan, where a wrong direction is cheapest to correct; implement and review then run to the end unattended. Trade-off: review is the only quality gate left, and nobody reads it before the branch is finished \u2014 a hold verdict stops the run, but prefer native-fix when the change has no precedent to follow.",
32624
33842
  steps: ["intent", "build-plan", "implement", "review"],
32625
33843
  // Spelled out for all four steps, including the two that would inherit
32626
33844
  // `true` anyway: where the humans stand is the point of this recipe, so
@@ -32629,7 +33847,48 @@ into working code on a feature branch.
32629
33847
  intent: { human_review: true },
32630
33848
  "build-plan": { human_review: true },
32631
33849
  implement: { human_review: false },
32632
- review: { human_review: false }
33850
+ // Nobody reads review.md before the run ends, so something has to: a
33851
+ // `hold` (or no readable verdict) rejects the step instead of letting
33852
+ // the run report `completed`.
33853
+ review: { human_review: false, auto_review: true, auto_review_runner: VERDICT_VALIDATOR }
33854
+ }
33855
+ },
33856
+ {
33857
+ id: "native-autopilot",
33858
+ description: "Left to run on its own once the plan is approved \u2014 no spec. A person approves intent and build-plan, then implement, verify and review run unattended: CI checks implement, and a failed verify or a review hold sends the work back to implement on its own, up to three times. The run stops at review for a person to approve the finished branch. Needs a branch of its own, the approval-gate hook and real CHECKS in .aidlc/validators/ci.mjs.",
33859
+ steps: ["intent", "build-plan", "implement", "verify", "review"],
33860
+ autopilot: true,
33861
+ max_send_backs: 3,
33862
+ gates: {
33863
+ intent: { human_review: true, timeout_minutes: 20 },
33864
+ "build-plan": { human_review: true, timeout_minutes: 20 },
33865
+ // A CI reject reruns implement on its own feedback; it is never sent
33866
+ // anywhere else. Each batch is a fresh session with its own 30 minutes.
33867
+ implement: {
33868
+ human_review: false,
33869
+ auto_review: true,
33870
+ auto_review_runner: CI_VALIDATOR,
33871
+ batches: true,
33872
+ timeout_minutes: 30
33873
+ },
33874
+ verify: {
33875
+ human_review: false,
33876
+ auto_review: true,
33877
+ auto_review_runner: VERDICT_VALIDATOR,
33878
+ on_reject: { send_back_to: "implement" },
33879
+ timeout_minutes: 20
33880
+ },
33881
+ // A person still approves the end of the run (a `ship` stops here), but
33882
+ // only once: a `hold` goes back to implement without waiting for them.
33883
+ // Once the validators have earned trust, `human_review: false` makes a
33884
+ // `ship` complete the run.
33885
+ review: {
33886
+ human_review: true,
33887
+ auto_review: true,
33888
+ auto_review_runner: VERDICT_VALIDATOR,
33889
+ on_reject: { send_back_to: "implement" },
33890
+ timeout_minutes: 20
33891
+ }
32633
33892
  }
32634
33893
  },
32635
33894
  {
@@ -32842,7 +34101,9 @@ into working code on a feature branch.
32842
34101
  description: r.description,
32843
34102
  from: workflow.pipelineId,
32844
34103
  steps: r.steps,
32845
- ...r.gates ? { gates: r.gates } : {}
34104
+ ...r.gates ? { gates: r.gates } : {},
34105
+ ...r.autopilot !== void 0 ? { autopilot: r.autopilot } : {},
34106
+ ...r.max_send_backs !== void 0 ? { max_send_backs: r.max_send_backs } : {}
32846
34107
  })),
32847
34108
  sidebar: {
32848
34109
  views: [
@@ -33073,12 +34334,15 @@ export default async function ci(_ctx) {
33073
34334
  }
33074
34335
  `;
33075
34336
  function writeBuiltinAutoReviewValidators(extensionPath, root, workflow) {
34337
+ const runners = [
34338
+ ...workflow.phases.filter((p) => p.autoReview).map((p) => p.autoReviewRunner),
34339
+ ...(workflow.recipes ?? []).flatMap((r) => Object.values(r.gates ?? {}).map((g) => g.auto_review_runner))
34340
+ ];
33076
34341
  const seen = /* @__PURE__ */ new Set();
33077
- for (const phase of workflow.phases) {
33078
- if (!phase.autoReview || !phase.autoReviewRunner) {
34342
+ for (const rel of runners) {
34343
+ if (!rel) {
33079
34344
  continue;
33080
34345
  }
33081
- const rel = phase.autoReviewRunner;
33082
34346
  if (path17.isAbsolute(rel) || rel.startsWith("..")) {
33083
34347
  continue;
33084
34348
  }
@@ -34135,13 +35399,14 @@ var require_dist = __commonJS({
34135
35399
  "../core/dist/index.js"(exports2) {
34136
35400
  "use strict";
34137
35401
  Object.defineProperty(exports2, "__esModule", { value: true });
34138
- exports2.isSignal = exports2.parseSignal = exports2.SignalSchema = exports2.EpicStepEditError = exports2.resolveStepRef = exports2.describeGateEffect = exports2.commitEpicStepEdit = exports2.planSetEpicStepGates = exports2.planRemoveEpicStep = exports2.planAddEpicStep = exports2.EpicWorkflowSwitchError = exports2.canSwitchEpicWorkflow = exports2.epicWorkflowLock = exports2.applyEpicWorkflowSwitch = exports2.stageEpicWorkflowSwitch = exports2.planEpicWorkflowSwitch = exports2.EpicDescriptionError = exports2.setEpicDescription = exports2.lockedEpicDirError = exports2.EpicScaffoldError = exports2.epicsRoot = exports2.mapStepStatusToEpic = exports2.mirrorRunStateToEpic = exports2.scaffoldEpic = exports2.resolveArtifactCommitConfig = exports2.commitApprovedArtifacts = exports2.PipelineAdaptError = exports2.applyAdaptation = exports2.parseAdaptationVerdict = exports2.buildAdaptationPrompt = exports2.buildPhaseCatalog = exports2.slugEpicId = exports2.parseClassificationVerdict = exports2.buildClassificationPrompt = exports2.heuristicClassify = exports2.PipelineAssembleError = exports2.pipelineRecipeLabel = exports2.recipePipelineId = exports2.assemblePipeline = exports2.collectWorkspaceRefIssues = exports2.resolveStepSkills = exports2.stepSkillAlternatives = exports2.stepDagId = exports2.stepAgentId = exports2.requiresEntries = exports2.producesEntries = exports2.normalizeStep = exports2.WorkspaceValidationError = exports2.validateWorkspace = exports2.WorkspaceSchema = void 0;
34139
- exports2.WORKSPACE_DIR = exports2.WORKSPACE_FILENAME = exports2.WorkspaceParseError = exports2.WorkspaceNotFoundError = exports2.WorkspaceLoader = exports2.renderPlanMarkdown = exports2.generatePlan = exports2.collectContext = exports2.followUpHookFailures = exports2.pendingFollowUpDone = exports2.applyFollowUpHookRun = exports2.trackFollowUpChildren = exports2.writeFollowUpHookLedger = exports2.readFollowUpHookLedger = exports2.followUpHookLedgerPath = exports2.runFollowUpHook = exports2.fillFollowUpHookCommand = exports2.resolveFollowUpHooks = exports2.epicPipelineId = exports2.FOLLOW_UP_HOOK_TIMEOUT_MS = exports2.FOLLOW_UP_HOOKS_LEDGER = exports2.stepProducesFollowUps = exports2.collectFollowUpHookIssues = exports2.FOLLOW_UP_HOOK_PLACEHOLDERS = exports2.FOLLOW_UP_HOOK_KEYS = exports2.FOLLOW_UP_INTENT = exports2.FOLLOW_UPS_FILE = exports2.MANUAL_FOLLOW_UP_KEY_PREFIX = exports2.followUpDefaults = exports2.nextManualFollowUpKey = exports2.withFollowUpProvenance = exports2.openManifestFollowUp = exports2.combinedParentId = exports2.followUpChildId = exports2.followUpsOf = exports2.readEpicFollowUps = exports2.parseFollowUps = exports2.FollowUpsParseError = exports2.FollowUpManifestSchema = exports2.FollowUpItemSchema = exports2.SIGNAL_FILE = exports2.FOLLOW_UP_ARTIFACT = exports2.readEpicSignal = exports2.existingEpicIds = exports2.followUpIdFor = exports2.followUpEpicId = exports2.renderIntentMarkdown = exports2.openFollowUpEpic = exports2.openIncidentEpic = exports2.SignalParseError = void 0;
34140
- exports2.EPIC_LIST_FLAT_KEY = exports2.EPIC_SORT_KEY = exports2.ACTIVE_EPIC_KEY = exports2.writeUserEpicIdPrefix = exports2.userConfigPath = exports2.updateUserConfig = exports2.readUserConfig = exports2.readGitUserName = exports2.ensureUserConfigIgnored = exports2.USER_CONFIG_RELPATH = exports2.USER_CONFIG_IGNORE_LINE = exports2.suggestEpicId = exports2.resolveEpicIdPrefixChain = exports2.resolveEpicIdPrefix = exports2.epicIdDateStamp = exports2.deriveEpicIdPrefix = exports2.EPIC_ID_PREFIX_PATTERN = exports2.EPIC_ID_PREFIX_KEY = exports2.resolveArtifactLanguage = exports2.commandBodyPredatesArtifactLanguage = exports2.artifactLanguageSection = exports2.ARTIFACT_LANGUAGE_HEADING = exports2.stripPersonaDirectives = exports2.composeAgentPrompt = exports2.PROJECT_INSTRUCTION_FILES = exports2.findProjectInstructions = exports2.stripPersonaMetadata = exports2.PersonaLoader = exports2.SkillNotFoundError = exports2.SkillLoader = exports2.EnvVarMissingError = exports2.EnvResolver = exports2.epicPipelineReport = exports2.planEpicPipelineExtraction = exports2.epicOwningPipeline = exports2.unstageEpicPipeline = exports2.stageEpicPipeline = exports2.writeEpicPipelines = exports2.splitEpicPipelines = exports2.mergeEpicPipelines = exports2.epicPipelinePath = exports2.EPIC_PIPELINE_FILENAME = exports2.unusedAfterStepRemoval = exports2.skillReferences = exports2.agentReferences = exports2.renameSkillRefs = exports2.renameAgentRefs = exports2.dropRecipesForPipeline = exports2.dropRecipeStep = exports2.recipesDrawingFrom = void 0;
34141
- exports2.isInsideClaudeCodeSession = exports2.createJsonSink = exports2.createLineSink = exports2.isCodexMcpConfigured = exports2.codexConfigPath = exports2.readProjectMcpServer = exports2.mcpRegistrarFor = exports2.codexMcpRegistrar = exports2.claudeMcpRegistrar = exports2.copilotMcpConfig = exports2.copilotGraphServer = exports2.CopilotRunner = exports2.CodexRunner = exports2.claudeModelArg = exports2.DefaultRunner = exports2.RunnerRegistry = exports2.targetPath = exports2.scopePaths = exports2.discoverAssets = exports2.commandBodyIsStale = exports2.readEpicTags = exports2.normalizeTags = exports2.normalizeTag = exports2.epicMatchesTags = exports2.applyTagEdit = exports2.MAX_TAG_LENGTH = exports2.EPIC_TAGS_KEY = exports2.inheritedContextNote = exports2.INHERITED_CONTEXT_MARKER = exports2.strictModeSection = exports2.resolveEpicStrictMode = exports2.epicStrictMode = exports2.commandBodyPredatesStrictMode = exports2.STRICT_MODE_KEY = exports2.STRICT_MODE_HEADING = exports2.setEpicWatched = exports2.setEpicListFlat = exports2.setEpicSort = exports2.setActiveEpic = exports2.recordEpicOpened = exports2.readEpicListFlat = exports2.readEpicSort = exports2.readEpicFocus = exports2.pushRecent = exports2.epicSortFrom = exports2.epicIdFromBranch = exports2.epicFocusFrom = exports2.WATCHED_EPICS_KEY = exports2.RECENT_EPICS_LIMIT = exports2.RECENT_EPICS_KEY = void 0;
34142
- exports2.resolvePath = exports2.AutoReviewerError = exports2.runAutoReview = exports2.renderRunReport = exports2.verifyRun = exports2.runExecLoop = exports2.estimateCostUsd = exports2.providerAliases = exports2.ratesFromConfig = exports2.mergeRates = exports2.BUILTIN_RATES = exports2.unchangedProduces = exports2.snapshotProduces = exports2.checkBudget = exports2.weighStepProgress = exports2.isActiveStatus = exports2.isStepOptional = exports2.isRunComplete = exports2.deriveRunProgress = exports2.PipelineRunError = exports2.retryAutoReview = exports2.submitAutoReviewVerdict = exports2.dirtyUpstreamOf = exports2.rerunApprovedStep = exports2.canRerunApprovedStep = exports2.chooseStepModel = exports2.chooseStepSkill = exports2.requestStepUpdate = exports2.rerunStep = exports2.rejectStep = exports2.approveStep = exports2.undoStepDone = exports2.canUndoStepDone = exports2.markStepDone = exports2.canStartStep = exports2.startRun = exports2.activateBackendFromWorkspace = exports2.activateRunStateBackend = exports2.resolveRunStateBackend = exports2.GitRunStateStore = exports2.RUN_ID_PATTERN = exports2.FileRunStateStore = exports2.RunStateStore = exports2.harnessCapabilities = exports2.NO_HARNESS_CAPABILITIES = exports2.RunnerValidationError = exports2.validateRunnerExport = exports2.CustomRunnerLoader = exports2.buildClaudeSpawnEnv = exports2.hasClaudeLogin = void 0;
34143
- exports2.shortcutCommandId = exports2.getCanonicalPhase = exports2.isCanonicalPhase = exports2.BACKBONE_COMMAND_ID = exports2.CANONICAL_PHASE_IDS = exports2.CANONICAL_PHASES = exports2.renderTemplate = exports2.DEFAULT_GLOBAL_WORKFLOW_IDS = exports2.detectGlobalBuiltinSource = exports2.uninstallWorkflowGlobalsByIds = exports2.isWorkflowGloballyInstalled = exports2.installWorkflowGlobalsByIds = exports2.installGlobalDefaults = exports2.stepModelOptions = exports2.pickAgentModel = exports2.resolveProviderModel = exports2.isClaudeTierAlias = exports2.CLAUDE_TIER_ALIASES = exports2.FAST_MODEL = exports2.CODING_MODEL = exports2.PLANNING_MODEL = exports2.isBuiltinPreset = exports2.BUILTIN_PRESET_IDS = exports2.writeBuiltinAutoReviewValidators = exports2.resolvePrimaryStack = exports2.getSdlcArtifactTemplates = exports2.getBuiltinArtifactTemplates = exports2.phaseArtifactFileName = exports2.sdlcClaudeCommand = exports2.builtinClaudeCommand = exports2.loadAllBuiltinPresets = exports2.loadBuiltinPreset = exports2.planRecipeMigration = exports2.getBuiltinRecipeSummaries = exports2.getAllBuiltinPipelineSummaries = exports2.getSdlcBuiltinPipelineSummary = exports2.getBuiltinPipelineSummary = exports2.getBuiltinWorkflowByPipelineId = exports2.getBuiltinWorkflow = exports2.workflowSlug = exports2.builtinTemplatesRoot = exports2.pipelineCommandId = exports2.PHASES = exports2.BUILTIN_WORKFLOWS = exports2.withBackfilledStepNames = exports2.describeDrift = exports2.reconcileRunSteps = exports2.RUN_STATE_SCHEMA_VERSION = exports2.migrateRunState = exports2.stepIdentity = void 0;
34144
- exports2.AIDLC_CORE_VERSION = exports2.AIDLC_CLI_GUIDE_TEXT = exports2.AIDLC_KNOWLEDGE = exports2.clearResolveCommandCache = exports2.resolveCommand = exports2.remapClaudePath = exports2.claudeConfigEnv = exports2.claudeJsonPath = exports2.isDefaultClaudeConfigDir = exports2.claudeConfigDir = exports2.resolveClaudeConfigDir = exports2.defaultClaudeConfigDir = exports2.getClaudeConfigDirOverride = exports2.setClaudeConfigDir = exports2.resolveDeclaredPath = exports2.expandHome = exports2.ProfileLoadError = exports2.UnknownStandardError = exports2.builtinProfiles = exports2.loadActiveProfile = exports2.loadProfile = exports2.listProfileManifests = exports2.resolveStandard = exports2.workspaceStandard = exports2.isBuiltinProfileId = exports2.ProfileSchema = exports2.TRACE_RULES = exports2.PROFILES_DIR = exports2.DEFAULT_PROFILE_ID = exports2.BUILTIN_PROFILE_IDS = exports2.setEpicMemoryHook = exports2.isEpicMemoryHookEnabled = exports2.installAnnotationTools = exports2.relativeEpicRoot = exports2.provisionDeclaredWorkflows = exports2.provisionWorkflowFiles = exports2.writeWorkflowArtifactTemplates = exports2.writeWorkflowCommands = exports2.provisionShortcutDocs = exports2.writeTwoLayerCommands = exports2.shortcutCommandDoc = exports2.backboneCommandDoc = exports2.unprovisionedPhases = exports2.nextEligiblePhase = exports2.resolveComposition = void 0;
35402
+ exports2.commitEpicStepEdit = exports2.planSetEpicStepGates = exports2.planRemoveEpicStep = exports2.planAddEpicStep = exports2.EpicWorkflowSwitchError = exports2.canSwitchEpicWorkflow = exports2.epicWorkflowLock = exports2.applyEpicWorkflowSwitch = exports2.stageEpicWorkflowSwitch = exports2.planEpicWorkflowSwitch = exports2.EpicDescriptionError = exports2.setEpicDescription = exports2.lockedEpicDirError = exports2.EpicScaffoldError = exports2.epicsRoot = exports2.mapStepStatusToEpic = exports2.mirrorRunStateToEpic = exports2.scaffoldEpic = exports2.resolveArtifactCommitConfig = exports2.commitApprovedArtifacts = exports2.PipelineAdaptError = exports2.applyAdaptation = exports2.parseAdaptationVerdict = exports2.buildAdaptationPrompt = exports2.buildPhaseCatalog = exports2.slugEpicId = exports2.parseClassificationVerdict = exports2.buildClassificationPrompt = exports2.heuristicClassify = exports2.PipelineAssembleError = exports2.pipelineRecipeLabel = exports2.recipePipelineId = exports2.assemblePipeline = exports2.unsafeRunnerArg = exports2.DEFAULT_MAX_BATCHES = exports2.DEFAULT_MAX_SEND_BACKS = exports2.AMBIGUOUS_STEP_INDEX = exports2.sendBackIssues = exports2.findStepIndex = exports2.collectWorkspaceRefIssues = exports2.resolveStepSkills = exports2.stepSkillAlternatives = exports2.stepDagId = exports2.stepAgentId = exports2.requiresEntries = exports2.producesEntries = exports2.normalizeStep = exports2.WorkspaceValidationError = exports2.validateWorkspace = exports2.WorkspaceSchema = void 0;
35403
+ exports2.generatePlan = exports2.collectContext = exports2.followUpHookFailures = exports2.pendingFollowUpDone = exports2.applyFollowUpHookRun = exports2.trackFollowUpChildren = exports2.writeFollowUpHookLedger = exports2.readFollowUpHookLedger = exports2.followUpHookLedgerPath = exports2.runFollowUpHook = exports2.fillFollowUpHookCommand = exports2.resolveFollowUpHooks = exports2.epicPipelineId = exports2.FOLLOW_UP_HOOK_TIMEOUT_MS = exports2.FOLLOW_UP_HOOKS_LEDGER = exports2.stepProducesFollowUps = exports2.collectFollowUpHookIssues = exports2.FOLLOW_UP_HOOK_PLACEHOLDERS = exports2.FOLLOW_UP_HOOK_KEYS = exports2.FOLLOW_UP_INTENT = exports2.FOLLOW_UPS_FILE = exports2.MANUAL_FOLLOW_UP_KEY_PREFIX = exports2.followUpDefaults = exports2.nextManualFollowUpKey = exports2.withFollowUpProvenance = exports2.openManifestFollowUp = exports2.combinedParentId = exports2.followUpChildId = exports2.followUpsOf = exports2.readEpicFollowUps = exports2.parseFollowUps = exports2.FollowUpsParseError = exports2.FollowUpManifestSchema = exports2.FollowUpItemSchema = exports2.SIGNAL_FILE = exports2.FOLLOW_UP_ARTIFACT = exports2.readEpicSignal = exports2.existingEpicIds = exports2.followUpIdFor = exports2.followUpEpicId = exports2.renderIntentMarkdown = exports2.openFollowUpEpic = exports2.openIncidentEpic = exports2.SignalParseError = exports2.isSignal = exports2.parseSignal = exports2.SignalSchema = exports2.EpicStepEditError = exports2.resolveStepRef = exports2.describeGateEffect = void 0;
35404
+ exports2.readUserConfig = exports2.readGitUserName = exports2.ensureUserConfigIgnored = exports2.USER_CONFIG_RELPATH = exports2.USER_CONFIG_IGNORE_LINE = exports2.suggestEpicId = exports2.resolveEpicIdPrefixChain = exports2.resolveEpicIdPrefix = exports2.epicIdDateStamp = exports2.deriveEpicIdPrefix = exports2.EPIC_ID_PREFIX_PATTERN = exports2.EPIC_ID_PREFIX_KEY = exports2.resolveArtifactLanguage = exports2.commandBodyPredatesArtifactLanguage = exports2.artifactLanguageSection = exports2.ARTIFACT_LANGUAGE_HEADING = exports2.stripPersonaDirectives = exports2.composeAgentPrompt = exports2.PROJECT_INSTRUCTION_FILES = exports2.findProjectInstructions = exports2.stripPersonaMetadata = exports2.PersonaLoader = exports2.SkillNotFoundError = exports2.SkillLoader = exports2.EnvVarMissingError = exports2.EnvResolver = exports2.epicPipelineReport = exports2.planEpicPipelineExtraction = exports2.epicOwningPipeline = exports2.unstageEpicPipeline = exports2.stageEpicPipeline = exports2.writeEpicPipelines = exports2.splitEpicPipelines = exports2.mergeEpicPipelines = exports2.epicPipelinePath = exports2.EPIC_PIPELINE_FILENAME = exports2.unusedAfterStepRemoval = exports2.skillReferences = exports2.agentReferences = exports2.renameSkillRefs = exports2.renameAgentRefs = exports2.dropRecipesForPipeline = exports2.dropRecipeStep = exports2.recipesDrawingFrom = exports2.WORKSPACE_DIR = exports2.WORKSPACE_FILENAME = exports2.WorkspaceParseError = exports2.WorkspaceNotFoundError = exports2.WorkspaceLoader = exports2.renderPlanMarkdown = void 0;
35405
+ exports2.hasEmptyCiChecks = exports2.findApprovalGate = exports2.autopilotPreflight = exports2.STOP_GRACE_MS = exports2.stopProcessTree = exports2.stopOnAbort = exports2.processGroupSpawnOptions = exports2.claudeModelArg = exports2.DefaultRunner = exports2.RunnerRegistry = exports2.targetPath = exports2.scopePaths = exports2.discoverAssets = exports2.commandBodyIsStale = exports2.readEpicTags = exports2.normalizeTags = exports2.normalizeTag = exports2.epicMatchesTags = exports2.applyTagEdit = exports2.MAX_TAG_LENGTH = exports2.EPIC_TAGS_KEY = exports2.inheritedContextNote = exports2.INHERITED_CONTEXT_MARKER = exports2.strictModeSection = exports2.resolveEpicStrictMode = exports2.epicStrictMode = exports2.commandBodyPredatesStrictMode = exports2.STRICT_MODE_KEY = exports2.STRICT_MODE_HEADING = exports2.setEpicWatched = exports2.setEpicListFlat = exports2.setEpicSort = exports2.setActiveEpic = exports2.recordEpicOpened = exports2.readEpicListFlat = exports2.readEpicSort = exports2.readEpicFocus = exports2.pushRecent = exports2.epicSortFrom = exports2.epicIdFromBranch = exports2.epicFocusFrom = exports2.WATCHED_EPICS_KEY = exports2.RECENT_EPICS_LIMIT = exports2.RECENT_EPICS_KEY = exports2.EPIC_LIST_FLAT_KEY = exports2.EPIC_SORT_KEY = exports2.ACTIVE_EPIC_KEY = exports2.writeUserEpicIdPrefix = exports2.userConfigPath = exports2.updateUserConfig = void 0;
35406
+ exports2.isStepOptional = exports2.isRunComplete = exports2.deriveRunProgress = exports2.PipelineRunError = exports2.retryAutoReview = exports2.submitAutoReviewVerdict = exports2.dirtyUpstreamOf = exports2.rerunApprovedStep = exports2.canRerunApprovedStep = exports2.chooseStepModel = exports2.chooseStepSkill = exports2.requestStepUpdate = exports2.rerunStep = exports2.rejectStep = exports2.approveStep = exports2.undoStepDone = exports2.canUndoStepDone = exports2.markStepDone = exports2.canStartStep = exports2.startRun = exports2.activateBackendFromWorkspace = exports2.activateRunStateBackend = exports2.resolveRunStateBackend = exports2.GitRunStateStore = exports2.RUN_ID_PATTERN = exports2.FileRunStateStore = exports2.RunStateStore = exports2.harnessCapabilities = exports2.NO_HARNESS_CAPABILITIES = exports2.RunnerValidationError = exports2.validateRunnerExport = exports2.CustomRunnerLoader = exports2.buildClaudeSpawnEnv = exports2.hasClaudeLogin = exports2.isInsideClaudeCodeSession = exports2.createJsonSink = exports2.createLineSink = exports2.isCodexMcpConfigured = exports2.codexConfigPath = exports2.readProjectMcpServer = exports2.mcpRegistrarFor = exports2.codexMcpRegistrar = exports2.claudeMcpRegistrar = exports2.copilotMcpConfig = exports2.copilotGraphServer = exports2.CopilotRunner = exports2.CodexRunner = exports2.AUTOPILOT_ENV = exports2.APPROVAL_GATE_SCRIPT = exports2.emptyCiChecksMessage = void 0;
35407
+ exports2.isClaudeTierAlias = exports2.CLAUDE_TIER_ALIASES = exports2.FAST_MODEL = exports2.CODING_MODEL = exports2.PLANNING_MODEL = exports2.isBuiltinPreset = exports2.BUILTIN_PRESET_IDS = exports2.writeBuiltinAutoReviewValidators = exports2.resolvePrimaryStack = exports2.getSdlcArtifactTemplates = exports2.getBuiltinArtifactTemplates = exports2.phaseArtifactFileName = exports2.sdlcClaudeCommand = exports2.builtinClaudeCommand = exports2.loadAllBuiltinPresets = exports2.loadBuiltinPreset = exports2.planRecipeMigration = exports2.getBuiltinRecipeSummaries = exports2.getAllBuiltinPipelineSummaries = exports2.getSdlcBuiltinPipelineSummary = exports2.getBuiltinPipelineSummary = exports2.getBuiltinWorkflowByPipelineId = exports2.getBuiltinWorkflow = exports2.workflowSlug = exports2.builtinTemplatesRoot = exports2.pipelineCommandId = exports2.PHASES = exports2.BUILTIN_WORKFLOWS = exports2.withBackfilledStepNames = exports2.describeDrift = exports2.reconcileRunSteps = exports2.RUN_STATE_SCHEMA_VERSION = exports2.migrateRunState = exports2.stepIdentity = exports2.resolvePath = exports2.AutoReviewerError = exports2.runAutoReview = exports2.renderRunReport = exports2.verifyRun = exports2.runExecLoop = exports2.estimateCostUsd = exports2.providerAliases = exports2.ratesFromConfig = exports2.mergeRates = exports2.BUILTIN_RATES = exports2.unchangedProduces = exports2.snapshotProduces = exports2.checkBudget = exports2.weighStepProgress = exports2.isActiveStatus = void 0;
35408
+ exports2.defaultClaudeConfigDir = exports2.getClaudeConfigDirOverride = exports2.setClaudeConfigDir = exports2.resolveDeclaredPath = exports2.expandHome = exports2.ProfileLoadError = exports2.UnknownStandardError = exports2.builtinProfiles = exports2.loadActiveProfile = exports2.loadProfile = exports2.listProfileManifests = exports2.resolveStandard = exports2.workspaceStandard = exports2.isBuiltinProfileId = exports2.ProfileSchema = exports2.TRACE_RULES = exports2.PROFILES_DIR = exports2.DEFAULT_PROFILE_ID = exports2.BUILTIN_PROFILE_IDS = exports2.setEpicMemoryHook = exports2.isEpicMemoryHookEnabled = exports2.installAnnotationTools = exports2.relativeEpicRoot = exports2.provisionDeclaredWorkflows = exports2.provisionWorkflowFiles = exports2.writeWorkflowArtifactTemplates = exports2.writeWorkflowCommands = exports2.provisionShortcutDocs = exports2.writeTwoLayerCommands = exports2.shortcutCommandDoc = exports2.backboneCommandDoc = exports2.unprovisionedPhases = exports2.nextEligiblePhase = exports2.resolveComposition = exports2.shortcutCommandId = exports2.getCanonicalPhase = exports2.isCanonicalPhase = exports2.BACKBONE_COMMAND_ID = exports2.CANONICAL_PHASE_IDS = exports2.CANONICAL_PHASES = exports2.renderTemplate = exports2.DEFAULT_GLOBAL_WORKFLOW_IDS = exports2.detectGlobalBuiltinSource = exports2.uninstallWorkflowGlobalsByIds = exports2.isWorkflowGloballyInstalled = exports2.installWorkflowGlobalsByIds = exports2.installGlobalDefaults = exports2.stepModelOptions = exports2.pickAgentModel = exports2.resolveProviderModel = void 0;
35409
+ exports2.AIDLC_CORE_VERSION = exports2.AIDLC_CLI_GUIDE_TEXT = exports2.AIDLC_KNOWLEDGE = exports2.clearResolveCommandCache = exports2.resolveCommand = exports2.remapClaudePath = exports2.claudeConfigEnv = exports2.claudeJsonPath = exports2.isDefaultClaudeConfigDir = exports2.claudeConfigDir = exports2.resolveClaudeConfigDir = void 0;
34145
35410
  var WorkspaceSchema_1 = require_WorkspaceSchema();
34146
35411
  Object.defineProperty(exports2, "WorkspaceSchema", { enumerable: true, get: function() {
34147
35412
  return WorkspaceSchema_1.WorkspaceSchema;
@@ -34176,6 +35441,24 @@ var require_dist = __commonJS({
34176
35441
  Object.defineProperty(exports2, "collectWorkspaceRefIssues", { enumerable: true, get: function() {
34177
35442
  return WorkspaceSchema_1.collectWorkspaceRefIssues;
34178
35443
  } });
35444
+ Object.defineProperty(exports2, "findStepIndex", { enumerable: true, get: function() {
35445
+ return WorkspaceSchema_1.findStepIndex;
35446
+ } });
35447
+ Object.defineProperty(exports2, "sendBackIssues", { enumerable: true, get: function() {
35448
+ return WorkspaceSchema_1.sendBackIssues;
35449
+ } });
35450
+ Object.defineProperty(exports2, "AMBIGUOUS_STEP_INDEX", { enumerable: true, get: function() {
35451
+ return WorkspaceSchema_1.AMBIGUOUS_STEP_INDEX;
35452
+ } });
35453
+ Object.defineProperty(exports2, "DEFAULT_MAX_SEND_BACKS", { enumerable: true, get: function() {
35454
+ return WorkspaceSchema_1.DEFAULT_MAX_SEND_BACKS;
35455
+ } });
35456
+ Object.defineProperty(exports2, "DEFAULT_MAX_BATCHES", { enumerable: true, get: function() {
35457
+ return WorkspaceSchema_1.DEFAULT_MAX_BATCHES;
35458
+ } });
35459
+ Object.defineProperty(exports2, "unsafeRunnerArg", { enumerable: true, get: function() {
35460
+ return WorkspaceSchema_1.unsafeRunnerArg;
35461
+ } });
34179
35462
  var PipelineAssembler_1 = require_PipelineAssembler();
34180
35463
  Object.defineProperty(exports2, "assemblePipeline", { enumerable: true, get: function() {
34181
35464
  return PipelineAssembler_1.assemblePipeline;
@@ -34739,6 +36022,38 @@ var require_dist = __commonJS({
34739
36022
  Object.defineProperty(exports2, "claudeModelArg", { enumerable: true, get: function() {
34740
36023
  return DefaultRunner_1.claudeModelArg;
34741
36024
  } });
36025
+ var processTree_1 = require_processTree();
36026
+ Object.defineProperty(exports2, "processGroupSpawnOptions", { enumerable: true, get: function() {
36027
+ return processTree_1.processGroupSpawnOptions;
36028
+ } });
36029
+ Object.defineProperty(exports2, "stopOnAbort", { enumerable: true, get: function() {
36030
+ return processTree_1.stopOnAbort;
36031
+ } });
36032
+ Object.defineProperty(exports2, "stopProcessTree", { enumerable: true, get: function() {
36033
+ return processTree_1.stopProcessTree;
36034
+ } });
36035
+ Object.defineProperty(exports2, "STOP_GRACE_MS", { enumerable: true, get: function() {
36036
+ return processTree_1.STOP_GRACE_MS;
36037
+ } });
36038
+ var autopilotPreflight_1 = require_autopilotPreflight();
36039
+ Object.defineProperty(exports2, "autopilotPreflight", { enumerable: true, get: function() {
36040
+ return autopilotPreflight_1.autopilotPreflight;
36041
+ } });
36042
+ Object.defineProperty(exports2, "findApprovalGate", { enumerable: true, get: function() {
36043
+ return autopilotPreflight_1.findApprovalGate;
36044
+ } });
36045
+ Object.defineProperty(exports2, "hasEmptyCiChecks", { enumerable: true, get: function() {
36046
+ return autopilotPreflight_1.hasEmptyCiChecks;
36047
+ } });
36048
+ Object.defineProperty(exports2, "emptyCiChecksMessage", { enumerable: true, get: function() {
36049
+ return autopilotPreflight_1.emptyCiChecksMessage;
36050
+ } });
36051
+ Object.defineProperty(exports2, "APPROVAL_GATE_SCRIPT", { enumerable: true, get: function() {
36052
+ return autopilotPreflight_1.APPROVAL_GATE_SCRIPT;
36053
+ } });
36054
+ Object.defineProperty(exports2, "AUTOPILOT_ENV", { enumerable: true, get: function() {
36055
+ return autopilotPreflight_1.AUTOPILOT_ENV;
36056
+ } });
34742
36057
  var CodexRunner_1 = require_CodexRunner();
34743
36058
  Object.defineProperty(exports2, "CodexRunner", { enumerable: true, get: function() {
34744
36059
  return CodexRunner_1.CodexRunner;
@@ -43913,7 +45228,7 @@ var require_package = __commonJS({
43913
45228
  "package.json"(exports2, module2) {
43914
45229
  module2.exports = {
43915
45230
  name: "@delete101020/aidlc",
43916
- version: "4.0.39",
45231
+ version: "4.0.42",
43917
45232
  description: "AIDLC Native CLI, a fork of AIDLC \u2014 drive Claude through the six-stage AI-Native SDLC Playbook: Plan \u2192 Design \u2192 Build \u2192 Test \u2192 Deploy \u2192 Maintain, with artifact gates, requirements traceability, an incident loop back to Plan, and Spec Kit support.",
43918
45233
  license: "MIT",
43919
45234
  repository: {
@@ -47180,6 +48495,76 @@ function providerChecks(ws) {
47180
48495
  }
47181
48496
  return checks;
47182
48497
  }
48498
+ function autopilotChecks(root, ws) {
48499
+ const checks = [];
48500
+ const autopilot = ws.config.pipelines.filter((p) => p.autopilot).map((p) => p.id);
48501
+ const needed = autopilot.length > 0;
48502
+ const missing = (label, info2) => needed ? fail(label, info2) : warn(label, info2);
48503
+ if (needed) {
48504
+ checks.push(ok(`autopilot pipeline${autopilot.length !== 1 ? "s" : ""}`, autopilot.join(", ")));
48505
+ }
48506
+ const usesClaude = ws.config.agents.some((a) => a.runner === "default");
48507
+ if (usesClaude) {
48508
+ const gate = (0, import_core5.findApprovalGate)(root);
48509
+ checks.push(gate.found ? ok("approval gate hook", path4.relative(root, gate.settingsFile).split(path4.sep).join("/") || gate.settingsFile) : missing("approval gate hook", gate.reason));
48510
+ const args = ws.config.providers.default?.args ?? [];
48511
+ const viaArgs = args.some((a) => /^--(allowedTools|allowed-tools|permission-mode)(=|$)/.test(a));
48512
+ const viaSettings = claudeAllowListFiles(root);
48513
+ checks.push(viaArgs ? ok("headless permissions", "providers.default.args") : viaSettings.length > 0 ? ok("headless permissions", `permissions.allow in ${viaSettings.join(", ")}`) : missing(
48514
+ "headless permissions",
48515
+ 'nothing allows Claude to edit files or run commands without asking, and under --print nobody can be asked \u2014 set providers.default.args (e.g. [--permission-mode, acceptEdits, --allowedTools, "Bash(npm test:*)"])'
48516
+ ));
48517
+ }
48518
+ for (const id of autopilot) {
48519
+ const pipeline = ws.config.pipelines.find((p) => p.id === id);
48520
+ const runners = new Set(pipeline.steps.map((s) => ws.config.agents.find((a) => a.id === (typeof s === "string" ? s : s.agent))?.runner).filter((r) => !!r && r !== "default"));
48521
+ for (const runner of runners) {
48522
+ checks.push(warn(
48523
+ `"${id}" on ${runner}`,
48524
+ `the approval gate is a Claude Code hook, and ${runner} steps run without it \u2014 they rely on ${runner}'s own sandbox`
48525
+ ));
48526
+ }
48527
+ }
48528
+ const unattended = [
48529
+ ...ws.config.pipelines.filter((p) => p.autopilot).map((p) => ({ label: `"${p.id}"`, pipeline: p, fatal: true }))
48530
+ ];
48531
+ for (const r of ws.config.recipes.filter((r2) => r2.autopilot)) {
48532
+ try {
48533
+ unattended.push({ label: `recipe "${r.id}"`, pipeline: (0, import_core5.assemblePipeline)(ws.config, { recipeId: r.id }), fatal: false });
48534
+ } catch {
48535
+ }
48536
+ }
48537
+ for (const { label, pipeline, fatal } of unattended) {
48538
+ for (const s of pipeline.steps) {
48539
+ const norm = (0, import_core5.normalizeStep)(s);
48540
+ const runner = norm.auto_review ? norm.auto_review_runner : void 0;
48541
+ if (!runner) {
48542
+ continue;
48543
+ }
48544
+ const step = norm.name ?? norm.agent;
48545
+ checks.push((0, import_core5.hasEmptyCiChecks)(root, runner) ? (fatal ? fail : warn)(`${label} CI checks`, (0, import_core5.emptyCiChecksMessage)(runner, step)) : ok(`${label} auto-review on "${step}"`, runner));
48546
+ }
48547
+ }
48548
+ return checks;
48549
+ }
48550
+ function claudeAllowListFiles(root) {
48551
+ const files = [
48552
+ path4.join(root, ".claude", "settings.json"),
48553
+ path4.join(root, ".claude", "settings.local.json"),
48554
+ path4.join((0, import_core5.claudeConfigDir)(), "settings.json")
48555
+ ];
48556
+ return files.filter((f) => {
48557
+ try {
48558
+ const allow = JSON.parse(fs3.readFileSync(f, "utf8"))?.permissions?.allow;
48559
+ return Array.isArray(allow) && allow.length > 0;
48560
+ } catch {
48561
+ return false;
48562
+ }
48563
+ }).map((f) => {
48564
+ const r = path4.relative(root, f);
48565
+ return r.startsWith("..") ? f.replace(os.homedir(), "~") : r.split(path4.sep).join("/");
48566
+ });
48567
+ }
47183
48568
  function registerDoctor(program3) {
47184
48569
  program3.command("doctor").description("Validate workspace, claude binary, env, skills, and run state files").option("--json", "Output all checks as JSON (exit 1 still signals failures)").action((opts, cmd) => {
47185
48570
  const root = resolveWorkspaceRoot(cmd);
@@ -47288,6 +48673,7 @@ function registerDoctor(program3) {
47288
48673
  if (ws) {
47289
48674
  emitSection("Harness parity", parityChecks(root, ws));
47290
48675
  emitSection("Providers", providerChecks(ws));
48676
+ emitSection("Unattended runs", autopilotChecks(root, ws));
47291
48677
  }
47292
48678
  const runChecks = [];
47293
48679
  const runsDir = path4.join(root, ".aidlc", "runs");
@@ -47598,21 +48984,9 @@ function findPipelineForRun(root, state) {
47598
48984
  return null;
47599
48985
  }
47600
48986
  }
47601
- var AMBIGUOUS_STEP = -2;
48987
+ var AMBIGUOUS_STEP = import_core7.AMBIGUOUS_STEP_INDEX;
47602
48988
  function resolveStepIdx(state, step) {
47603
- const asInt = parseInt(step, 10);
47604
- if (!isNaN(asInt) && String(asInt) === step) {
47605
- return asInt >= 0 && asInt < state.steps.length ? asInt : -1;
47606
- }
47607
- const byName = state.steps.findIndex((s) => s.name === step);
47608
- if (byName >= 0) {
47609
- return byName;
47610
- }
47611
- const byAgent = state.steps.map((s, i) => s.agent === step ? i : -1).filter((i) => i >= 0);
47612
- if (byAgent.length > 1) {
47613
- return AMBIGUOUS_STEP;
47614
- }
47615
- return byAgent.length === 1 ? byAgent[0] : -1;
48989
+ return (0, import_core7.findStepIndex)(state.steps, step);
47616
48990
  }
47617
48991
  function stepLabel(s) {
47618
48992
  return s.name ?? s.agent;
@@ -48752,19 +50126,40 @@ function registerRun(program3) {
48752
50126
  console.log(import_chalk10.default.green("\u{1F389} Run completed \u2014 all steps approved."));
48753
50127
  }
48754
50128
  });
48755
- cmd.command("reject <runId>").description("Reject the current awaiting_review step").requiredOption("--reason <text>", "Why the step was rejected").action((runId, opts, actionCmd) => {
50129
+ cmd.command("reject <runId>").description(
50130
+ 'Reject the current awaiting_review step.\n Without --to the work stays on this step (then `run rerun`). With --to it goes\n back to an earlier step: that step reopens with the reason as its feedback and\n the steps after it reset to pending. Mirrors the extension\'s "Send back to step N".'
50131
+ ).requiredOption("--reason <text>", "Why the step was rejected").option("--to <step>", "Earlier step to send the work back to (0-based index, step name or agent id)").action((runId, opts, actionCmd) => {
48756
50132
  const root = resolveWorkspaceRoot(actionCmd);
48757
50133
  const state = requireRun(root, runId);
48758
50134
  const pipeline = requirePipelineForRun(root, state);
50135
+ const idx = state.currentStepIdx;
50136
+ let targetIdx;
50137
+ if (opts.to !== void 0) {
50138
+ targetIdx = requireStepIdx(state, opts.to);
50139
+ if (targetIdx >= idx) {
50140
+ console.error(import_chalk10.default.red(
50141
+ `--to must name a step before the rejected one (${idx}:${state.steps[idx].agent}), got ${targetIdx}:${state.steps[targetIdx].agent}.`
50142
+ ));
50143
+ process.exit(1);
50144
+ }
50145
+ }
48759
50146
  let next;
48760
50147
  try {
48761
- next = (0, import_core13.rejectStep)({ state, reason: opts.reason, pipeline });
50148
+ next = (0, import_core13.rejectStep)({ state, reason: opts.reason, targetIdx, pipeline });
48762
50149
  } catch (err) {
48763
50150
  console.error(import_chalk10.default.red(err instanceof Error ? err.message : String(err)));
48764
50151
  process.exit(1);
48765
50152
  }
48766
50153
  saveRunState(root, next, state);
48767
- const step = state.steps[state.currentStepIdx];
50154
+ const step = state.steps[idx];
50155
+ if (targetIdx !== void 0) {
50156
+ const target = next.steps[targetIdx];
50157
+ console.log(import_chalk10.default.red("\u2718") + ` Rejected "${step.agent}" \u2014 sent back to "${target.agent}" (rev ${target.revision})`);
50158
+ console.log(import_chalk10.default.dim(` Reason: ${opts.reason}`));
50159
+ console.log(import_chalk10.default.dim(` When done: aidlc run mark-done ${runId}`));
50160
+ printRunSummary(next);
50161
+ return;
50162
+ }
48768
50163
  console.log(import_chalk10.default.red("\u2718") + ` Rejected "${step.agent}"`);
48769
50164
  console.log(import_chalk10.default.dim(` Reason: ${opts.reason}`));
48770
50165
  console.log(import_chalk10.default.dim(` Rerun: aidlc run rerun ${runId} [--feedback "..."]`));
@@ -48867,7 +50262,7 @@ function registerRun(program3) {
48867
50262
  console.log(JSON.stringify(state, null, 2));
48868
50263
  });
48869
50264
  cmd.command("exec <runId>").description(
48870
- "Execute the current step by spawning the claude CLI, then auto-advance.\n Streams output live. Stops at human_review steps unless --auto-approve.\n Exit codes: 0 completed (or stopped at --until), 2 paused on a gate\n (awaiting review / rejected / budget), 1 error. --require-complete maps\n any non-completed outcome to 1 for CI gating."
50265
+ "Execute the current step by spawning the claude CLI, then auto-advance.\n Streams output live. Stops at human_review steps unless --auto-approve.\n Exit codes: 0 completed (or stopped at --until), 2 paused on a gate\n (awaiting review / rejected / sent back max_send_backs times / no code\n change after a send-back or a batch / max_batches / pause_between_batches /\n budget / stopped), 1 error (a step that ran\n past its timeout_minutes included). --require-complete maps any\n non-completed outcome to 1 for CI gating.\n Ctrl+C once: stop after the running step. Twice: stop it now."
48871
50266
  ).option("--until <step>", "Stop after this step completes (index or agent id)").option("--auto-approve", "Also auto-approve human_review steps without pausing").option("--require-complete", "Exit 1 unless the run reaches completed (for CI gating)").option("--json", "Suppress decorative output; print a final JSON summary to stdout (claude stream goes to stderr)").option("--message <text>", "Override the user message sent to claude (default: context pairs)").option("--dry-run", "Print the assembled prompt without spawning claude").option("--skill <id>", "On steps that offer alternative skills (default_skill), run this one").action(async (runId, opts, actionCmd) => {
48872
50267
  const root = resolveWorkspaceRoot(actionCmd);
48873
50268
  if (opts.json) {
@@ -48974,12 +50369,38 @@ async function execLoop(root, runId, opts) {
48974
50369
  const initialState = requireRun(root, runId);
48975
50370
  const untilIdx = opts.until !== void 0 ? requireStepIdx(initialState, opts.until) : -1;
48976
50371
  const claudeOut = opts.json ? process.stderr : process.stdout;
48977
- return (0, import_core13.runExecLoop)(
48978
- root,
48979
- runId,
48980
- { untilIdx, autoApprove: opts.autoApprove, message: opts.message, dryRun: opts.dryRun, skill: opts.skill },
48981
- cliExecHooks(runId, claudeOut)
48982
- );
50372
+ let cancelRequested = false;
50373
+ const stopNow = new AbortController();
50374
+ const onSigint = () => {
50375
+ if (!cancelRequested) {
50376
+ cancelRequested = true;
50377
+ console.error(import_chalk10.default.yellow("\n\u23F9 Stopping after the running step finishes. Press Ctrl+C again to stop it now."));
50378
+ } else if (!stopNow.signal.aborted) {
50379
+ console.error(import_chalk10.default.yellow("\n\u23F9 Stopping the running step now\u2026"));
50380
+ stopNow.abort();
50381
+ } else {
50382
+ process.exit(130);
50383
+ }
50384
+ };
50385
+ process.on("SIGINT", onSigint);
50386
+ try {
50387
+ return await (0, import_core13.runExecLoop)(
50388
+ root,
50389
+ runId,
50390
+ {
50391
+ untilIdx,
50392
+ autoApprove: opts.autoApprove,
50393
+ message: opts.message,
50394
+ dryRun: opts.dryRun,
50395
+ skill: opts.skill,
50396
+ shouldCancel: () => cancelRequested,
50397
+ signal: stopNow.signal
50398
+ },
50399
+ cliExecHooks(runId, claudeOut)
50400
+ );
50401
+ } finally {
50402
+ process.off("SIGINT", onSigint);
50403
+ }
48983
50404
  }
48984
50405
  async function driveAutoReview(root, runId, state) {
48985
50406
  const pipeline = requirePipelineForRun(root, state);
@@ -49079,6 +50500,35 @@ ${reason}`)),
49079
50500
  \u2718 Step "${e.agent}" was rejected.`));
49080
50501
  info(import_chalk10.default.dim(` Rerun: aidlc run rerun ${e.runId} [--feedback "..."]`));
49081
50502
  },
50503
+ onStepStopped: (e) => {
50504
+ info(import_chalk10.default.yellow(`
50505
+ \u23F9 Stopped "${e.agent}" mid-step (rev ${e.revision}). It is still awaiting work.`));
50506
+ info(import_chalk10.default.dim(" Anything it half-wrote is still in the working tree \u2014 check git status."));
50507
+ info(import_chalk10.default.dim(` Resume: aidlc run exec ${e.runId}`));
50508
+ },
50509
+ onCancelled: () => info(import_chalk10.default.yellow("\n\u23F9 Stopped after the step that was running.")),
50510
+ onStalled: (e) => {
50511
+ info(import_chalk10.default.yellow(`
50512
+ \u23F8 ${e.reason}`));
50513
+ info(import_chalk10.default.dim(` What it was asked, and what it did: aidlc run report ${e.runId}`));
50514
+ info(import_chalk10.default.dim(` Then make the change and: aidlc run mark-done ${e.runId} \u2014 or try again: aidlc run exec ${e.runId}`));
50515
+ },
50516
+ onBatchDone: (e) => {
50517
+ sep4();
50518
+ info(import_chalk10.default.cyan(`
50519
+ \u21BB Batch ${e.batch} of "${e.agent}" done \u2014 starting the next in a fresh session.`));
50520
+ if (typeof e.costUsd === "number") {
50521
+ info(import_chalk10.default.dim(` cost: $${e.costUsd.toFixed(4)}`));
50522
+ }
50523
+ },
50524
+ onBatchPause: (e) => {
50525
+ info(import_chalk10.default.cyan(`
50526
+ \u23F8 Batch ${e.batch} of "${e.agent}" done; pausing (pause_between_batches).`));
50527
+ if (e.report) {
50528
+ info(import_chalk10.default.dim(` Progress: ${e.report}`));
50529
+ }
50530
+ info(import_chalk10.default.dim(` Next batch: aidlc run exec ${e.runId}`));
50531
+ },
49082
50532
  onStepSkipped: (e) => {
49083
50533
  info(import_chalk10.default.yellow(`
49084
50534
  \u2933 Step "${e.agent}" was rejected \u2014 carrying on.`) + import_chalk10.default.dim(` ${e.reason}`));
@@ -49089,6 +50539,9 @@ ${reason}`)),
49089
50539
  onAutoReviewResult: (e) => {
49090
50540
  if (e.decision === "pass") {
49091
50541
  info(import_chalk10.default.green("\u2714 Auto-review passed") + import_chalk10.default.dim(` \u2014 ${e.reason}`));
50542
+ } else if (e.sentBackTo) {
50543
+ const to = e.sentBackTo;
50544
+ info(import_chalk10.default.yellow(`\u21A9 Auto-review rejected \u2014 sent back to "${to.agent}" (rev ${to.revision}, send-back ${to.count} of ${to.max})`) + import_chalk10.default.dim(` \u2014 ${e.reason}`));
49092
50545
  } else {
49093
50546
  info(import_chalk10.default.red("\u2718 Auto-review rejected") + import_chalk10.default.dim(` \u2014 ${e.reason}`));
49094
50547
  info(import_chalk10.default.dim(` Fix the issue, then: aidlc run rerun ${e.runId} [--feedback "..."]`));