@delete101020/aidlc 4.0.38 → 4.0.41

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,14 +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;
19772
+ exports2.requiresEntries = requiresEntries;
19771
19773
  exports2.stepAgentId = stepAgentId3;
19772
19774
  exports2.stepDagId = stepDagId;
19775
+ exports2.findStepIndex = findStepIndex2;
19776
+ exports2.sendBackIssues = sendBackIssues;
19773
19777
  exports2.collectWorkspaceRefIssues = collectWorkspaceRefIssues3;
19778
+ exports2.unsafeRunnerArg = unsafeRunnerArg;
19774
19779
  exports2.validateWorkspace = validateWorkspace6;
19775
19780
  var zod_1 = require_zod();
19776
19781
  var FollowUpHookSchema_1 = require_FollowUpHookSchema();
@@ -19916,8 +19921,14 @@ var require_WorkspaceSchema = __commonJS({
19916
19921
  * agent's `models` as one more choice on the card; a pick there wins.
19917
19922
  */
19918
19923
  model: zod_1.z.string().min(1).optional(),
19919
- /** Artifact paths required from upstream. Gate-checked before work AND on Mark step done. */
19920
- requires: zod_1.z.array(zod_1.z.string().min(1)).default([]),
19924
+ /**
19925
+ * Artifact paths required from upstream. Gate-checked before work AND on
19926
+ * Mark step done — except entries marked `{ path, optional: true }`, for
19927
+ * an upstream file that is legitimately absent on some runs (an upstream
19928
+ * `produces` entry that is itself optional). Those are listed with the
19929
+ * step but never block it.
19930
+ */
19931
+ requires: zod_1.z.array(ProducesEntrySchema).default([]),
19921
19932
  /**
19922
19933
  * Agent ids of upstream steps this step depends on. When non-empty the
19923
19934
  * step stays `pending` until every listed step transitions to `approved`,
@@ -19940,6 +19951,46 @@ var require_WorkspaceSchema = __commonJS({
19940
19951
  * (infinite loop, network stall). Defaults to 120_000 when omitted.
19941
19952
  */
19942
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(),
19943
19994
  /** When true, the runner pauses for human approval before advancing. */
19944
19995
  human_review: zod_1.z.boolean().default(false),
19945
19996
  /**
@@ -20001,12 +20052,42 @@ var require_WorkspaceSchema = __commonJS({
20001
20052
  recipe: zod_1.z.string().min(1).optional(),
20002
20053
  steps: zod_1.z.array(PipelineStepSchema).min(1),
20003
20054
  on_failure: zod_1.z.enum(["stop", "continue"]).default("stop"),
20004
- 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
+ }
20005
20076
  });
20077
+ exports2.DEFAULT_MAX_SEND_BACKS = 3;
20078
+ exports2.DEFAULT_MAX_BATCHES = 10;
20006
20079
  var RecipeGateSchema = zod_1.z.object({
20007
20080
  human_review: zod_1.z.boolean().optional(),
20008
20081
  auto_review: zod_1.z.boolean().optional(),
20009
- 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()
20010
20091
  });
20011
20092
  var RecipeSchema = zod_1.z.object({
20012
20093
  id: zod_1.z.string().min(1),
@@ -20031,9 +20112,13 @@ var require_WorkspaceSchema = __commonJS({
20031
20112
  * plan) and none after, a risky one wants them everywhere. Only the fields
20032
20113
  * present override; the rest inherit.
20033
20114
  */
20034
- 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()
20035
20120
  });
20036
- function normalizeStep4(step) {
20121
+ function normalizeStep5(step) {
20037
20122
  if (typeof step === "string") {
20038
20123
  return {
20039
20124
  agent: step,
@@ -20043,26 +20128,15 @@ var require_WorkspaceSchema = __commonJS({
20043
20128
  produces_optional: [],
20044
20129
  produces_contains: [],
20045
20130
  requires: [],
20131
+ requires_optional: [],
20046
20132
  depends_on: [],
20047
20133
  auto_review: false,
20048
20134
  human_review: false
20049
20135
  };
20050
20136
  }
20051
20137
  const obj = step;
20052
- const requires = Array.isArray(obj.requires) ? obj.requires : [];
20053
- const produces = [];
20054
- const produces_optional = [];
20055
- for (const entry of Array.isArray(obj.produces) ? obj.produces : []) {
20056
- if (typeof entry === "string") {
20057
- produces.push(entry);
20058
- } else if (entry && typeof entry === "object" && typeof entry.path === "string") {
20059
- const e = entry;
20060
- produces.push(e.path);
20061
- if (e.optional === true) {
20062
- produces_optional.push(e.path);
20063
- }
20064
- }
20065
- }
20138
+ const [requires, requires_optional] = splitArtifactEntries(obj.requires);
20139
+ const [produces, produces_optional] = splitArtifactEntries(obj.produces);
20066
20140
  const produces_contains = Array.isArray(obj.produces_contains) ? obj.produces_contains : [];
20067
20141
  const depends_on = Array.isArray(obj.depends_on) ? obj.depends_on : [];
20068
20142
  const skills = Array.isArray(obj.skills) ? obj.skills.map(String).filter((s) => s.length > 0) : typeof obj.skill === "string" && obj.skill.length > 0 ? [obj.skill] : void 0;
@@ -20085,10 +20159,19 @@ var require_WorkspaceSchema = __commonJS({
20085
20159
  produces_optional,
20086
20160
  produces_contains,
20087
20161
  requires,
20162
+ requires_optional,
20088
20163
  depends_on,
20089
20164
  auto_review: obj.auto_review === true,
20090
20165
  auto_review_runner: typeof obj.auto_review_runner === "string" ? obj.auto_review_runner : void 0,
20091
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
+ } : {},
20092
20175
  human_review: obj.human_review === true,
20093
20176
  ...git && { git }
20094
20177
  };
@@ -20110,6 +20193,25 @@ var require_WorkspaceSchema = __commonJS({
20110
20193
  const optional = new Set(norm.produces_optional);
20111
20194
  return norm.produces.map((p) => optional.has(p) ? { path: p, optional: true } : p);
20112
20195
  }
20196
+ function requiresEntries(norm) {
20197
+ return producesEntries({ produces: norm.requires, produces_optional: norm.requires_optional });
20198
+ }
20199
+ function splitArtifactEntries(raw) {
20200
+ const all = [];
20201
+ const optional = [];
20202
+ for (const entry of Array.isArray(raw) ? raw : []) {
20203
+ if (typeof entry === "string") {
20204
+ all.push(entry);
20205
+ } else if (entry && typeof entry === "object" && typeof entry.path === "string") {
20206
+ const e = entry;
20207
+ all.push(e.path);
20208
+ if (e.optional === true) {
20209
+ optional.push(e.path);
20210
+ }
20211
+ }
20212
+ }
20213
+ return [all, optional];
20214
+ }
20113
20215
  function stepAgentId3(step) {
20114
20216
  if (typeof step === "string") {
20115
20217
  return step;
@@ -20120,9 +20222,57 @@ var require_WorkspaceSchema = __commonJS({
20120
20222
  return "";
20121
20223
  }
20122
20224
  function stepDagId(step) {
20123
- const norm = normalizeStep4(step);
20225
+ const norm = normalizeStep5(step);
20124
20226
  return norm.name ?? norm.agent;
20125
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
+ }
20126
20276
  function collectWorkspaceRefIssues3(config) {
20127
20277
  const issues = [];
20128
20278
  const agentIds = new Set(config.agents.map((a) => a.id));
@@ -20140,7 +20290,7 @@ var require_WorkspaceSchema = __commonJS({
20140
20290
  }
20141
20291
  for (const pipeline of config.pipelines) {
20142
20292
  for (const step of pipeline.steps) {
20143
- const norm = normalizeStep4(step);
20293
+ const norm = normalizeStep5(step);
20144
20294
  const id = norm.name ?? norm.agent;
20145
20295
  if (!agentIds.has(norm.agent)) {
20146
20296
  issues.push({
@@ -20266,8 +20416,60 @@ var require_WorkspaceSchema = __commonJS({
20266
20416
  /** Claude tier alias (or any `model:` value) → concrete model id, lowercased keys. */
20267
20417
  model_aliases: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).default({}),
20268
20418
  /** Model id → rate. The key `*` applies to every model of this provider. */
20269
- 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
+ }
20270
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
+ }
20271
20473
  exports2.WorkspaceSchema = zod_1.z.object({
20272
20474
  /** Schema version — bump on breaking changes. Currently always "1.0". */
20273
20475
  version: zod_1.z.string().min(1),
@@ -20378,7 +20580,7 @@ var require_PipelineAssembler = __commonJS({
20378
20580
  "use strict";
20379
20581
  Object.defineProperty(exports2, "__esModule", { value: true });
20380
20582
  exports2.PipelineAssembleError = void 0;
20381
- exports2.assemblePipeline = assemblePipeline4;
20583
+ exports2.assemblePipeline = assemblePipeline5;
20382
20584
  exports2.recipePipelineId = recipePipelineId4;
20383
20585
  exports2.pipelineRecipeLabel = pipelineRecipeLabel;
20384
20586
  var WorkspaceSchema_1 = require_WorkspaceSchema();
@@ -20389,7 +20591,7 @@ var require_PipelineAssembler = __commonJS({
20389
20591
  }
20390
20592
  };
20391
20593
  exports2.PipelineAssembleError = PipelineAssembleError4;
20392
- function assemblePipeline4(config, opts) {
20594
+ function assemblePipeline5(config, opts) {
20393
20595
  const recipe = config.recipes.find((r) => r.id === opts.recipeId);
20394
20596
  if (!recipe) {
20395
20597
  const available = config.recipes.map((r) => r.id).join(", ") || "(none defined)";
@@ -20446,7 +20648,7 @@ var require_PipelineAssembler = __commonJS({
20446
20648
  name: norm.name ?? id,
20447
20649
  enabled: norm.enabled,
20448
20650
  produces: (0, WorkspaceSchema_1.producesEntries)(norm),
20449
- requires: norm.requires,
20651
+ requires: (0, WorkspaceSchema_1.requiresEntries)(norm),
20450
20652
  depends_on,
20451
20653
  auto_review,
20452
20654
  human_review: gate.human_review ?? norm.human_review
@@ -20466,14 +20668,40 @@ var require_PipelineAssembler = __commonJS({
20466
20668
  if (auto_review_runner) {
20467
20669
  step.auto_review_runner = auto_review_runner;
20468
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
+ }
20469
20690
  return step;
20470
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
+ }
20471
20697
  const assembled = {
20472
20698
  id: opts.pipelineId ?? recipe.id,
20473
20699
  derived_from: source.id,
20474
20700
  recipe: recipe.id,
20475
20701
  steps,
20476
- 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 } : {}
20477
20705
  };
20478
20706
  const refIssues = (0, WorkspaceSchema_1.collectWorkspaceRefIssues)({
20479
20707
  ...config,
@@ -20795,7 +21023,7 @@ ${raw.slice(0, 200)}`);
20795
21023
  name: norm.name ?? a.phase,
20796
21024
  enabled: norm.enabled,
20797
21025
  produces: (0, WorkspaceSchema_1.producesEntries)(norm),
20798
- requires: norm.requires,
21026
+ requires: (0, WorkspaceSchema_1.requiresEntries)(norm),
20799
21027
  depends_on: anchor ? [anchor] : usesDag && steps.length > 0 ? [(0, WorkspaceSchema_1.stepDagId)(steps[steps.length - 1])] : [],
20800
21028
  auto_review: norm.auto_review,
20801
21029
  human_review: norm.human_review
@@ -21318,6 +21546,9 @@ var require_PipelineRunner = __commonJS({
21318
21546
  steps
21319
21547
  };
21320
21548
  }
21549
+ function requiredPaths(norm) {
21550
+ return norm.requires.filter((p) => !norm.requires_optional.includes(p));
21551
+ }
21321
21552
  function canStartStep(args) {
21322
21553
  const { state, pipeline, workspaceRoot } = args;
21323
21554
  const idx = args.stepIdx ?? state.currentStepIdx;
@@ -21327,7 +21558,7 @@ var require_PipelineRunner = __commonJS({
21327
21558
  }
21328
21559
  const norm = (0, WorkspaceSchema_1.normalizeStep)(stepConfig);
21329
21560
  const missing = [];
21330
- for (const rel of norm.requires.map((p) => (0, RunState_1.resolvePath)(p, state.context))) {
21561
+ for (const rel of requiredPaths(norm).map((p) => (0, RunState_1.resolvePath)(p, state.context))) {
21331
21562
  const abs = path17.isAbsolute(rel) ? rel : path17.join(workspaceRoot, rel);
21332
21563
  if (!fs14.existsSync(abs)) {
21333
21564
  missing.push(rel);
@@ -21354,7 +21585,7 @@ var require_PipelineRunner = __commonJS({
21354
21585
  throw new PipelineRunError3(`Pipeline mismatch \u2014 step "${(0, RunState_1.stepIdentity)(step)}" is at index ${idx}, which pipeline "${pipeline.id}" does not have.`);
21355
21586
  }
21356
21587
  const norm = (0, WorkspaceSchema_1.normalizeStep)(stepConfig);
21357
- const resolvedRequires = norm.requires.map((p) => (0, RunState_1.resolvePath)(p, state.context));
21588
+ const resolvedRequires = requiredPaths(norm).map((p) => (0, RunState_1.resolvePath)(p, state.context));
21358
21589
  const missingRequires = [];
21359
21590
  for (const rel of resolvedRequires) {
21360
21591
  const abs = path17.isAbsolute(rel) ? rel : path17.join(workspaceRoot, rel);
@@ -21476,6 +21707,7 @@ var require_PipelineRunner = __commonJS({
21476
21707
  const step = state.steps[idx];
21477
21708
  const now = (/* @__PURE__ */ new Date()).toISOString();
21478
21709
  const next = clone(state);
21710
+ delete next.sendBacks;
21479
21711
  if (step.status === "approved") {
21480
21712
  for (const i of followersOf(pipeline, idx)) {
21481
21713
  const follower = next.steps[i];
@@ -21533,13 +21765,34 @@ var require_PipelineRunner = __commonJS({
21533
21765
  runner: verdict.runner
21534
21766
  });
21535
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;
21536
21789
  nextStep.status = "rejected";
21537
- nextStep.rejectReason = verdict.reason;
21790
+ nextStep.rejectReason = reason;
21538
21791
  nextStep.history = pushHistory(nextStep.history, {
21539
21792
  kind: "reject",
21540
21793
  at: verdict.at,
21541
21794
  revision: nextStep.revision,
21542
- reason: verdict.reason,
21795
+ reason,
21543
21796
  sentBackToIdx: idx,
21544
21797
  ...nextStep.skill ? { skill: nextStep.skill } : {},
21545
21798
  ...nextStep.chosenModel ? { model: nextStep.chosenModel } : {}
@@ -21555,6 +21808,35 @@ var require_PipelineRunner = __commonJS({
21555
21808
  }
21556
21809
  return advance(next, idx, pipeline);
21557
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
+ }
21558
21840
  function retryAutoReview(args) {
21559
21841
  const { pipeline } = args;
21560
21842
  const state = alignedOrThrow(args.state, pipeline);
@@ -21577,6 +21859,7 @@ var require_PipelineRunner = __commonJS({
21577
21859
  throw new PipelineRunError3(`Step "${step.agent}" no longer has auto_review enabled in pipeline "${pipeline.id}".`);
21578
21860
  }
21579
21861
  const next = clone(state);
21862
+ delete next.sendBacks;
21580
21863
  next.steps[idx] = {
21581
21864
  ...step,
21582
21865
  status: "awaiting_auto_review",
@@ -21597,7 +21880,9 @@ var require_PipelineRunner = __commonJS({
21597
21880
  if (step.status !== "awaiting_review") {
21598
21881
  throw new PipelineRunError3(`Cannot approve step "${step.agent}": status is "${step.status}", expected "awaiting_review"`);
21599
21882
  }
21600
- return advance(clone(state), idx, pipeline);
21883
+ const next = clone(state);
21884
+ delete next.sendBacks;
21885
+ return advance(next, idx, pipeline);
21601
21886
  }
21602
21887
  function rejectStep3(args) {
21603
21888
  const { state, reason, targetIdx, pipeline } = args;
@@ -21606,75 +21891,19 @@ var require_PipelineRunner = __commonJS({
21606
21891
  if (!step) {
21607
21892
  throw new PipelineRunError3(`No step at index ${idx}`);
21608
21893
  }
21609
- if (step.status !== "awaiting_review") {
21610
- throw new PipelineRunError3(`Cannot reject step "${step.agent}": status is "${step.status}", expected "awaiting_review"`);
21611
- }
21612
21894
  const now = (/* @__PURE__ */ new Date()).toISOString();
21613
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
+ }
21614
21899
  if (isCascade) {
21615
21900
  const next2 = clone(state);
21616
- const blame = `Rejected at step ${idx + 1} (${step.agent})${reason ? `: ${reason}` : ""}`;
21617
- const rejectedHistory = pushHistory(next2.steps[idx].history, {
21618
- kind: "reject",
21619
- at: now,
21620
- revision: next2.steps[idx].revision,
21621
- reason,
21622
- sentBackToIdx: targetIdx,
21623
- ...next2.steps[idx].skill ? { skill: next2.steps[idx].skill } : {},
21624
- ...next2.steps[idx].chosenModel ? { model: next2.steps[idx].chosenModel } : {}
21625
- });
21626
- const usesDag = pipeline ? pipeline.steps.map(WorkspaceSchema_1.normalizeStep).some((s) => s.depends_on.length > 0) : false;
21627
- const targetIdxN = targetIdx;
21628
- const resetIndices = usesDag && pipeline ? collectDagResetSet(pipeline, state, targetIdxN, idx) : sequentialRange(targetIdxN, idx);
21629
- for (const i of resetIndices) {
21630
- const s = next2.steps[i];
21631
- if (i === targetIdxN) {
21632
- const newRev = s.revision + 1;
21633
- next2.steps[i] = {
21634
- ...s,
21635
- status: "awaiting_work",
21636
- revision: newRev,
21637
- feedback: blame,
21638
- rejectReason: void 0,
21639
- autoReviewVerdict: void 0,
21640
- artifactsProduced: [],
21641
- finishedAt: void 0,
21642
- startedAt: now,
21643
- history: pushHistory(s.history, {
21644
- kind: "rerun",
21645
- at: now,
21646
- revision: newRev,
21647
- feedback: blame
21648
- })
21649
- };
21650
- } else if (i === idx) {
21651
- next2.steps[i] = {
21652
- ...s,
21653
- status: "pending",
21654
- rejectReason: void 0,
21655
- autoReviewVerdict: void 0,
21656
- artifactsProduced: [],
21657
- startedAt: void 0,
21658
- finishedAt: void 0,
21659
- history: rejectedHistory
21660
- };
21661
- } else {
21662
- next2.steps[i] = {
21663
- ...s,
21664
- status: "pending",
21665
- rejectReason: void 0,
21666
- autoReviewVerdict: void 0,
21667
- artifactsProduced: [],
21668
- startedAt: void 0,
21669
- finishedAt: void 0
21670
- };
21671
- }
21672
- }
21673
- next2.currentStepIdx = targetIdx;
21674
- next2.status = "running";
21901
+ sendBack(next2, { idx, targetIdx, reason, at: now, pipeline });
21902
+ delete next2.sendBacks;
21675
21903
  return next2;
21676
21904
  }
21677
21905
  const next = clone(state);
21906
+ delete next.sendBacks;
21678
21907
  next.steps[idx] = {
21679
21908
  ...step,
21680
21909
  status: "rejected",
@@ -21707,6 +21936,7 @@ var require_PipelineRunner = __commonJS({
21707
21936
  }
21708
21937
  const now = (/* @__PURE__ */ new Date()).toISOString();
21709
21938
  const next = clone(state);
21939
+ delete next.sendBacks;
21710
21940
  const newRev = step.revision + 1;
21711
21941
  const carriedFeedback = feedback ?? step.feedback;
21712
21942
  next.steps[idx] = {
@@ -21769,6 +21999,7 @@ var require_PipelineRunner = __commonJS({
21769
21999
  }
21770
22000
  const now = (/* @__PURE__ */ new Date()).toISOString();
21771
22001
  const next = clone(state);
22002
+ delete next.sendBacks;
21772
22003
  const normalized = pipeline.steps.map(WorkspaceSchema_1.normalizeStep);
21773
22004
  const usesDag = normalized.some((s) => s.depends_on.length > 0);
21774
22005
  const upper = state.status === "completed" ? pipeline.steps.length - 1 : state.currentStepIdx;
@@ -21912,6 +22143,7 @@ var require_PipelineRunner = __commonJS({
21912
22143
  const step = state.steps[idx];
21913
22144
  const now = (/* @__PURE__ */ new Date()).toISOString();
21914
22145
  const next = clone(state);
22146
+ delete next.sendBacks;
21915
22147
  const newRev = step.revision + 1;
21916
22148
  const carriedFeedback = feedback ?? step.feedback;
21917
22149
  next.steps[idx] = {
@@ -22058,6 +22290,70 @@ var require_PipelineRunner = __commonJS({
22058
22290
  next.status = "completed";
22059
22291
  }
22060
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
+ }
22061
22357
  function clone(value) {
22062
22358
  return JSON.parse(JSON.stringify(value));
22063
22359
  }
@@ -25470,7 +25766,7 @@ var require_PlanGenerator = __commonJS({
25470
25766
  Object.defineProperty(exports2, "__esModule", { value: true });
25471
25767
  exports2.generatePlan = generatePlan;
25472
25768
  exports2.renderPlanMarkdown = renderPlanMarkdown;
25473
- function normalizeStep4(step) {
25769
+ function normalizeStep5(step) {
25474
25770
  if (typeof step === "string") {
25475
25771
  return { name: step };
25476
25772
  }
@@ -25586,7 +25882,7 @@ var require_PlanGenerator = __commonJS({
25586
25882
  }
25587
25883
  function generatePlan(epicId, context, pipeline, workspace) {
25588
25884
  const complexity = inferScopeComplexity(context.scope);
25589
- const steps = (pipeline.steps || []).map(normalizeStep4);
25885
+ const steps = (pipeline.steps || []).map(normalizeStep5);
25590
25886
  const agentCountPerPhase = allocateAgentCount(complexity, steps.length);
25591
25887
  const phaseAllocations = steps.map((step) => {
25592
25888
  const agentId = step.agent || "unknown";
@@ -26708,6 +27004,11 @@ var require_EpicStepEdit = __commonJS({
26708
27004
  }
26709
27005
  function commitEpicStepEdit2(args) {
26710
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
+ }
26711
27012
  args.writeWorkspace(plan.pipeline);
26712
27013
  try {
26713
27014
  RunStateStore_1.RunStateStore.save(workspaceRoot, plan.runState);
@@ -26754,6 +27055,7 @@ var require_EpicStepEdit = __commonJS({
26754
27055
  delete next.auto_review;
26755
27056
  delete next.auto_review_runner;
26756
27057
  delete next.auto_review_timeout_ms;
27058
+ delete next.on_reject;
26757
27059
  }
26758
27060
  const nextRaw = [...raw];
26759
27061
  nextRaw[idx] = next;
@@ -28662,6 +28964,69 @@ var require_resolveCommand = __commonJS({
28662
28964
  }
28663
28965
  });
28664
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
+
28665
29030
  // ../core/dist/runner/DefaultRunner.js
28666
29031
  var require_DefaultRunner = __commonJS({
28667
29032
  "../core/dist/runner/DefaultRunner.js"(exports2) {
@@ -28674,6 +29039,7 @@ var require_DefaultRunner = __commonJS({
28674
29039
  var mcp_1 = require_mcp();
28675
29040
  var ndjson_1 = require_ndjson();
28676
29041
  var resolveCommand_1 = require_resolveCommand();
29042
+ var processTree_1 = require_processTree();
28677
29043
  var DefaultRunner3 = class {
28678
29044
  opts;
28679
29045
  /**
@@ -28712,6 +29078,10 @@ var require_DefaultRunner = __commonJS({
28712
29078
  ctx.skill,
28713
29079
  ...model ? ["--model", model] : [],
28714
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
+ "--",
28715
29085
  userMessage
28716
29086
  ];
28717
29087
  const cmd = (0, resolveCommand_1.resolveCommand)(bin);
@@ -28721,8 +29091,10 @@ var require_DefaultRunner = __commonJS({
28721
29091
  // inside a Claude Code session — a fresh `claude` rejects it as invalid.
28722
29092
  // ctx.env (workspace-configured) is layered last so explicit keys win.
28723
29093
  env: (0, claudeEnv_1.buildClaudeSpawnEnv)(ctx.env),
28724
- stdio: ["ignore", "pipe", "pipe"]
29094
+ stdio: ["ignore", "pipe", "pipe"],
29095
+ ...(0, processTree_1.processGroupSpawnOptions)()
28725
29096
  });
29097
+ const detach = (0, processTree_1.stopOnAbort)(proc, ctx.signal);
28726
29098
  let finalText = "";
28727
29099
  let costUsd;
28728
29100
  const handleEvent = (evt) => {
@@ -28748,11 +29120,13 @@ var require_DefaultRunner = __commonJS({
28748
29120
  });
28749
29121
  return new Promise((resolve5) => {
28750
29122
  proc.on("error", (err) => {
29123
+ detach();
28751
29124
  ctx.onError(`Failed to spawn ${bin}: ${err.message}
28752
29125
  `);
28753
29126
  resolve5({ success: false, output: finalText, costUsd });
28754
29127
  });
28755
29128
  proc.on("close", (code) => {
29129
+ detach();
28756
29130
  sink.flush();
28757
29131
  resolve5({ success: code === 0, output: finalText, costUsd });
28758
29132
  });
@@ -28823,6 +29197,7 @@ var require_CodexRunner = __commonJS({
28823
29197
  var ndjson_1 = require_ndjson();
28824
29198
  var models_1 = require_models();
28825
29199
  var resolveCommand_1 = require_resolveCommand();
29200
+ var processTree_1 = require_processTree();
28826
29201
  var CodexRunner2 = class {
28827
29202
  opts;
28828
29203
  /**
@@ -28859,14 +29234,17 @@ var require_CodexRunner = __commonJS({
28859
29234
  this.opts.sandbox ?? "workspace-write",
28860
29235
  ...model ? ["--model", model] : [],
28861
29236
  ...this.opts.extraArgs ?? [],
29237
+ ...ctx.extraArgs ?? [],
28862
29238
  buildPrompt(ctx)
28863
29239
  ];
28864
29240
  const cmd = (0, resolveCommand_1.resolveCommand)(bin);
28865
29241
  const proc = (0, child_process_1.spawn)(cmd.command, [...cmd.args, ...args], {
28866
29242
  cwd: ctx.workspaceRoot,
28867
29243
  env: { ...process.env, ...ctx.env },
28868
- stdio: ["ignore", "pipe", "pipe"]
29244
+ stdio: ["ignore", "pipe", "pipe"],
29245
+ ...(0, processTree_1.processGroupSpawnOptions)()
28869
29246
  });
29247
+ const detach = (0, processTree_1.stopOnAbort)(proc, ctx.signal);
28870
29248
  let streamed = "";
28871
29249
  let lastMessage = "";
28872
29250
  let sawDelta = false;
@@ -28918,11 +29296,13 @@ var require_CodexRunner = __commonJS({
28918
29296
  proc.stderr.on("data", (d) => ctx.onError(d.toString("utf8")));
28919
29297
  return new Promise((resolve5) => {
28920
29298
  proc.on("error", (err) => {
29299
+ detach();
28921
29300
  ctx.onError(`Failed to spawn ${bin}: ${err.message}
28922
29301
  `);
28923
29302
  resolve5({ success: false, output: "", usage });
28924
29303
  });
28925
29304
  proc.on("close", (code) => {
29305
+ detach();
28926
29306
  sink.flush();
28927
29307
  resolve5({
28928
29308
  success: code === 0,
@@ -28995,6 +29375,7 @@ var require_CopilotRunner = __commonJS({
28995
29375
  var ndjson_1 = require_ndjson();
28996
29376
  var models_1 = require_models();
28997
29377
  var resolveCommand_1 = require_resolveCommand();
29378
+ var processTree_1 = require_processTree();
28998
29379
  var GRAPH_SERVERS2 = ["ast-graph", "codegraph"];
28999
29380
  var CopilotRunner2 = class {
29000
29381
  opts;
@@ -29032,14 +29413,17 @@ var require_CopilotRunner = __commonJS({
29032
29413
  ...this.opts.keepBuiltinMcps ? [] : ["--disable-builtin-mcps"],
29033
29414
  ...graph ? ["--additional-mcp-config", copilotMcpConfig(graph)] : [],
29034
29415
  ...model ? ["--model", model] : [],
29035
- ...this.opts.extraArgs ?? []
29416
+ ...this.opts.extraArgs ?? [],
29417
+ ...ctx.extraArgs ?? []
29036
29418
  ];
29037
29419
  const cmd = (0, resolveCommand_1.resolveCommand)(bin);
29038
29420
  const proc = (0, child_process_1.spawn)(cmd.command, [...cmd.args, ...args], {
29039
29421
  cwd: ctx.workspaceRoot,
29040
29422
  env: { ...process.env, ...ctx.env },
29041
- stdio: ["pipe", "pipe", "pipe"]
29423
+ stdio: ["pipe", "pipe", "pipe"],
29424
+ ...(0, processTree_1.processGroupSpawnOptions)()
29042
29425
  });
29426
+ const detach = (0, processTree_1.stopOnAbort)(proc, ctx.signal);
29043
29427
  let streamed = "";
29044
29428
  let lastMessage = "";
29045
29429
  const deltaSeen = /* @__PURE__ */ new Set();
@@ -29101,11 +29485,13 @@ var require_CopilotRunner = __commonJS({
29101
29485
  proc.stdin.end((0, CodexRunner_1.buildPrompt)(ctx));
29102
29486
  return new Promise((resolve5) => {
29103
29487
  proc.on("error", (err) => {
29488
+ detach();
29104
29489
  ctx.onError(`Failed to spawn ${bin}: ${err.message}
29105
29490
  `);
29106
29491
  resolve5({ success: false, output: "" });
29107
29492
  });
29108
29493
  proc.on("close", (code) => {
29494
+ detach();
29109
29495
  sink.flush();
29110
29496
  resolve5({
29111
29497
  success: code === 0,
@@ -30355,7 +30741,10 @@ var require_promptComposer = __commonJS({
30355
30741
  const inlineInstructions = !!instructions && !harness.projectInstructions;
30356
30742
  const language = artifactLanguage?.trim() || null;
30357
30743
  const depth = (0, strictMode_1.strictModeSection)(input.strictMode === false ? false : true);
30358
- 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) {
30359
30748
  return {
30360
30749
  text: skills,
30361
30750
  included: { persona: false, instructions: false, language: false, depth: false }
@@ -30380,6 +30769,9 @@ var require_promptComposer = __commonJS({
30380
30769
  if (depth) {
30381
30770
  parts.push(depth);
30382
30771
  }
30772
+ if (history) {
30773
+ parts.push(history);
30774
+ }
30383
30775
  const skillText = inlinePersona ? stripPersonaDirectives(skills) : skills;
30384
30776
  parts.push(["## Phase Behavior", "", skillText.trim()].join("\n"));
30385
30777
  return {
@@ -30731,6 +31123,235 @@ var require_commandBodyFreshness = __commonJS({
30731
31123
  }
30732
31124
  });
30733
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
+
30734
31355
  // ../core/dist/runs/GitRunStateStore.js
30735
31356
  var require_GitRunStateStore = __commonJS({
30736
31357
  "../core/dist/runs/GitRunStateStore.js"(exports2) {
@@ -31355,20 +31976,323 @@ Expected a JS/TS module at "${scriptPath}" with a default-exported function retu
31355
31976
  at,
31356
31977
  runner
31357
31978
  };
31358
- } finally {
31359
- if (timer) {
31360
- 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] };
31361
32275
  }
31362
32276
  }
31363
- if (!raw || raw.decision !== "pass" && raw.decision !== "reject" || typeof raw.reason !== "string") {
31364
- return {
31365
- decision: "reject",
31366
- reason: `Auto-reviewer returned malformed verdict: ${JSON.stringify(raw)}`,
31367
- at,
31368
- runner
31369
- };
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.` };
31370
32294
  }
31371
- return { decision: raw.decision, reason: raw.reason, at, runner };
32295
+ return readBatchStatus(text, rel);
31372
32296
  }
31373
32297
  }
31374
32298
  });
@@ -31426,9 +32350,16 @@ var require_execEngine = __commonJS({
31426
32350
  var runProgress_1 = require_runProgress();
31427
32351
  var budget_1 = require_budget();
31428
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();
31429
32359
  var pricing_1 = require_pricing();
31430
32360
  var models_1 = require_models();
31431
32361
  var WorkspaceSchema_1 = require_WorkspaceSchema();
32362
+ var RunState_1 = require_RunState();
31432
32363
  var artifactLanguage_1 = require_artifactLanguage();
31433
32364
  var strictMode_1 = require_strictMode();
31434
32365
  var inheritedContext_1 = require_inheritedContext();
@@ -31444,6 +32375,21 @@ var require_execEngine = __commonJS({
31444
32375
  }
31445
32376
  }
31446
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) {
31447
32393
  const untilIdx = opts.untilIdx ?? -1;
31448
32394
  const initialState = RunStateStore_1.RunStateStore.load(root, runId);
31449
32395
  if (!initialState) {
@@ -31456,8 +32402,23 @@ var require_execEngine = __commonJS({
31456
32402
  return { kind: "error" };
31457
32403
  }
31458
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
+ }
31459
32420
  while (true) {
31460
- if (opts.shouldCancel?.()) {
32421
+ if (opts.shouldCancel?.() || opts.signal?.aborted) {
31461
32422
  hooks.onCancelled?.();
31462
32423
  return { kind: "cancelled" };
31463
32424
  }
@@ -31523,47 +32484,56 @@ var require_execEngine = __commonJS({
31523
32484
  hooks.onRunFailed?.(`Unexpected step status "${step.status}" \u2014 cannot exec.`);
31524
32485
  return { kind: "error" };
31525
32486
  }
31526
- const success = await execStep(root, state, runId, opts, hooks);
31527
- if (!success) {
32487
+ const ran = await execStep(root, state, runId, opts, hooks);
32488
+ if (ran === "failed") {
31528
32489
  return { kind: "error" };
31529
32490
  }
31530
- if (opts.dryRun) {
31531
- return { kind: "dry_run" };
32491
+ if (ran === "stalled") {
32492
+ return { kind: "stalled" };
32493
+ }
32494
+ if (ran === "stopped") {
32495
+ return { kind: "cancelled" };
31532
32496
  }
31533
- 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);
31534
32507
  const after = RunStateStore_1.RunStateStore.load(root, runId);
31535
- const stepCosts = after ? after.steps.map((s) => s.costUsd) : [];
31536
- const stepAccounting = after ? after.steps.map((s) => {
31537
- if (typeof s.costUsd === "number") {
31538
- return s.costEstimated ? "estimated" : "measured";
31539
- }
31540
- return s.startedAt ? "blind" : void 0;
31541
- }) : [];
31542
- const lastStepCost = after?.steps[state.currentStepIdx]?.costUsd;
31543
- const verdict = (0, budget_1.checkBudget)({ stepCosts, stepAccounting, budget, lastStepCost });
31544
- if (!verdict.ok) {
31545
- hooks.onBudget?.({
31546
- spent: verdict.spent,
31547
- limit: verdict.limit,
31548
- ok: false,
31549
- exceeded: verdict.exceeded,
31550
- 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,
31551
32514
  runId,
31552
- measured: verdict.measured,
31553
- estimated: verdict.estimated,
31554
- 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.`
31555
32516
  });
31556
- return budget.on_exceed === "fail" ? { kind: "error" } : { kind: "budget_pause" };
31557
- }
31558
- hooks.onBudget?.({
31559
- spent: verdict.spent,
31560
- limit: budget.max_usd,
31561
- ok: true,
31562
- runId,
31563
- measured: verdict.measured,
31564
- estimated: verdict.estimated,
31565
- blindSteps: verdict.blindSteps
31566
- });
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;
31567
32537
  }
31568
32538
  if (untilIdx >= 0 && state.currentStepIdx >= untilIdx) {
31569
32539
  hooks.onUntilStop?.({ untilIdx });
@@ -31571,10 +32541,49 @@ var require_execEngine = __commonJS({
31571
32541
  }
31572
32542
  }
31573
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
+ }
31574
32583
  function loadSkills(ws, skillIds) {
31575
32584
  return skillIds.map((id) => ws.skills.load(id)).join("\n\n---\n\n");
31576
32585
  }
31577
- function buildStepPrompt(ws, agent, skillIds, runner, root, epicId) {
32586
+ function buildStepPrompt(ws, agent, skillIds, runner, root, epicId, stepRec, stepIdx) {
31578
32587
  const harness = (0, types_1.harnessCapabilities)(runner);
31579
32588
  return (0, promptComposer_1.composeAgentPrompt)({
31580
32589
  skills: loadSkills(ws, skillIds),
@@ -31584,7 +32593,8 @@ var require_execEngine = __commonJS({
31584
32593
  artifactLanguage: (0, artifactLanguage_1.resolveArtifactLanguage)(ws.config),
31585
32594
  // Per epic, not per workspace: how deep a phase goes is a property of the
31586
32595
  // work item. An epic that never set it reads as strict.
31587
- 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
31588
32598
  });
31589
32599
  }
31590
32600
  async function execStep(root, state, runId, opts, hooks) {
@@ -31596,17 +32606,17 @@ var require_execEngine = __commonJS({
31596
32606
  ws = WorkspaceLoader_1.WorkspaceLoader.load(root);
31597
32607
  } catch (err) {
31598
32608
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Failed to load workspace: ${errMsg(err)}` });
31599
- return false;
32609
+ return "failed";
31600
32610
  }
31601
32611
  const pipeline = ws.config.pipelines.find((p) => p.id === state.pipelineId);
31602
32612
  if (!pipeline) {
31603
32613
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Pipeline "${state.pipelineId}" not found in workspace.yaml.` });
31604
- return false;
32614
+ return "failed";
31605
32615
  }
31606
32616
  const agent = ws.config.agents.find((a) => a.id === agentId);
31607
32617
  if (!agent) {
31608
32618
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Agent "${agentId}" not found in workspace.yaml.` });
31609
- return false;
32619
+ return "failed";
31610
32620
  }
31611
32621
  const stepCfg = pipeline.steps[stepIdx];
31612
32622
  const norm = stepCfg ? (0, WorkspaceSchema_1.normalizeStep)(stepCfg) : void 0;
@@ -31621,22 +32631,38 @@ var require_execEngine = __commonJS({
31621
32631
  runner = ws.runners.resolve(agent);
31622
32632
  } catch (err) {
31623
32633
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Failed to resolve runner for agent "${agentId}": ${errMsg(err)}` });
31624
- return false;
32634
+ return "failed";
31625
32635
  }
31626
32636
  try {
31627
- prompt2 = buildStepPrompt(ws, agent, skillIds, runner, root, state.runId);
32637
+ prompt2 = buildStepPrompt(ws, agent, skillIds, runner, root, state.runId, stepRec, stepIdx);
31628
32638
  } catch (err) {
31629
32639
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Failed to load skills for agent "${agentId}": ${errMsg(err)}` });
31630
- return false;
32640
+ return "failed";
31631
32641
  }
31632
32642
  const skillText = prompt2.text;
31633
32643
  const env = ws.envResolver.resolveLayered(ws.config.environment ?? {}, agent.env ?? {});
32644
+ if (pipeline.autopilot) {
32645
+ env[autopilotPreflight_1.AUTOPILOT_ENV] = "1";
32646
+ }
31634
32647
  const contextStr = Object.entries(state.context).map(([k, v]) => `${k}=${v}`).join(" ");
31635
32648
  const epicRoot = path17.relative(root, (0, EpicScaffold_1.epicsRoot)(root, ws.config)).split(path17.sep).join("/");
31636
32649
  const inherited = state.context.from_epic ? `
31637
32650
 
31638
32651
  About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` : "";
31639
- 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");
31640
32666
  if (opts.dryRun) {
31641
32667
  hooks.onDryRunPreview?.({
31642
32668
  skills: skillIds.join(", "),
@@ -31645,7 +32671,7 @@ About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` :
31645
32671
  env,
31646
32672
  inlined: prompt2.included
31647
32673
  });
31648
- return true;
32674
+ return "done";
31649
32675
  }
31650
32676
  hooks.onStepStart?.({
31651
32677
  stepIdx,
@@ -31659,20 +32685,69 @@ About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` :
31659
32685
  const aliases = (0, pricing_1.providerAliases)(ws.config.providers, agent.runner);
31660
32686
  const resolvedModel = (0, models_1.resolveProviderModel)(agent.runner, agentModel, aliases);
31661
32687
  const producedBefore = (0, producesSnapshot_1.snapshotProduces)({ state, pipeline, workspaceRoot: root, stepIdx });
31662
- const result = await runner.run({
31663
- skill: skillText,
31664
- env,
31665
- args: userMessage ? [userMessage] : [],
31666
- workspaceRoot: root,
31667
- model: agentModel,
31668
- modelAliases: aliases,
31669
- onOutput: (chunk) => hooks.onOutput?.(chunk),
31670
- onError: (chunk) => hooks.onErrorOutput?.(chunk),
31671
- claude: null
31672
- });
31673
- 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) {
31674
32738
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: `Step "${agentId}" failed (non-zero exit).` });
31675
- 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";
31676
32751
  }
31677
32752
  const stale = (0, producesSnapshot_1.unchangedProduces)(producedBefore, root);
31678
32753
  if (stale.length > 0) {
@@ -31681,37 +32756,64 @@ About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` :
31681
32756
  agent: agentId,
31682
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.`
31683
32758
  });
31684
- 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
+ }
31685
32809
  }
31686
32810
  let next;
31687
32811
  let freshState;
31688
32812
  try {
31689
32813
  freshState = RunStateStore_1.RunStateStore.load(root, runId);
31690
32814
  const rec = freshState.steps[stepIdx];
31691
- rec.runner = agent.runner;
31692
- rec.model = resolvedModel;
31693
- if (alternatives) {
31694
- rec.skill = skillIds[0];
31695
- }
31696
- if (modelOpts.models) {
31697
- rec.chosenModel = agentModel;
31698
- }
31699
- rec.usage = result.usage;
31700
- if (typeof result.costUsd === "number") {
31701
- rec.costUsd = result.costUsd;
31702
- rec.costEstimated = void 0;
31703
- } else {
31704
- const est = (0, pricing_1.estimateCostUsd)({
31705
- table: (0, pricing_1.ratesFromConfig)(ws.config.providers),
31706
- provider: agent.runner,
31707
- model: resolvedModel ?? agentModel,
31708
- usage: result.usage
31709
- });
31710
- if (est) {
31711
- rec.costUsd = est.usd;
31712
- rec.costEstimated = true;
31713
- }
31714
- }
32815
+ recordProvenance(rec, { agent, resolvedModel, agentModel, skillIds, alternatives, modelOpts });
32816
+ addBatchCost(rec, batchNo, result, batchCost(result, ws, agent.runner, resolvedModel ?? agentModel));
31715
32817
  next = (0, PipelineRunner_1.markStepDone)({ state: freshState, pipeline, workspaceRoot: root });
31716
32818
  } catch (err) {
31717
32819
  if (err instanceof PipelineRunner_1.PipelineRunError && err.missing?.length) {
@@ -31719,7 +32821,7 @@ About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` :
31719
32821
  } else {
31720
32822
  hooks.onStepFailed?.({ stepIdx, agent: agentId, message: errMsg(err) });
31721
32823
  }
31722
- return false;
32824
+ return "failed";
31723
32825
  }
31724
32826
  persist(root, next, freshState);
31725
32827
  const doneStep = next.steps[stepIdx];
@@ -31732,7 +32834,111 @@ About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` :
31732
32834
  runner: agent.runner,
31733
32835
  model: resolvedModel
31734
32836
  });
31735
- 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);
31736
32942
  }
31737
32943
  async function runAutoReviewStep(root, runId, hooks) {
31738
32944
  const state = RunStateStore_1.RunStateStore.load(root, runId);
@@ -31762,18 +32968,43 @@ About these inputs: ${(0, inheritedContext_1.inheritedContextNote)(epicRoot)}` :
31762
32968
  return false;
31763
32969
  }
31764
32970
  persist(root, next, state);
32971
+ const idx = state.currentStepIdx;
32972
+ const sentBack = verdict.decision === "reject" && next.currentStepIdx !== idx && next.steps[idx].status === "pending";
31765
32973
  hooks.onAutoReviewResult?.({
31766
32974
  agent: step.agent,
31767
32975
  decision: verdict.decision,
31768
- reason: verdict.reason,
31769
- 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
+ } : {}
31770
32987
  });
31771
32988
  return true;
31772
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
+ }
31773
33004
  async function autoApproveStep(root, state, hooks) {
31774
33005
  const ws = WorkspaceLoader_1.WorkspaceLoader.load(root);
31775
33006
  const pipeline = ws.config.pipelines.find((p) => p.id === state.pipelineId);
31776
- const next = (0, PipelineRunner_1.approveStep)({ state, pipeline });
33007
+ const next = { ...(0, PipelineRunner_1.approveStep)({ state, pipeline }), sendBacks: state.sendBacks };
31777
33008
  persist(root, next, state);
31778
33009
  hooks.onAutoApproved?.({ agent: state.steps[state.currentStepIdx].agent });
31779
33010
  }
@@ -31948,6 +33179,10 @@ var require_runReport = __commonJS({
31948
33179
  return ` - \u2714 approved (rev ${h.revision}${h.skill ? `, ${h.skill}` : ""}${h.model ? `, ${h.model}` : ""})`;
31949
33180
  case "undo":
31950
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)}` : ""})`;
31951
33186
  default:
31952
33187
  return " - (unknown event)";
31953
33188
  }
@@ -32593,6 +33828,8 @@ into working code on a feature branch.
32593
33828
  dependsOn: ["review"]
32594
33829
  }
32595
33830
  ];
33831
+ var VERDICT_VALIDATOR = ".aidlc/validators/verdict.mjs";
33832
+ var CI_VALIDATOR = ".aidlc/validators/ci.mjs";
32596
33833
  var AINATIVE_RECIPES = [
32597
33834
  {
32598
33835
  id: "native-quick",
@@ -32601,7 +33838,7 @@ into working code on a feature branch.
32601
33838
  },
32602
33839
  {
32603
33840
  id: "native-lite",
32604
- 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.",
32605
33842
  steps: ["intent", "build-plan", "implement", "review"],
32606
33843
  // Spelled out for all four steps, including the two that would inherit
32607
33844
  // `true` anyway: where the humans stand is the point of this recipe, so
@@ -32610,7 +33847,48 @@ into working code on a feature branch.
32610
33847
  intent: { human_review: true },
32611
33848
  "build-plan": { human_review: true },
32612
33849
  implement: { human_review: false },
32613
- 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
+ }
32614
33892
  }
32615
33893
  },
32616
33894
  {
@@ -32823,7 +34101,9 @@ into working code on a feature branch.
32823
34101
  description: r.description,
32824
34102
  from: workflow.pipelineId,
32825
34103
  steps: r.steps,
32826
- ...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 } : {}
32827
34107
  })),
32828
34108
  sidebar: {
32829
34109
  views: [
@@ -33054,12 +34334,15 @@ export default async function ci(_ctx) {
33054
34334
  }
33055
34335
  `;
33056
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
+ ];
33057
34341
  const seen = /* @__PURE__ */ new Set();
33058
- for (const phase of workflow.phases) {
33059
- if (!phase.autoReview || !phase.autoReviewRunner) {
34342
+ for (const rel of runners) {
34343
+ if (!rel) {
33060
34344
  continue;
33061
34345
  }
33062
- const rel = phase.autoReviewRunner;
33063
34346
  if (path17.isAbsolute(rel) || rel.startsWith("..")) {
33064
34347
  continue;
33065
34348
  }
@@ -34116,13 +35399,14 @@ var require_dist = __commonJS({
34116
35399
  "../core/dist/index.js"(exports2) {
34117
35400
  "use strict";
34118
35401
  Object.defineProperty(exports2, "__esModule", { value: true });
34119
- exports2.SignalParseError = 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.producesEntries = exports2.normalizeStep = exports2.WorkspaceValidationError = exports2.validateWorkspace = exports2.WorkspaceSchema = void 0;
34120
- exports2.recipesDrawingFrom = 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 = void 0;
34121
- exports2.RECENT_EPICS_KEY = 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 = void 0;
34122
- 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.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 = void 0;
34123
- 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 = 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 = void 0;
34124
- 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 = 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 = void 0;
34125
- 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 = 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;
34126
35410
  var WorkspaceSchema_1 = require_WorkspaceSchema();
34127
35411
  Object.defineProperty(exports2, "WorkspaceSchema", { enumerable: true, get: function() {
34128
35412
  return WorkspaceSchema_1.WorkspaceSchema;
@@ -34139,6 +35423,9 @@ var require_dist = __commonJS({
34139
35423
  Object.defineProperty(exports2, "producesEntries", { enumerable: true, get: function() {
34140
35424
  return WorkspaceSchema_1.producesEntries;
34141
35425
  } });
35426
+ Object.defineProperty(exports2, "requiresEntries", { enumerable: true, get: function() {
35427
+ return WorkspaceSchema_1.requiresEntries;
35428
+ } });
34142
35429
  Object.defineProperty(exports2, "stepAgentId", { enumerable: true, get: function() {
34143
35430
  return WorkspaceSchema_1.stepAgentId;
34144
35431
  } });
@@ -34154,6 +35441,24 @@ var require_dist = __commonJS({
34154
35441
  Object.defineProperty(exports2, "collectWorkspaceRefIssues", { enumerable: true, get: function() {
34155
35442
  return WorkspaceSchema_1.collectWorkspaceRefIssues;
34156
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
+ } });
34157
35462
  var PipelineAssembler_1 = require_PipelineAssembler();
34158
35463
  Object.defineProperty(exports2, "assemblePipeline", { enumerable: true, get: function() {
34159
35464
  return PipelineAssembler_1.assemblePipeline;
@@ -34717,6 +36022,38 @@ var require_dist = __commonJS({
34717
36022
  Object.defineProperty(exports2, "claudeModelArg", { enumerable: true, get: function() {
34718
36023
  return DefaultRunner_1.claudeModelArg;
34719
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
+ } });
34720
36057
  var CodexRunner_1 = require_CodexRunner();
34721
36058
  Object.defineProperty(exports2, "CodexRunner", { enumerable: true, get: function() {
34722
36059
  return CodexRunner_1.CodexRunner;
@@ -43891,7 +45228,7 @@ var require_package = __commonJS({
43891
45228
  "package.json"(exports2, module2) {
43892
45229
  module2.exports = {
43893
45230
  name: "@delete101020/aidlc",
43894
- version: "4.0.38",
45231
+ version: "4.0.41",
43895
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.",
43896
45233
  license: "MIT",
43897
45234
  repository: {
@@ -47158,6 +48495,76 @@ function providerChecks(ws) {
47158
48495
  }
47159
48496
  return checks;
47160
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
+ }
47161
48568
  function registerDoctor(program3) {
47162
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) => {
47163
48570
  const root = resolveWorkspaceRoot(cmd);
@@ -47266,6 +48673,7 @@ function registerDoctor(program3) {
47266
48673
  if (ws) {
47267
48674
  emitSection("Harness parity", parityChecks(root, ws));
47268
48675
  emitSection("Providers", providerChecks(ws));
48676
+ emitSection("Unattended runs", autopilotChecks(root, ws));
47269
48677
  }
47270
48678
  const runChecks = [];
47271
48679
  const runsDir = path4.join(root, ".aidlc", "runs");
@@ -47576,21 +48984,9 @@ function findPipelineForRun(root, state) {
47576
48984
  return null;
47577
48985
  }
47578
48986
  }
47579
- var AMBIGUOUS_STEP = -2;
48987
+ var AMBIGUOUS_STEP = import_core7.AMBIGUOUS_STEP_INDEX;
47580
48988
  function resolveStepIdx(state, step) {
47581
- const asInt = parseInt(step, 10);
47582
- if (!isNaN(asInt) && String(asInt) === step) {
47583
- return asInt >= 0 && asInt < state.steps.length ? asInt : -1;
47584
- }
47585
- const byName = state.steps.findIndex((s) => s.name === step);
47586
- if (byName >= 0) {
47587
- return byName;
47588
- }
47589
- const byAgent = state.steps.map((s, i) => s.agent === step ? i : -1).filter((i) => i >= 0);
47590
- if (byAgent.length > 1) {
47591
- return AMBIGUOUS_STEP;
47592
- }
47593
- return byAgent.length === 1 ? byAgent[0] : -1;
48989
+ return (0, import_core7.findStepIndex)(state.steps, step);
47594
48990
  }
47595
48991
  function stepLabel(s) {
47596
48992
  return s.name ?? s.agent;
@@ -48730,19 +50126,40 @@ function registerRun(program3) {
48730
50126
  console.log(import_chalk10.default.green("\u{1F389} Run completed \u2014 all steps approved."));
48731
50127
  }
48732
50128
  });
48733
- 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) => {
48734
50132
  const root = resolveWorkspaceRoot(actionCmd);
48735
50133
  const state = requireRun(root, runId);
48736
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
+ }
48737
50146
  let next;
48738
50147
  try {
48739
- next = (0, import_core13.rejectStep)({ state, reason: opts.reason, pipeline });
50148
+ next = (0, import_core13.rejectStep)({ state, reason: opts.reason, targetIdx, pipeline });
48740
50149
  } catch (err) {
48741
50150
  console.error(import_chalk10.default.red(err instanceof Error ? err.message : String(err)));
48742
50151
  process.exit(1);
48743
50152
  }
48744
50153
  saveRunState(root, next, state);
48745
- 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
+ }
48746
50163
  console.log(import_chalk10.default.red("\u2718") + ` Rejected "${step.agent}"`);
48747
50164
  console.log(import_chalk10.default.dim(` Reason: ${opts.reason}`));
48748
50165
  console.log(import_chalk10.default.dim(` Rerun: aidlc run rerun ${runId} [--feedback "..."]`));
@@ -48845,7 +50262,7 @@ function registerRun(program3) {
48845
50262
  console.log(JSON.stringify(state, null, 2));
48846
50263
  });
48847
50264
  cmd.command("exec <runId>").description(
48848
- "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."
48849
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) => {
48850
50267
  const root = resolveWorkspaceRoot(actionCmd);
48851
50268
  if (opts.json) {
@@ -48952,12 +50369,38 @@ async function execLoop(root, runId, opts) {
48952
50369
  const initialState = requireRun(root, runId);
48953
50370
  const untilIdx = opts.until !== void 0 ? requireStepIdx(initialState, opts.until) : -1;
48954
50371
  const claudeOut = opts.json ? process.stderr : process.stdout;
48955
- return (0, import_core13.runExecLoop)(
48956
- root,
48957
- runId,
48958
- { untilIdx, autoApprove: opts.autoApprove, message: opts.message, dryRun: opts.dryRun, skill: opts.skill },
48959
- cliExecHooks(runId, claudeOut)
48960
- );
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
+ }
48961
50404
  }
48962
50405
  async function driveAutoReview(root, runId, state) {
48963
50406
  const pipeline = requirePipelineForRun(root, state);
@@ -49057,6 +50500,35 @@ ${reason}`)),
49057
50500
  \u2718 Step "${e.agent}" was rejected.`));
49058
50501
  info(import_chalk10.default.dim(` Rerun: aidlc run rerun ${e.runId} [--feedback "..."]`));
49059
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
+ },
49060
50532
  onStepSkipped: (e) => {
49061
50533
  info(import_chalk10.default.yellow(`
49062
50534
  \u2933 Step "${e.agent}" was rejected \u2014 carrying on.`) + import_chalk10.default.dim(` ${e.reason}`));
@@ -49067,6 +50539,9 @@ ${reason}`)),
49067
50539
  onAutoReviewResult: (e) => {
49068
50540
  if (e.decision === "pass") {
49069
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}`));
49070
50545
  } else {
49071
50546
  info(import_chalk10.default.red("\u2718 Auto-review rejected") + import_chalk10.default.dim(` \u2014 ${e.reason}`));
49072
50547
  info(import_chalk10.default.dim(` Fix the issue, then: aidlc run rerun ${e.runId} [--feedback "..."]`));